2017-09-19 98 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 
      } 
     } 
    } 
}