Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Video playback issues only on iOS 13 with AVPlayerViewController and AVPlayer when using HLS video

I have an app that plays back videos. It's compatible with iOS 11, 12 and iOS 13. On iOS 11 and 12, video playback works properly as expected using either AVPlayerViewController or even just AVPlayerLayer.

However, on iOS 13 I started getting reports that suddenly video wasn't loading (or would only load audio, or only the first frame) for quite a few users when they updated iOS. I had a really hard time replicating it, but some mentioned it occurred mostly with poor network connections, and sure enough with Network Link Conditioner I was able to reproduce it.

It specifically affects HLS video (the fancy livestream compatible one that Reddit uses, for instance). It continues to work fine with MP4. Here's an example URL that fails: https://v.redd.it/gl3chx2kd4v31/HLSPlaylist.m3u8

Here's a Network Link Conditioner profile that triggers it: https://i.imgur.com/XWsKUeM.jpg

Here's a sample project that triggers it, showing both AVPlayerViewController and AVPlayer (hit Download, Google is being weird): https://drive.google.com/file/d/1RS5DvUypdOLFCYJe1Pt2Ig0fQljZDLs2/view

Here's sample code showing it off with AVPlayerViewController:

let assetURL = URL(string: "https://v.redd.it/gl3chx2kd4v31/HLSPlaylist.m3u8")!

// The following MP4 URL *does* work, for instance
// let assetURL = URL(string: "https://giant.gfycat.com/DependentFreshKissingbug.mp4")!

let player = AVPlayer(url: assetURL)
let playerViewController = AVPlayerViewController()
playerViewController.player = player

self.present(playerViewController, animated: true) {
    playerViewController.player!.play()
}

If I try that exact same code on an iOS 12 device instead it works perfectly.

Does anyone have any suggestions on how to fix it? If you scrub back to the beginning sometimes you can get the video to play back properly, but not reliably enough so to seemingly build a solution off that. Video stuff definitely isn't my forte in iOS development so I'm starting to scratch my head a little, any help would be super appreciated.

Note: I'm quite aware this is (likely) an iOS bug and I will file a Radar, but I still have to deal with it now.

like image 827
christianselig Avatar asked Oct 28 '19 02:10

christianselig


1 Answers

You need to kvo on the playerItems status in order to know when it is ready to play. Then in the playerItem status change to ready to play call your player.play.

playerItem.addObserver(self,
                           forKeyPath: #keyPath(AVPlayerItem.status),
                           options: [.old, .new],
                           context: &playerItemContext)


override func observeValue(forKeyPath keyPath: String?,
                           of object: Any?,
                           change: [NSKeyValueChangeKey : Any]?,
                           context: UnsafeMutableRawPointer?) {

    // Only handle observations for the playerItemContext
    guard context == &playerItemContext else {
        super.observeValue(forKeyPath: keyPath,
                           of: object,
                           change: change,
                           context: context)
        return
    }

    if keyPath == #keyPath(AVPlayerItem.status) {
        let status: AVPlayerItemStatus
        if let statusNumber = change?[.newKey] as? NSNumber {
            status = AVPlayerItemStatus(rawValue: statusNumber.intValue)!
        } else {
            status = .unknown
        }

        // Switch over status value
        switch status {
        case .readyToPlay:
            // Player item is ready to play.
        case .failed:
            // Player item failed. See error.
        case .unknown:
            // Player item is not yet ready.
        }
    }
}


like image 90
Lunarchaos42 Avatar answered Sep 19 '22 21:09

Lunarchaos42