2017-09-19 94 views
2

我在旧的Grails 2.5.1应用程序中,并且我注意到服务器提供的mp4视频文件不在Safari中播放。我在SO上查找了这个问题,并得到了一些提示,说明它与范围标题有关。但我怀疑我处理范围标题的方式并不完全正确。在grails中通过http使用范围标头流式传输mp4请求

到目前为止,我发现的是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 

随后,第二发送GET请求:

host:  localhost:8080 
language:  en-us 
playback-session-id:  03F1B4E6-F97E-**** 
bytes=0-1 
accept:  */* 
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 
https://.../videos/lol.mp4 
encoding:  identity 
request-time:  t=**** 
forwarded-for:  *.*.*.* 
forwarded-host:  *.com 
forwarded-server:  *.com 
connection:  Keep-Alive 
cookie: ...TOO BIG TO SHOW HERE 
<- "GET /uiv2/videos/lol.mp4 HTTP/1.1" 206 149ms 

调试,这是困难的,因为Safari网络检查员不告诉你了。事实上,它甚至不会向你显示它发送的所有标题,所以我必须从后端得到它。

可以看出,请求1和2之间的区别在于2nd具有回放会话ID和范围。

难题在于了解如何在Safari中处理范围和播放会话标识。

我做了一个控制器来返回请求的字节范围,如果他们请求。但仍然没有运气。

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 
     } 
    } 
} 

在Safari浏览器的控制台,我得到:

Failed to load resource: Plug-in handled load 

没有别的。可惜的是,网络监察员中的很多领域都是空白的,即使他们显然设置在服务器中。

enter image description here

我在这一点上,任何帮助,指针,提示可以理解的尝试这么多东西。多谢你们 :) !

回答

1

在尝试了很多事情并搜遍了很多帖子后,此公式奏效。你需要所有这四个头文件。不需要在第一个请求中返回任何内容。这可能不适用于所有浏览器,但这适用于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 
      } 
     } 
    } 
}