通过http传输mp4请求,grails中带有范围标头

通过http传输mp4请求,grails中带有范围标头,http,grails,safari,video-streaming,mp4,Http,Grails,Safari,Video Streaming,Mp4,我使用的是旧的Grails2.5.1应用程序,我注意到服务器提供的mp4视频文件不能在Safari中播放。我在SO上查找了这个问题,得到了一些提示,它与范围标题有关。但我怀疑我处理范围标题的方式不太正确 到目前为止,我发现Mac OS Safari 11.0(11604.1.38.1.7)(我现在不关心ios Safari)发送了两个GET请求。首先,它发送一个带有: host: localhost:8080 accept: text/html,application/xhtml

我使用的是旧的Grails2.5.1应用程序,我注意到服务器提供的mp4视频文件不能在Safari中播放。我在SO上查找了这个问题,得到了一些提示,它与范围标题有关。但我怀疑我处理范围标题的方式不太正确

到目前为止,我发现Mac OS Safari 11.0(11604.1.38.1.7)(我现在不关心ios Safari)发送了两个GET请求。首先,它发送一个带有:

host:     localhost:8080
accept:     text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
user-agent:     Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Safari/604.1.38
accept-language:     en-us
accept-encoding:     gzip, deflate
x-request-time:     t=****
x-forwarded-for:     *.*.*.*
x-forwarded-host:     *.com
x-forwarded-server:     *.com
connection:     Keep-Alive
cookie: ...TOO BIG TO SHOW HERE
<- "GET /.../videos/lol.mp4 HTTP/1.1" 200 186ms 
没有别的了。遗憾的是,web inspector中的许多字段都是空白的,尽管它们显然是在服务器中设置的


在这一点上,我已经尝试了很多东西,任何帮助、指点和提示都将不胜感激。谢谢大家:)

在尝试了许多事情并浏览了许多帖子之后,这个公式奏效了。您需要所有四个标题。不需要在第一个请求中返回任何内容。这可能不适用于所有浏览器,但适用于safari。额外的修改可以确保处理所有浏览器

class VideoController {
    GrailsApplication grailsApplication
    AssetResourceLocator assetResourceLocator

    public index() {
        Resource mp4Resource = assetResourceLocator.findAssetForURI('/../lol.mp4')

        String range = request.getHeader('range')
        if(range) {
            String[] rangeKeyValue = range.split('=')
            String[] rangeEnds = rangeKeyValue[1].split('-')
            if(rangeEnds.length  > 1) {
                int startByte = Integer.parseInt(rangeEnds[0])
                int endByte = Integer.parseInt(rangeEnds[1])
                int contentLength = (endByte - startByte) + 1
                byte[] inputBytes = new byte[contentLength]
                def inputStream = mp4Resource.inputStream
                inputStream.skip(startByte) // input stream always starts at the first byte, so skip bytes until you get to the start of the requested range
                inputStream.read(inputBytes, 0, contentLength) // read from the first non-skipped byte
                response.reset() // Clears any data that exists in the buffer as well as the status code and headers
                response.status = 206
                response.addHeader("Content-Type", "video/mp4")
                response.addHeader( 'Accept-Ranges', 'bytes')
                response.addHeader('Content-Range', "bytes ${startByte}-${endByte}/${mp4Resource.contentLength()}")
                response.addHeader( 'Content-Length', "${contentLength}")
                response.outputStream << inputBytes
            }
        }
    }
}
class视频控制器{
GrailsApplication GrailsApplication
AssetResourceLocator AssetResourceLocator
公共索引(){
Resource mp4Resource=assetResourceLocator.findAssetForURI('/../lol.mp4')
字符串范围=request.getHeader('range')
如果(范围){
字符串[]rangeKeyValue=range.split('='))
字符串[]rangeEnds=rangeKeyValue[1]。拆分(“-”)
如果(rangeEnds.length>1){
int startByte=Integer.parseInt(rangeEnds[0])
int endByte=Integer.parseInt(rangeEnds[1])
int contentLength=(endByte-startByte)+1
byte[]inputBytes=新字节[contentLength]
def inputStream=mp4Resource.inputStream
inputStream.skip(startByte)//输入流总是从第一个字节开始,所以跳过字节直到到达请求范围的开始
读取(inputBytes,0,contentLength)//从第一个未跳过的字节读取
response.reset()//清除缓冲区中存在的所有数据以及状态代码和标头
response.status=206
响应.addHeader(“内容类型”、“视频/mp4”)
addHeader('Accept Ranges','bytes')
addHeader('Content-Range',“bytes${startByte}-${endByte}/${mp4Resource.contentLength()}”)
addHeader('Content Length',“${contentLength}”)
response.outputStream
import grails.compiler.GrailsTypeChecked
import grails.plugin.springsecurity.annotation.Secured
import asset.pipeline.grails.AssetResourceLocator
import grails.util.BuildSettings
import org.codehaus.groovy.grails.commons.GrailsApplication
import org.springframework.core.io.Resource

class VideoController {
    GrailsApplication grailsApplication
    AssetResourceLocator assetResourceLocator

    public index() {
        Resource mp4Resource = assetResourceLocator.findAssetForURI('/../lol.mp4');

        response.addHeader("Content-type", "video/mp4")
        response.addHeader( 'Accept-Ranges', 'bytes')

        String range = request.getHeader('range')
        if(range) {
            String[] rangeKeyValue = range.split('=')
            String[] rangeEnds = rangeKeyValue[1].split('-')
            if(rangeEnds.length  > 1) {
                int startByte = Integer.parseInt(rangeEnds[0])
                int endByte = Integer.parseInt(rangeEnds[1])
                int contentLength = (endByte - startByte) + 1
                byte[] inputBytes = new byte[contentLength]
                mp4Resource.inputStream.read(inputBytes, startByte, contentLength)
                response.status = 206
                response.addHeader( 'Content-Length', "${contentLength}")
                response.outputStream << inputBytes

            } else {
                response.addHeader( 'Content-Length', "${mp4Resource.contentLength()}")
                response.outputStream << mp4Resource.inputStream
            }
        } else {
            log.info 'no range, so responding with whole mp4'
            response.addHeader( 'Content-Length', "${mp4Resource.contentLength()}")
            response.outputStream << mp4Resource.inputStream
        }
    }
}
Failed to load resource: Plug-in handled load
class VideoController {
    GrailsApplication grailsApplication
    AssetResourceLocator assetResourceLocator

    public index() {
        Resource mp4Resource = assetResourceLocator.findAssetForURI('/../lol.mp4')

        String range = request.getHeader('range')
        if(range) {
            String[] rangeKeyValue = range.split('=')
            String[] rangeEnds = rangeKeyValue[1].split('-')
            if(rangeEnds.length  > 1) {
                int startByte = Integer.parseInt(rangeEnds[0])
                int endByte = Integer.parseInt(rangeEnds[1])
                int contentLength = (endByte - startByte) + 1
                byte[] inputBytes = new byte[contentLength]
                def inputStream = mp4Resource.inputStream
                inputStream.skip(startByte) // input stream always starts at the first byte, so skip bytes until you get to the start of the requested range
                inputStream.read(inputBytes, 0, contentLength) // read from the first non-skipped byte
                response.reset() // Clears any data that exists in the buffer as well as the status code and headers
                response.status = 206
                response.addHeader("Content-Type", "video/mp4")
                response.addHeader( 'Accept-Ranges', 'bytes')
                response.addHeader('Content-Range', "bytes ${startByte}-${endByte}/${mp4Resource.contentLength()}")
                response.addHeader( 'Content-Length', "${contentLength}")
                response.outputStream << inputBytes
            }
        }
    }
}