Video playback in Swift from URL - ios

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.
}

Related

Using pinned certificates with AVPlayer in 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)

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")

Unable to play video in avplayer with custom request

Hi am trying to send custom header with avplayer request so I had added a custom scheme like
let asset = AVURLAsset(url: URL(string: "mycustomscheme://tungsten.aaplimg.com/VOD")!, options: nil)
because of this request fails and delegate method shouldWaitForLoadingOfRequestedResource is called.
In shouldWaitForLoadingOfRequestedResource am making a custom request and am recieving the data from server but avplayer is still not playing the file. Here is my code :
func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {
resourceLoader.delegateQueue?.async {
var request: URLRequest? = loadingRequest.request
request?.url = self.url
// Add header
request?.setValue(HeadersForWS.DeviceOS, forHTTPHeaderField: HeadersForWS.DeviceType)
request?.setValue(Utility.getUserId(), forHTTPHeaderField:HeadersForWS.UserId )
if let aRequest = request {
let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
session.dataTask(with: aRequest as URLRequest) { (data, response, error) in
guard let data = data else {
try? loadingRequest.finishLoading()
return
}
loadingRequest.dataRequest?.respond(with: data)
loadingRequest.finishLoading()
}.resume()
}
}
return true
}
If I save the data received to a file sample.mp4 and pull the file from app and then try to play then it plays means am receiving the video data properly. What may be the problem here??
Thanks in advance

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

AVPlayer & AVAudioPlayer not playing audio from URL

I have trying to play audio from URL. I tried both AVPlayer and AVAudioPlayer but none of them worked.
Here is the URL which I am trying to play.
Here is the code I am using.
// AVPlayer Code
self.avPlayerItem = AVPlayerItem.init(url: urlOfAudio)
self.avPlayer = AVPlayer.init(playerItem: avPlayerItem)
self.avPlayer.volume = 1.0
self.avPlayer.play()
// AVAudioPlayer
if let url = URL.init(string: escapeCharactorString){
do {
self.playerReference = try AVAudioPlayer.init(contentsOf: url)
guard let player = playerReference else {
return
}
player.volume = 1.0
player.prepareToPlay()
player.numberOfLoops = -1
player.play()
} catch let error {
}
}
None of them worked for me.
Please let me know where I am going wrong in it.
There is nothing wrong with your code, it is perfect but the issue is something related to server settings that audio file stored. Here is your working code which successfully playing an other mp3 file,
var avPlayer:AVPlayer?
var avPlayerItem:AVPlayerItem?
override func viewDidLoad() {
super.viewDidLoad()
// let urlstring = "http://www.noiseaddicts.com/samples_1w72b820/2514.mp3"
let urlstring = "https://s3.amazonaws.com/kargopolov/kukushka.mp3"
let url = NSURL(string: urlstring)
print("playing \(String(describing: url))")
avPlayerItem = AVPlayerItem.init(url: url! as URL)
avPlayer = AVPlayer.init(playerItem: avPlayerItem)
avPlayer?.volume = 1.0
avPlayer?.play()
}
So please try the mp3 file with different host it will solve your problem
The cause is Application Transport Security. You need to add:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoadsForMedia</key>
<true/>
</dict>
to your application's .plist file to load media from HTTP schemes. HTTPS schemes need no such exception.

Resources