How to play video from ipfs url using avplayer in swift

Viewed 189

I have an ipfs encrypted video URL.
I am not able to play video in avplayer but URL plays well in webview.
here is URL: https://ipfs.moralis.io:2053/ipfs/QmUjWnQZVVNmTSVak2QDhTxMkn3dPQozxawa1sm3jE5bLr

let currentURL = URL(string: "https://ipfs.moralis.io:2053/ipfs/QmUjWnQZVVNmTSVak2QDhTxMkn3dPQozxawa1sm3jE5bLr")!
let playerItem = AVPlayerItem(url: currentURL)
playerItem.addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions(), context: nil)
player = AVPlayer(playerItem: playerItem)let playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = self.avplayerUIView.bounds
self.avplayerUIView.layer.addSublayer(playerLayer)
player.play()

Also, I have used AVUrlAssets with video extension and video codecs but still, video is not playing

let mimeType = "video/mp4; codecs=\"avc1.42E01E, mp4a.40.2\""
let asset: AVURLAsset = AVURLAsset(url: videoURL!, options: ["AVURLAssetOutOfBandMIMETypeKey" : mimeType])
2 Answers

It's a good practice to handle errors from your code to get info about the problem so lets make next error handler:

class ErrorObserver: NSObject {
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
        guard let player = object as? AVPlayer, let error = player.currentItem?.error, let keyPath = keyPath else {
            return
        }
        print("Key:", keyPath)
        print("Error:", error)
    }
}

let errorObserver = ErrorObserver()

...

// Add observer AVPlayerItem status
player.addObserver(errorObserver, forKeyPath: #keyPath(AVPlayer.currentItem.status), options:[.new], context: nil)

If you run your code it produces next error:

Key: currentItem.status
Error: Error Domain=AVFoundationErrorDomain Code=-11850 "Operation Stopped" UserInfo={NSLocalizedFailureReason=The server is not correctly configured., NSLocalizedDescription=Operation Stopped, NSUnderlyingError=0x600003bbc060 {Error Domain=NSOSStatusErrorDomain Code=-12939 "(null)"}}

"The server is not correctly configured." is often related to the HTTP range requests issue on a server side because supporting downloading by ranges is required to play media from the Internet.

Lets check this with curl:

$ curl -I https://ipfs.moralis.io:2053/ipfs/QmUjWnQZVVNmTSVak2QDhTxMkn3dPQozxawa1sm3jE5bLr
HTTP/2 200 
date: Fri, 05 Aug 2022 09:48:23 GMT
content-type: video/mp4
content-length: 4835525
cf-ray: 735e9de8d95c9290-FRA
accept-ranges: bytes
...

As you can see from the response the server supports ranges - "accept-ranges: bytes".

Lets check the partial request:

$ curl https://ipfs.moralis.io:2053/ipfs/QmUjWnQZVVNmTSVak2QDhTxMkn3dPQozxawa1sm3jE5bLr -i -H "Range: bytes=0-1023"
HTTP/2 200 
date: Fri, 05 Aug 2022 09:50:05 GMT
content-type: video/mp4
content-length: 4835525
cf-ray: 735ea066cfd61649-MUC
accept-ranges: bytes
...

The server responds with HTTP/2 200 and the full content length but the response must be like this:

HTTP/2 206 Partial Content
Accept-Ranges: bytes
Server: UploadServer
Aka-c-hit: cache-hit
Content-Range: bytes 0-1023/253755577
Content-Length: 1024
Connection: keep-alive
Content-Type: video/mp4
...

So that your server doesn't support HTTP ranges requests and AVPlayer can't play your mp4 video.

You can resolve the issue with two ways:

  1. Change the servers's settings to support HTTP range requests.
  2. Programmatically download video and save it to a local file and after you can play it from file URL.

Strange. It seems AVPlayerLayer can not resolve video. If you remove playerLayer, you will hear audio is playing fine.

My solution is using AVPlayerViewController.

let vc = AVPlayerViewController()
vc.view.frame = view.bounds
vc.player = player
player.play()
present(vc, animated: true) // or use as a child view controller by embedding in a container view 
Related