Play a protected video content in iOS using AVPlayer - ios

im trying to play a protected video which you need to be logged thru our API and get a cookie to access to the m3u8. In Safari, i need to be logged in in order to play the video. I tried to implement this but doesnt work at all.
let cookies: [Any] = HTTPCookieStorage.shared.cookies!
self.playerAsset = AVURLAsset.init(url: urlStream!, options:[AVURLAssetHTTPCookiesKey : cookies])
let item = AVPlayerItem(asset: self.playerAsset!)
self.playerItems.append(item)
Anyone could help me ?

could you play the video in saffari? If yes then you can use the new safari webview instead of avplayer

Related

Playing Offline HLS with AES-128 encryption iOS

I want to integrate offline HLS in iOS through AVFoundation.
I have an encrypted HLS with simple AES-128 and it doesn't want to play in offline mode, I was trying to integrate AVAssetResourceLoaderDelegate, but don't know how to integrate applicationCertificate and contentKeyFromKeyServerModuleWithSPCData that are in https://developer.apple.com/streaming/fps/ examples. I have a feeling that I am doing something wrong. It is a sample AES-128 encryption, not even DRM.
Without the internet, AVPlayer is still trying to get encryption key through GET request.
It would be great if someone succeeded to save the encrypted key locally and somehow gave it to AVPlayer together with AVURLAsset.
Did someone manage to integrate this?
I have written to apple support and their responses weren't new for me. Information that they provided to me I got from wwdc videos and documentation before I started a conversation with them. (https://developer.apple.com/streaming/fps/)
Further, I will describe how I achieve to play HLS in offline mode with AES-128 encryption. The Example On Github describes the below process.
Take care AVDownloadTask doesn’t work on the simulator so you should have a device for this implementation.
At the beginning, you need a stream URL.
Step 1:
Before creating AVURLAsset we should take stream URL and change scheme to an invalid one (example: https -> fakehttps, I did it through URLComponents) and assign AVAssetResourceLoaderDelegate to the new created url asset. All this changes force AVAssetDownloadTask to call:
func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {
}
(it is calling because AVFoundation see an invalid URL and doesn’t know what to do with it)
Step 2:
When delegate is called we should check that url is that one that we had before. We need to change back scheme to valid one and create a simple URLSession with it. We will get first .m3u8 file that should be like:
#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1697588,RESOLUTION=1280x720,FRAME-RATE=23.980,CODECS="mp4a"
https://avid.avid.net/avid/information_about_stream1
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1132382,RESOLUTION=848x480,FRAME-RATE=23.980,CODECS="mp4a"
https://avid.avid.net/avid/information_about_stream2
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=690409,RESOLUTION=640x360,FRAME-RATE=23.980,CODECS="mp4a"
https://avid.avid.net/avid/information_about_stream3
Step 3:
Parse all needed information from this data and change all https schemes to invalid one fakehttps
Now you should setup AVAssetResourceLoadingRequest from shouldWaitForLoadingOfRequestedResource delegate like:
loadingRequest.contentInformationRequest?.contentType = response.mimeType
loadingRequest.contentInformationRequest?.isByteRangeAccessSupported = true
loadingRequest.contentInformationRequest?.contentLength = response.expectedContentLength
loadingRequest.dataRequest?.respond(with: modifiedData)
loadingRequest.finishLoading()
downloadTask?.resume()
where: response -> response from URLSession, modifiedData -> data with changed URL’s
Resume your download task and return true in shouldWaitForLoadingOfRequestedResource delegate
Step 4:
If everything will be ok AVAssetDownloadDelegate will fire with:
- (void)URLSession:(NSURLSession *)session assetDownloadTask:(AVAssetDownloadTask *)assetDownloadTask didResolveMediaSelection:(AVMediaSelection *)resolvedMediaSelection NS_AVAILABLE_IOS(9_0) {
}
Step 5:
We have changed all https to fakehttps when AVFoundation will select best media stream URL, shouldWaitForLoadingOfRequestedResource will trigger again with one of the URL from first .m3u8
Step 6:
When delegate is called again we should check that url is that one that we needed. Change again fake scheme to a valid one and create a simple URLSession with this url. We will get second .m3u8 file:
#EXTM3U
#EXT-X-TARGETDURATION:12
#EXT-X-ALLOW-CACHE:YES
#EXT-X-KEY:METHOD=AES-128,URI="https://avid.avid.net/avid/key”
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:1
#EXTINF:6.006,
https://avid.avid.net/avid/information_about_stream1
#EXTINF:4.713,
https://avid.avid.net/avid/information_about_stream2
#EXTINF:10.093,
https://avid.avid.net/avid/information_about_stream3
#EXT-X-ENDLIST
Step 7:
Parse second .m3u8 file and take all information that you need from it, also take a look on
#EXT-X-KEY:METHOD=AES-128,URI="https://avid.avid.net/avid/key”
We have URL for encryption key
Step 8:
Before sending some information back to AVAssetDownloadDelegate we need to download the key from the server and save it locally on the device. After this you should change URI=https://avid.avid.net/avid/key from second .m3u8 to an invalid URI=fakehttps://avid.avid.net/avid/key, or maybe a local file path where you have saved your local key.
Now you should setup AVAssetResourceLoadingRequest from shouldWaitForLoadingOfRequestedResource delegate smth. like:
loadingRequest.contentInformationRequest?.contentType = response.mimeType
loadingRequest.contentInformationRequest?.isByteRangeAccessSupported = true
loadingRequest.contentInformationRequest?.contentLength = response.expectedContentLength
loadingRequest.dataRequest?.respond(with: modifiedData)
loadingRequest.finishLoading()
downloadTask?.resume()
where: response -> response from URLSession, modifiedData -> data with changed URL’s
Resume your download task and return true in shouldWaitForLoadingOfRequestedResource delegate (Same as on Step 3)
Step 9:
Of course, when download task will try to create request with modified URI= that again is not a valid one shouldWaitForLoadingOfRequestedResource will trigger again. In this case, you should detect this and create new data with your persistent key(the key that you saved locally. Take care here contentType should be AVStreamingKeyDeliveryPersistentContentKeyType without it AVFoundation doesn’t understand that this contains key).
loadingRequest.contentInformationRequest?.contentType = AVStreamingKeyDeliveryPersistentContentKeyType
loadingRequest.contentInformationRequest?.isByteRangeAccessSupported = true
loadingRequest.contentInformationRequest?.contentLength = keyData.count
loadingRequest.dataRequest?.respond(with: keyData)
loadingRequest.finishLoading()
downloadTask?.resume()
Step 10:
Chunks will be downloaded automatically by AVFoudnation.
When download is finished this delegate will be called:
func urlSession(_ session: URLSession, assetDownloadTask: AVAssetDownloadTask, didFinishDownloadingTo location: URL) {
}
You should save location somewhere, when you want to play stream from device you should create AVURLAsset from this location URL
All this information is saved locally by AVFoundation so next time when you will try to play local content in offline AVURLAsset delegate will be called because of URI=fakehttps://avid.avid.net/avid/key, that is an invalid link, here you will do Step 9 again and video will play in offline mode.
This works for me if anyone knows better implementation I will be glad to know.
Example On Github
#Cyklet Answer helped me a ton! Thanks.
There is however an additional step I had to do to get hls streaming to work.
When using shouldWaitForLoadingOfRequestedResource apples documentation states:
contentType
Before finishing loading an AVAssetResourceLoadingRequest instance, if its contentInformationRequest property is not nil, set the value of this property to a UTI indicating the type of data contained by the requested resource.
When trying to implement HLS Streaming there are two UTIs that may be used (as far as I know...).
AVStreamingKeyDeliveryPersistentContentKeyType
"com.apple.streamingkeydelivery.persistentcontentkey"
AVStreamingKeyDeliveryContentKeyType
"com.apple.streamingkeydelivery.contentkey"
Check allowedContentTypes what UTI to use. See possible implementation below:
func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {
....
var contentType = AVStreamingKeyDeliveryPersistentContentKeyType
if let allowedContentType = contentInformationRequest.allowedContentTypes?.first{
if allowedContentType == AVStreamingKeyDeliveryContentKeyType{
contentType = AVStreamingKeyDeliveryContentKeyType
}
}
....
}

iMessaged-based invitations for GameCenter for iOS 10

I'm trying to update my app to work correctly with the new features of GameCenter in iOS10.
I create a new GKGameSession on device1, get a share URL, and all that works fine. I send the share URL out via a share sheet to device 2.
Device2 clicks the link, the device briefly displays 'Retrieving...' and then launches my app. Great! But, now what? Is there context information available for this URL that I can somehow access? Otherwise I have no way how to respond when the app is launched.
Previously you'd get a callback to something adhering to the GKLocalPlayerListener protocol, to the method player:didAcceptInvite:, and you could join the match that way. But with these iCloud-based messages, the player might not be even logged into GameCenter, right? This part seems to have been glossed over in the WWDC presentation.
Also, as of today (12/28/2016) there is no Apple documentation on these new methods.
Since the GKGameSessionEventListener callback session:didAddPlayer: only fires if the game is already running, to be sure you can process this callback every time requires a work around. I've tested this and it works.
When you send out an iMessage or email invite to the game, don't include the Game Session Invite URL directly in the message. Instead use a registered URL that will open your app when opened on a device on which your app is installed. Check here to see how:
Complete Tutorial on iOS Custom URL Schemes
But add a percent escaped encoding of the game invite URL as a parameter to this URL thusly (I'm assuming the registration of a url e.g. newGameRequest but it will be best to make this quite unique, or even better - though it requires more setup, try Universal Link Support as this will allow you to direct users who don't have your app installed to a webpage with a download link)
let openOverWordForPlayerChallenge = "newGameRequest://?token="
gameState.gameSession?.getShareURL { (url, error) in
guard error == nil else { return }
// No opponent so we need to issue an invite
let encodedChallengeURL = url!.absoluteString.addingPercentEncoding(withAllowedCharacters:.urlHostAllowed)
let nestedURLString = openOverWordForPlayerChallenge + encodedChallengeURL!
let nestedURL = URL(string: nestedURLString)!
}
send the URL in a message or email or WhatsApp or whatever. Then in your app delegate, add the following:
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
var success = false
if let queryString = url.query {
if let urlStringToken = queryString.removingPercentEncoding {
let token = "token="
let startIndex = urlStringToken.startIndex
let stringRange = startIndex..<urlStringToken.index(startIndex, offsetBy: token.characters.count)
let urlString = urlStringToken.replacingOccurrences(of: token, with: "", options: .literal, range: stringRange)
if let url = URL(string: urlString) {
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
success = true
}
}
}
}
return success
}
Now you can be sure the session:didAddPlayer: will be called. What's the betting this workarround is good for about 2 weeks, and they fix this in the next release of iOS showcased at WWDC 2017 ! Update: this problem hasn't been fixed - so the workaround above remains good!
I agree, the lack of documentation is frustrating. From what I can see, we have to:
add <GKGameSessionEventListener> protocol in the class' header
Then session:didAddPlayer: fires on the joining player's device after accepting an invite link.
update:
Unfortunately, I'm not surprised to hear your results. I hadn't tried all of those scenarios, but GKTurnBasedMatch had similar shortcomings. The way I got around it there was: I added a list of player statuses to match data (invited, active, quit, etc). I gave the player a view of "pending invitations." When they opened that view, I would load all of their matches and display the entries where the player was in invited state. With GKGameSession, that should work too.
Or, it might be easier if you could maintain a local list of sessions that you are aware of. Whenever the game becomes active, pull the entire list of sessions from the server and look for a new entry. The new entry would have to be the match the player just accepted by clicking the share URL.

How to send parameters with AVPlayer request in iOS swift?

I have a POST request where I have to post parameters (track_id , track_name) to url to get a mp3 file content. Now How can i get this mp3 file streaming in AVPlayer?
I know how to stream in AVplayer with an url like :
var player: AVPlayer?
player = AVPlayer(URL: trackURL)
player?.play()
But here my URL won't have any content unless I provide its required parameters (track_id , track_name). How can i set the URL with these 2 parameters?
You need to have the url path to the mp3 resource at the time of initialization. Make the POST request first and then initialize the player with the URL that you get in response.

Follow playlists using Spotify iOS SDK

My requirement is to add a track to a particular playlist using the Spotify iOS SDK, and to make sure the user is following that playlist. In general adding a track is fine unless the user has deleted the playlist in Spotify (which actually just unfollows it), in this case the track is added, but the user cannot see the playlist anymore.
If the user isn't following the playlist, can we follow playlists using the SDK?
Thanks
Jules
Yeah just use SPTFollow (API documentation). Here's an example of a request in Swift 3:
var req = SPTFollow.createRequestFor(followingPlaylist: URL(string: "spotify:user:USERID:playlist:PLAYLISTID"), withAccessToken: (SPTAuth.defaultInstance().session.accessToken)!, secret: false)
SPTRequest.sharedHandler().perform(req) { (error, response, data) in
print("error=\(error), response=\(response), data=\(data)"
var results = SPTFollow.followingResult(from: data!, with: response!)
print("is following? \(results[0])")
}

mp4 video on iOS app using Swift

I've been looking at other posted questions regarding playing videos from websites and none of the solutions have worked for me. I get a black screen when I test my app, and the video doesn't seem to ever load.
Globally, I have declared:
var moviePlayer:MPMoviePlayerController!
And within my ViewDidLoad() function, I have:
let url:NSURL = NSURL(string:"http://uapi-f1.picovico.com/v2.1/v/nMirP/ArwenUndomiel.mp4")!
self.moviePlayer = MPMoviePlayerController(contentURL: url)
if let player = self.moviePlayer {
player.view.frame = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: self.view.frame.size.height)
player.view.sizeToFit()
player.scalingMode = MPMovieScalingMode.None
player.movieSourceType = MPMovieSourceType.Streaming
self.view.addSubview(player.view)
player.play()
}
Thanks for the help!
The URL that Picovico have given you actually responds with a message telling the requester that the content is not at this location but that they should try a different location - this is a common technique for URL redirecting.
You can see this if you try the link in a browser and capture the network request and responses. For example using the link you include above in Chrome gives this response:
You can see that the server is telling the client (browser in this case) to redirect (i.e. send a new request) to an S3 amazon URL which is where your video actually has been stored by Picovico.
When the browser then sends it's request to that location it finds the video successfully:
Looking at your video using ffprobe the format seems fine so it seems likely that the iOS client is having a hard time handling the redirect, or else that there was an issues with the URL redirection temporarily on Picovico's servers. If you still have the problem now then the former is most likely the problem.
If you take a look here you can see how someone has solved the problem of iOS media player handling redirects:
iOS Mediaplayer and URL redirection

Resources