Using pinned certificates with AVPlayer in iOS - ios

Is there a way to get AVPlayer to enforce certificate pinning in iOS?
Our video loading code is basically:
let url = URL(string: "https://www.example.com/file.mp4")!
let item = AVPlayerItem(url: url)
We're able to do certificate pinning with URLSession using this delegate method: https://developer.apple.com/documentation/foundation/urlsessiondelegate/1409308-urlsession. But I haven't been able to figure out an analogous approach for AVPlayer, if there is one.
Thanks for your help!

The relevant method to implement should be this one in AVAssetResourceLoaderDelegate (docs):
optional func resourceLoader(_ resourceLoader: AVAssetResourceLoader,
shouldWaitForResponseTo authenticationChallenge: URLAuthenticationChallenge) -> Bool
In your case, create an AVURLAsset directly, and set your delegate implementation on its resource loader. Then initialize the player item with the asset.
let url = URL(string: "https://www.example.com/file.mp4")!
let urlAsset = AVURLAsset(url: url)
urlAsset.resourceLoader.setDelegate(resourceLoaderDelegate,
queue: .main)
let item = AVPlayerItem(asset: urlAsset)

Related

Swift AVPlayer continuously loads when trying to play url video file

I am attempting to play a video file from a youtube url. I have created the AVPlayer and linked the url to the player. The player opens when I click the button, but the video player just shows the file continuously loading. I have changed the App Transport Security Settings -> Allow Arbitrary Loads on the plist.
Heres the code:
#IBAction func playVideo(_ sender: Any) {
let movieId = "xLCn88bfW1o"
let path = URL(string: "https://www.youtube.com/watch?v=\(movieId)")
let video = AVPlayer(url: path!)
let videoPlayer = AVPlayerViewController()
videoPlayer.player = video
present(videoPlayer, animated: true, completion: {
video.play()
})
}
EDIT: I've also tried by using the URL for sharing the video; https://youtu.be/xLCn88bfW1o.
The url you are using is not the url to the video, it is the url to a webpage which has the video embedded. There are tools out there that can help you get the actual url of the video file.
For example: YoutubeDirectLinkExtractor
YoutubeDirectLinkExtractor allows you to obtain the direct link to a YouTube video, which you can easily use with AVPlayer. It uses type safety and optionals to guarantee that you won't crash while extracting the link no matter what. There are popular alternatives, which use more straightforward and risky approach, though:
Use extracted video link with AVPlayer:
let y = YoutubeDirectLinkExtractor()
y.extractInfo(for: .urlString("https://www.youtube.com/watch?v=HsQvAnCGxzY"), success: { info in
let player = AVPlayer(url: URL(string: info.highestQualityPlayableLink!)!)
let playerViewController = AVPlayerViewController()
playerViewController.player = player
self.present(playerViewController, animated: true) {
playerViewController.player!.play()
}
}) { error in
print(error)
}
You need to pass the video file's direct URL to AVPlayer (not a Youtube website URL)
For example:
let path = URL(string: "https://www.sample-videos.com/video/mp4/720/big_buck_bunny_720p_1mb.mp4")

Swift 4 - Trying to display AWS video url from backend in AVPlayerViewController not working, get weird error

I have a tableview which sets a UIImage to hold either an image url from AWS or a thumbnail generated from a video URL also from AWS. The video url refuses to display in my tableview and it throws this error in the debugger.
2017-12-29 12:20:37.053337-0800 VideoFit[3541:1366125] CredStore - performQuery - Error copying matching creds. Error=-25300, query={
class = inet;
"m_Limit" = "m_LimitAll";
"r_Attributes" = 1;
sync = syna;
}
When I click the cell to display either the image or video url, it segues to the video player correctly and then I get the error again and the triangular start button has a line through it signifying that there is no video to be played.
But when I print the url it has successfully passed so that is not the issue, the issue is AVPlayer can't handle my AWS video url for some reason. It is an https link so that means it must be secure but I already set my arbitrary loads to true
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
Here is some code for displaying my videos in the videoPlayer VC and also the thumbnail generator function, perhaps there is some issue lying with these?
import UIKit
import AVKit
import MediaPlayer
class VideoPlayerVC: AVPlayerViewController {
var urlToPlay: String?
override func viewDidLoad() {
super.viewDidLoad()
print("Here is the url ---> \(String(describing: urlToPlay))")
playVideo()
}
private func playVideo() {
guard let urlFromString = urlToPlay else { print("No url to play") ;return }
let url = URL(string: urlFromString)
print("Here is the url to play ---> \(String(describing: url))")
let asset: AVURLAsset = AVURLAsset(url: url!)
let item: AVPlayerItem = AVPlayerItem(asset: asset)
let player: AVPlayer = AVPlayer(playerItem: item)
self.player = player
self.showsPlaybackControls = true
self.player?.play()
}
}
This is how I make the thumbnail, when my cellforRow atIndexPath method runs it throws this error for every video object in the tableview.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "sortedExerciseCell") as? SortedExerciseCell! {
// Do if check for videoURI and imageURI
if selectedExerciseArray[indexPath.row].imageURI != nil {
if let imageURI = URL(string: selectedExerciseArray[indexPath.row].imageURI!) {
print("It's a photo!")
// Using DispatchQueue.global(qos: .background).async loads cells in background
DispatchQueue.global(qos: .background).async {
let data = try? Data(contentsOf: imageURI)
DispatchQueue.main.async {
cell.exerciseImg.image = UIImage(data: data!)
}
}
}
} else {
if let videoURI = URL(string: selectedExerciseArray[indexPath.row].videoURI!) {
print("It's a video!")
print(videoURI)
DispatchQueue.global(qos: .background).async {
DispatchQueue.main.async {
cell.exerciseImg.image = self.thumbnailForVideoAtURL(url: videoURI)
// for every video object the above error is thrown!!!
}
}
}
}
cell.exerciseName.text = selectedExerciseArray[indexPath.row].name
return cell
} else {
return UITableViewCell()
}
}
// Used to just display the video thumbnail in cell, when clicked on will display video as needed
private func thumbnailForVideoAtURL(url: URL) -> UIImage? {
let asset = AVAsset(url: url)
let assetImageGenerator = AVAssetImageGenerator(asset: asset)
do {
print("Doing the video thumbnail from backend")
let imageRef = try assetImageGenerator.copyCGImage(at: CMTimeMake(1, 60) , actualTime: nil)
return UIImage(cgImage: imageRef)
} catch {
print("This is failing for some reason")
print("error")
return nil
}
}
I've looked at similar questions on stack overflow about this but none seem to give a complete answer on how to solve this problem, most chalking it up to an iOS 11 bug that can't be fixed or transport security (Already have arbitrary loads on so this can't be the issue..) anyone else have any approaches that might work?
Important note - My backend developer can only view the video url's from a webpage, in other words he must make a basic website to display the video after downloading it from the web. I'm not sure if this is standard procedure for handling video url's from AWS but I decided to try loading the url with "loadHTMLString" in a custom UIViewController conforming to WKUIDelegate, I see the video but the same situation happens where the triangular start button is crossed out signifying no video can be played. I'm not really sure what else I can try at this moment, any help is appreciated.
Here is one of the links I've pulled from my backend.
https://videofitapptestbucket.s3.us-west-2.amazonaws.com/100001427750
It seems that your problem is in file extension. DMS is not recognized by OS, it throws when creating image with assetgenerator.
Error Domain=AVFoundationErrorDomain Code=-11828 "Cannot Open" UserInfo={NSLocalizedFailureReason=This media format is not supported., NSLocalizedDescription=Cannot Open, NSUnderlyingError=0x6040000534d0 {Error Domain=NSOSStatusErrorDomain Code=-12847 "(null)"}}
Rename your files on server. The mp4 extension seems to work just fine with your file.
Also, subclassing AVPlayerViewController is generally not that great idea as Apple says that it will result in undefined behavior. (read: random mess). I would suggest to use class that have AVPlayer inside.
Try:
if let path = urlToPlay {
let url = URL(string: path)!
let videoPlayer = AVPlayer(url: url)
self.player = videoPlayer
DispatchQueue.main.async {
self.player?.play()
}
}
Call it in viewDidAppear() method

Video playback in Swift from URL

I am currently trying to play a video on iOS from a URL in Swift. URLs with endings like ".mp4" or ".m4v" work, but when I try to play a shared video link from Dropbox it doesn't.
I already changed the ending of the Dropbox link to "dl=1" but anyway nothing happens.
Playing a Youtube-video in a webview is not a solution for me, because I want the native iOS player.
Does anyone know what to do to play URLs from Dropbox or maybe recommend cloud services where this does work?
Thank you!
I didn't know dropbox streaming spec.
But if dropbox url doesn't have hint for playing like mp4,
you can use AVAssetLoaderDelegate like proxy server.
https://developer.apple.com/documentation/avfoundation/avassetresourceloaderdelegate
AVAssetLoaderDelegate excute when url has custom scheme.
If you use "foo" scheme, 'shouldWaitForLoadingOfRequestedResource' will be called. And you should make another request for dropbox with same http header.
Try like this
func play() {
let url = URL(string: "foo://bar.mp4")!
let asset = AVURLAsset(url: url)
let playerItem = AVPlayerItem(asset: asset)
asset.resourceLoader.setDelegate(self, queue: nil)
player.replaceCurrentItem(with: playerItem)
player.play()
}
func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {
var newRequest = URLRequest(url: URL(string: "dropbox url")!)
newRequest.allHTTPHeaderFields = loadingRequest.request.allHTTPHeaderFields
let sessionTask = URLSession.shared.dataTask(with: newRequest) { data, response, error in
if let responseData = data {
loadingRequest.dataRequest?.respond(with: responseData)
}
loadingRequest.finishLoading() // Let player know that finish loding
}
sessionTask.resume()
return true // Wait for sessionTask response.
}

Unable to play .mp4 video file using AVPlayer

I am using the AVPlayer to play the video from url.
func playVideoFromUrl(urlString: String){
let videoURL = URL(string: urlString)
let player = AVPlayer(url: videoURL!)
let playerViewController = AVPlayerViewController()
playerViewController.player = player
let delegate : AppDelegate = UIApplication.shared.delegate as! AppDelegate
delegate.window?.rootViewController?.present(playerViewController, animated: true) {
playerViewController.player!.play()
}
}
The url of video:- "http://gsn-input-dev.s3.amazonaws.com/public/video/00229/9229d4ad2a0c547e7bfa51e3fbef806f.mp4"
I am not getting any warning at console. Below is the screenshot of simulator. What I am doing wrong?
I have accomplished what you've wanted to do. Here is my approach:
First import AVFoundation, AVKit(I believe you had) then I have configured my viewcontroller like this
#IBOutlet weak var avPlayerView: UIView! // drag a UIView to view controller
var videoPlayer = AVPlayer()
var avPlayerViewController = AVPlayerViewController()
override func viewDidLoad() {
super.viewDidLoad()
let videoURL = URL(string: "http://gsn-input-dev.s3.amazonaws.com/public/video/00229/9229d4ad2a0c547e7bfa51e3fbef806f.mp4")
self.playVideo(url: videoURL!)
}
playVideo function is configured like below
func playVideo(url: URL) {
self.videoPlayer = AVPlayer(url: url)
self.avPlayerViewController = AVPlayerViewController()
self.avPlayerViewController.player = self.videoPlayer
avPlayerViewController.view.frame = avPlayerView.frame
self.addChildViewController(avPlayerViewController)
self.view.addSubview((avPlayerViewController.view)!)
}
Open your info.plist as Source Code
then add this to info.plist
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
screen shot is given below
Have you tried adding arbitrary load flag in your info.plist file as i see the url is a HTTP url and to make the HTTP url work, we need to specify it in app transportation security via info.plist as follows
Please try doing this as video is working perfectly fine with you code.
Read following document for more info on App Transportation Security
I hope this will help you :)
I recently found out that a device running iOS 14 using AVPlayer cannot play .mp4 files. A compatible video format that works on AVPlayer is .mov.
I also have had a trouble playing .mp4 files. When I made the URL end with .mp4 it works.

How to stream a video with AVURLAsset and save to disk the cached data

Some days ago I was asked to check how difficult is to play a video while downloading it from Internet. I know it's an easy task because someone told me a while ago. So, I checked and it was super easy.
The problem was that I wanted to save to disk the video to do not force the user to download it again and again.
The problem was to access the buffer and store it to disk.
Many answers in Stackoverflow says it is nor possible. Specially with videos.
My original code to play the video:
import AVFoundation
....
//MARK: - Accessors
lazy var player: AVPlayer = {
var player: AVPlayer = AVPlayer(playerItem: self.playerItem)
player.actionAtItemEnd = AVPlayerActionAtItemEnd.None
return player
}()
lazy var playerItem: AVPlayerItem = {
var playerItem: AVPlayerItem = AVPlayerItem(asset: self.asset)
return playerItem
}()
lazy var asset: AVURLAsset = {
var asset: AVURLAsset = AVURLAsset(URL: self.url)
return asset
}()
lazy var playerLayer: AVPlayerLayer = {
var playerLayer: AVPlayerLayer = AVPlayerLayer(player: self.player)
playerLayer.frame = UIScreen.mainScreen().bounds
playerLayer.backgroundColor = UIColor.clearColor().CGColor
return playerLayer
}()
var url: NSURL = {
var url = NSURL(string: "https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4")
return url!
}()
//MARK: - ViewLifeCycle
override func viewDidLoad() {
super.viewDidLoad()
view.layer.addSublayer(playerLayer)
player.play()
}
The solution for this problem is to use AVAssetExportSession and AVAssetResourceLoaderDelegate:
First step is to add a notification to know when the video finish. Then we can start saving it to disk.
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(playerItemDidReachEnd(_:)), name: AVPlayerItemDidPlayToEndTimeNotification, object: nil)
...
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
The implementation of our function:
func playerItemDidReachEnd(notification: NSNotification) {
if notification.object as? AVPlayerItem == player.currentItem {
let exporter = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality)
let filename = "filename.mp4"
let documentsDirectory = NSFileManager.defaultManager().URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask).last!
let outputURL = documentsDirectory.URLByAppendingPathComponent(filename)
exporter?.outputURL = outputURL
exporter?.outputFileType = AVFileTypeMPEG4
exporter?.exportAsynchronouslyWithCompletionHandler({
print(exporter?.status.rawValue)
print(exporter?.error)
})
}
}
Finally we need to make our AVURLAsset delegate of AVAssetResourceLoaderDelegate:
lazy var asset: AVURLAsset = {
var asset: AVURLAsset = AVURLAsset(URL: self.url)
asset.resourceLoader.setDelegate(self, queue: dispatch_get_main_queue())
return asset
}()
And:
extension ViewController : AVAssetResourceLoaderDelegate {
}
I created a small demo with this code in GitHub.
The team at Calm has open-sourced our implementation to this. It's available as a CocoaPod. It's called PersistentStreamPlayer.
Features include:
streaming of audio file, starting playback as soon as first data is available
also saves streamed data to a file URL as soon as the buffer completes
exposes timeBuffered, helpful for displaying buffer progress bars in the UI
handles re-starting the audio file after the buffer stream stalls (e.g. slow network)
simple play, pause and destroy methods (destroy clears all memory resources)
does not keep audio file data in memory, so that it supports large files that don't fit in RAM
You can find it here: https://github.com/calmcom/PersistentStreamPlayer

Resources