2

I'm trying to stream a mp4 file to iOS devices (iPhone and iPad) from a Grails controller:

def streamContent() {
    def contentPath = "/path/to/file"
    File f = new File(contentPath)
    if(f.exists()) {
        response.setContentType("video/mp4")
        response.outputStream << f.newInputStream()
        response.outputStream.flush()
        response.outputStream.close()
    } else {
        render status: 404
    }
}

this code works well on desktop browsers like safari (I see the video), but when I access to the same page with an iPhone or an iPad the video won't play. Note that if I put the same video on Apache httpd and I request it from iOS devices, there is no problem. So it must be a streaming problem.

On the html page the video is embedded using HTML5 video tag:

<video width="360" height="200" controls>
    <source src="http://localhost:8080/myapp/controller/streamContent" type='video/mp4'>
</video>
Pierpaolo Follia
  • 3,020
  • 1
  • 14
  • 11

2 Answers2

0

I solved this problem by handling Partial Content and Range Requests (HTTP 206 status). It seems that mobile browsers/media players are using partial requests the avoid to much data transfer all at once. So instead of doing a simple

response.outputStream << f.newInputStream()

I read only the requested bytes when the request is for a range of bytes:

if (isRange) {
    //start and end are requested bytes offsets
    def bytes = new byte[end-start]
    f.newInputStream().read(bytes, start, bytes.length)
    response.outputStream << bytes
    response.status = 206
} else {
    response.outputStream << f.newInputStream()
    response.status = 200
}
Pierpaolo Follia
  • 3,020
  • 1
  • 14
  • 11
0

I don't have enough reputation to comment yet, but I just want to point out that the answer above is not complete, specifically that you need to include additional headers, and that the usage of f.newInputStream().read() is not being used accurately, as it won't just read a chunk at any starting point from the input stream, but it will read a chunk from the current position, so you must use save the inputStream and then use inputStream.skip() to jump to the right position.

I have a more complete answer here (where I answered my own similar question) https://stackoverflow.com/a/23137725/2601060

Community
  • 1
  • 1
mnd
  • 2,709
  • 3
  • 27
  • 48