Jared Loomis Jared Loomis - 2 months ago 19
Scala Question

Play Framework eating up disk space

I am successfully serving videos using the Play framework, but I'm experiencing an issue: each time a file is served, the Play framework creates a copy in C:\Users\user\AppData\Temp. I'm serving large files so this quickly creates a problem with disk space.

Is there any way to serve a file in Play without creating a copy? Or have Play automatically delete the temp file?

Code I'm using to serve is essentially:

public Result video() {
return ok(new File("whatever"));
}

Answer

Use Streaming

I use following method for video streaming. This code does not create temp copies of the media file.

Basically this code responds to the RANGE queries sent by the browser. If browser does not support RANGE queries I fallback to the method where I try to send the whole file using Ok.sendFile (internally play also tries to stream the file) (this might create temp files). but this happens very rarely when range queries is not supported by the browser.

GET   /media       controllers.MediaController.media

Put this code inside a Controller called MediaController

def media = Action { req =>
     val file = new File("/Users/something/Downloads/somefile.mp4")
     val rangeHeaderOpt = req.headers.get(RANGE)
     rangeHeaderOpt.map { range =>
       val strs = range.substring("bytes=".length).split("-")
       if (strs.length == 1) {
         val start = strs.head.toLong
         val length = file.length() - 1L
         partialContentHelper(file, start, length)
       } else {
         val start = strs.head.toLong
         val length = strs.tail.head.toLong
         partialContentHelper(file, start, length)
       }
     }.getOrElse {
       Ok.sendFile(file)
     }
   }

   def partialContentHelper(file: File, start: Long, length: Long) = {
     val fis = new FileInputStream(file)
     fis.skip(start)
     val byteStringEnumerator = Enumerator.fromStream(fis).&>(Enumeratee.map(ByteString.fromArray(_)))
     val mediaSource = Source.fromPublisher(Streams.enumeratorToPublisher(byteStringEnumerator))
     PartialContent.sendEntity(HttpEntity.Streamed(mediaSource, None, None)).withHeaders(
       CONTENT_TYPE -> MimeTypes.forExtension("mp4").get,
       CONTENT_LENGTH -> ((length - start) + 1).toString,
       CONTENT_RANGE -> s"bytes $start-$length/${file.length()}",
       ACCEPT_RANGES -> "bytes",
       CONNECTION -> "keep-alive"
     )
   }
Comments