我有一个m3u8
播放列表文件(让我们称之为素数),它指向另一个播放列表文件,该文件又具有带有密钥文件URL的ts网址。使用MPMoviePlayer
我当前可以播放prime m3u8
文件。
这些段为encrypted
,加密AES-128
位,密钥文件位于最终m3u8
文件中。有没有办法可以提供最终的m3u8
文件并告诉应用程序使用本地密钥文件来解密视频,因此我不必公开发布密钥文件。
答案 0 :(得分:5)
我已经实现了类似的东西。我们做的是:
以下是完整的工作代码,其中包含以下步骤:
//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 :(得分:1)
是 - 您可以在将最终m3u8文件传递给播放器之前对其进行修改。例如,更改KEY行以引用http://localhost/key
。然后你想运行一个本地的http服务器,如cocoahttpserver,将密钥传递给视频播放器。