2012-12-21 66 views
1

我有一个m3u8播放列表文件(让我们称之为素数),它指向另一个播放列表文件,该文件又具有包含密钥文件URL的ts URL。使用MPMoviePlayer我目前可以播放prime m3u8文件。 段是encryptedAES-128位加密和密钥文件是在最后m3u8文件。有没有一种方法可以提供最终的m3u8文件并告诉应用程序使用本地密钥文件来解密视频,因此我不必公开发布密钥文件。它传递给玩家之前,可以修改最终M3U8文件 -如何通过分别提供密钥文件来播放m3u8加密播放列表?

这在一定程度上关系到this SO question

回答

1

是。例如,将KEY行更改为http://localhost/key。然后你会想运行一个本地的http服务器,比如cocoahttpserver来把密钥交给视频播放器。

+0

我的意思是有一种方法可以将密钥文件本地存储在应用程序中,而不是通过互联网发布它 – kitwalker

+0

@kitwalker呃,是的,这正是我在回答中所描述的。它必须通过HTTP传送,但不需要通过互联网传送。 – vipw

+0

谢谢。我会尽力做到这一点。 – kitwalker

4

我已经实现了类似的东西。我们所做的是:

  1. 加密直播流段的每个段在运行时与JWT 令牌具有键值对和 验证时间戳的组合。
  2. 我们的服务器知道如何解密这个密钥。当解密数据有效时,服务器以.ts文件和 响应,因此播放变得安全。

这里提到的与步骤的完整的工作代码:

//Step 1,2:- Initialise player, change the scheme from http to fakehttp and set delete of resource loader. These both steps will trigger the resource loader delegate function so that we can manually handle the loading of segments. 

func setupPlayer(stream: String) { 

operationQ.cancelAllOperations() 
let blckOperation = BlockOperation { 


    let currentTStamp = Int(Date().timeIntervalSince1970 + 86400)// 
    let timeStamp = String(currentTStamp) 
    self.token = JWT.encode(["Expiry": timeStamp], 
          algorithm: .hs256("qwerty".data(using: .utf8)!)) 

    self.asset = AVURLAsset(url: URL(string: "fake\(stream)")!, options: nil) 
    let loader = self.asset?.resourceLoader 
    loader?.setDelegate(self, queue: DispatchQueue.main) 
    self.asset!.loadValuesAsynchronously(forKeys: ["playable"], completionHandler: { 


     var error: NSError? = nil 
     let keyStatus = self.asset!.statusOfValue(forKey: "playable", error: &error) 
     if keyStatus == AVKeyValueStatus.failed { 
      print("asset status failed reason \(error)") 
      return 
     } 
     if !self.asset!.isPlayable { 
      //FIXME: Handle if asset is not playable 
      return 
     } 

     self.playerItem = AVPlayerItem(asset: self.asset!) 
     self.player = AVPlayer(playerItem: self.playerItem!) 
     self.playerView.playerLayer.player = self.player 
     self.playerLayer?.backgroundColor = UIColor.black.cgColor 
     self.playerLayer?.videoGravity = AVLayerVideoGravityResizeAspect 

     NotificationCenter.default.addObserver(self, selector: #selector(self.playerItemDidReachEnd(notification:)), name: Notification.Name.AVPlayerItemDidPlayToEndTime, object: self.playerItem!) 
     self.addObserver(self, forKeyPath: "player.currentItem.duration", options: [.new, .initial], context: &playerViewControllerKVOContext) 
     self.addObserver(self, forKeyPath: "player.rate", options: [.new, .old], context: &playerViewControllerKVOContext) 
     self.addObserver(self, forKeyPath: "player.currentItem.status", options: [.new, .initial], context: &playerViewControllerKVOContext) 
     self.addObserver(self, forKeyPath: "player.currentItem.loadedTimeRanges", options: [.new], context: &playerViewControllerKVOContext) 
     self.addObserver(self, forKeyPath: "player.currentItem.playbackLikelyToKeepUp", options: [.new], context: &playerViewControllerKVOContext) 
     self.addObserver(self, forKeyPath: "player.currentItem.playbackBufferEmpty", options: [.new], context: &playerViewControllerKVOContext) 
    }) 
} 


operationQ.addOperation(blckOperation) 
} 

//Step 2, 3:- implement resource loader delegate functions and replace the fakehttp with http so that we can pass this m3u8 stream to the parser to get the current m3u8 in string format. 

func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool { 

var url = loadingRequest.request.url?.absoluteString 

let contentRequest = loadingRequest.contentInformationRequest 
let dataRequest = loadingRequest.dataRequest 
//Check if the it is a content request or data request, we have to check for data request and do the m3u8 file manipulation 

if (contentRequest != nil) { 

    contentRequest?.isByteRangeAccessSupported = true 
} 
if (dataRequest != nil) { 

    //this is data request so processing the url. change the scheme to http 

    url = url?.replacingOccurrences(of: "fakehttp", with: "http") 

    if (url?.contains(".m3u8"))! 
    { 

     // do the parsing on background thread to avoid lags 
// step 4: 
     self.parsingHandler(url: url!, loadingRequest: loadingRequest, completion: { (success) in 

      return true 
     }) 
    } 
    else if (url?.contains(".ts"))! { 

     let redirect = self.generateRedirectURL(sourceURL: url!) 

     if (redirect != nil) { 
      //Step 9 and 10:- 
      loadingRequest.redirect = redirect! 
      let response = HTTPURLResponse(url: URL(string: url!)!, statusCode: 302, httpVersion: nil, headerFields: nil) 
      loadingRequest.response = response 
      loadingRequest.finishLoading() 
     } 
     return true 
    } 
    return true 
} 
return true 
} 

func parsingHandler(url: String, loadingRequest: AVAssetResourceLoadingRequest, completion:((Bool)->Void)?) -> Void { 

DispatchQueue.global(qos: .background).async { 

    var string = "" 

    var originalURIStrings = [String]() 
    var updatedURIStrings = [String]() 

    do { 

     let model = try M3U8PlaylistModel(url: url) 
     if model.masterPlaylist == nil { 
      //Step 5:- 
      string = model.mainMediaPl.originalText 
      let array = string.components(separatedBy: CharacterSet.newlines) 
      if array.count > 0 { 

       for line in array { 
        //Step 6:- 
        if line.contains("EXT-X-KEY:") { 

         //at this point we have the ext-x-key tag line. now tokenize it with , and then 
         let furtherComponents = line.components(separatedBy: ",") 

         for component in furtherComponents { 

          if component.contains("URI") { 
           // Step 7:- 
           //save orignal URI string to replaced later 
           originalURIStrings.append(component) 

           //now we have the URI 
           //get the string in double quotes 

           var finalString = component.replacingOccurrences(of: "URI=\"", with: "").replacingOccurrences(of: "\"", with: "") 

           finalString = "\"" + finalString + "&token=" + self.token! + "\"" 
           finalString = "URI=" + finalString 
           updatedURIStrings.append(finalString) 
          } 
         } 
        } 

       } 
      } 

      if originalURIStrings.count == updatedURIStrings.count { 
       //Step 8:- 
       for uriElement in originalURIStrings { 

        string = string.replacingOccurrences(of: uriElement, with: updatedURIStrings[originalURIStrings.index(of: uriElement)!]) 
       } 

       //print("String After replacing URIs \n") 
       //print(string) 
      } 
     } 

     else { 

      string = model.masterPlaylist.originalText 
     } 
    } 
    catch let error { 

     print("Exception encountered") 
    } 

    loadingRequest.dataRequest?.respond(with: string.data(using: String.Encoding.utf8)!) 
    loadingRequest.finishLoading() 

    if completion != nil { 
     completion!(true) 
    } 
} 
} 

func generateRedirectURL(sourceURL: String)-> URLRequest? { 

    let redirect = URLRequest(url: URL(string: sourceURL)!) 
    return redirect 
} 
  1. 实现资产资源加载委托流的自定义处理。
  2. 假现场流的方案,以便资源加载器委托被调用(对于普通的http/https,它不会被调用,玩家试图处理流本身)
  3. 用Http方案替换假方案。
  4. 将流传递给M3U8解析器以获取纯文本格式的m3u8文件。
  5. 解析纯字符串以查找当前字符串中的EXT-X-KEY标记。
  6. 将EXT-X-KEY行标记为“URI”方法字符串。
  7. 追加单独制作的JWT令牌,使用m3u8中的当前URI方法。
  8. 用新的附加标记URI字符串替换当前m3u8字符串中的所有URI实例。
  9. 将此字符串转换为NSData格式
  10. 将其再次送入播放器。

希望这会有所帮助!

相关问题