reproductor pasar para m3u listas lista crear convertir archivo objective-c ios ios5 http-live-streaming

objective c - pasar - ¿Cómo se reproducen las listas de reproducción cifradas de m3u8 proporcionando el archivo de clave por separado?



m3u8 reproductor (2)

Sí, puede modificar el archivo m3u8 final antes de pasarlo al reproductor. Por ejemplo, cambie las líneas KEY para hacer referencia a http://localhost/key . Luego, querrá ejecutar un servidor http local como cocoahttpserver para entregar la clave del reproductor de video.

Tengo un archivo de lista de reproducción m3u8 (vamos a llamarlo principal), que apunta a otro archivo de lista de reproducción que a su vez tiene las URL de ts con la URL del archivo de clave. Usando MPMoviePlayer actualmente puedo reproducir el archivo prime m3u8 . Los segmentos se encrypted con AES-128 bits AES-128 y el archivo de claves se encuentra en el archivo m3u8 final. ¿Hay alguna manera de que pueda suministrar el archivo m3u8 final y decirle a la aplicación que use un archivo de clave local para descifrar el video, por lo que no tengo que publicar el archivo de clave públicamente?

Esto está relacionado de alguna manera con esta pregunta SO


He implementado algo similar a esto. Lo que hicimos fue:

  1. Encripte cada segmento del segmento de transmisión en tiempo real en tiempo de ejecución con un token JWT que tenga una combinación de pares de valores clave y sello de tiempo para la validación.
  2. Nuestro servidor sabe cómo descifrar esta clave. y cuando los datos descifrados son válidos, el servidor responde con un archivo .ts y, por lo tanto, la reproducción se vuelve segura.

Aquí está el código de trabajo completo con los pasos mencionados:

//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. Implementar delegado del cargador de recursos de activos para el manejo personalizado de las transmisiones.
  2. Simular el esquema de transmisión en vivo para que se llame al delegado del cargador de recursos (para http / https normal no se llama y el reproductor intenta manejar la transmisión en sí)
  3. Reemplace el Esquema falso con el esquema Http.
  4. Pase la secuencia al Analizador M3U8 para obtener el archivo m3u8 en formato de texto sin formato.
  5. Analice la cadena simple para buscar las etiquetas EXT-X-KEY en la cadena actual.
  6. Tokenice la línea EXT-X-KEY para llegar a la cadena del método "URI".
  7. Añada el token JWT por separado, con el método URI actual en el m3u8.
  8. Reemplace todas las instancias de URI en la cadena m3u8 actual con la nueva cadena URI con el token nuevo.
  9. Convierta esta cadena a formato NSData
  10. Aliméntalo al jugador de nuevo.

¡Espero que esto ayude!