Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AVPlayer stuck video, but audio works

I go to create AVPlayerItem through AVURLAsset, my code:

let asset = AVURLAsset(URL: safeURL, options: [AVURLAssetPreferPreciseDurationAndTimingKey: true])
    asset.loadValuesAsynchronouslyForKeys([assetKeyPlayable, self.assetKeyTracks, self.assetKeyHasProtectedContent]) {
        () -> Void in
        dispatch_async(dispatch_get_main_queue(), {
            () -> Void in
            // Use the AVAsset playable property to detect whether the asset can be played
            if !asset.playable {
                let localizedDescription = "Item cannot be played,Item cannot be played description"
                let localizedFailureReason = "The assets tracks were loaded, but could not be made playable,Item cannot be played failure reason"
                let userInfo = [NSLocalizedDescriptionKey: localizedDescription, NSLocalizedFailureReasonErrorKey: localizedFailureReason]
                let error = NSError(domain: "domain", code: 0, userInfo: userInfo)
                self.videoPlayerDelegate?.videoPlayer?(self, playerItemStatusDidFail: error)
                self.cleanPlayer()
                return
            }
            // At this point we're ready to set up for playback of the asset. Stop observing
            if let _ = self.player?.currentItem {
                self.cleanPlayer()
            }
            if asset.URL.absoluteString != safeURL.absoluteString {
                return
            }
            var error: NSError?
            let status = asset.statusOfValueForKey(self.assetKeyTracks, error: &error)
            var playerItem = AVPlayerItem(URL: safeURL)
            if status == .Loaded {
                playerItem = AVPlayerItem(asset: asset)
            } else {
                // You should deal with the error appropriately.If Loaded fails, create an AVPlayerItem directly from the URL
                playerItem = AVPlayerItem(URL: safeURL)
            }
            self.player = self.playerWithPlayerItem(playerItem)
            self.registerMonitoring()
            self.registerNotification()
            self.addTimeObserver()
            completionBlock?(loadURLString: playerURL.absoluteString)
        })
    }

Add AVPlayerLayer display video in my View, my code:

// MARK: - Property

var player: AVPlayer? {
    get {
        return playerLayer.player
    }

    set {
        playerLayer.player = newValue
    }
}

var playerLayer: AVPlayerLayer {
    return layer as! AVPlayerLayer
}

When displaying video after completion of loading

self.videoPlayer?.loadPlayer({
        [weak self](loadURLString) in
        if let strongSelf = self {
            strongSelf.player = strongSelf.videoPlayer?.player
            strongSelf.startPlay()
        }
    })

Call seekToTime method to specify the play:

self.player?.currentItem?.seekToTime(CMTimeMakeWithSeconds(time, Int32(NSEC_PER_SEC)), toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero) {
        [weak self] finished in
        if let weakSelf = self {
            if weakSelf.isPlaying {
               weakSelf.videoPlayerDelegate?.videoPlayerDidplayerItemSeekToTime?(weakSelf)
            }
        }
    }

Some pictures of the stuck interface:

enter image description here

In the first picture the sound is audible, but the interface is stuck.

enter image description here

In the second picture, the video works, but I get no sound.

My question:

When I call the seekToTime method upon completion, sometimes the video has sound, but the interface is stuck, occasionally the video works. I tried to call the CALayer setNeedsDisplay methods, to update the AVPlayerLayer picture, but that didn't help. I don't know what to do anymore, i would be grateful for every and any help.

like image 989
Shannon Yang Avatar asked Apr 12 '16 10:04

Shannon Yang


2 Answers

Since this is happening for many of us in a different way and is not answered, I am answering it here, For me this was happening when I was trying to play the video in UIView using AVPlayer and AVPlayerLayer but when I used the same AVPlayer to load the video in AVPlayerViewController the video was getting stuck and audio kept on playing.

The trick is to destroy the AVPlayerLayer before using the same AVPlayer somewhere else ,Cant believe I found this enlightenment over here.

https://twitter.com/skumancer/status/294605708073263104

How I implemented this?

I created 2 view controllers in my main story board.

1) View Controller Scene i) This has a UIView in it referenced to IBOutlet 'videoView' ii) A button with segue navigation to 'show' AV Player View Controller Scene referenced to IBOutlet 'fullscreen' 2) AV Player View Controller Scene

Story board for the video player.

// Code in your controller class.


// Make sure to reference your IBOutlet in your StoryBoard.
@IBOutlet weak var videoView : UIView!
@IBOutlet weak var fullscreen : UIButton!

var player:AVPlayer? = nil;
var playerLayer:AVPlayerLayer? = nil;

override func viewDidLoad() {
    super.viewDidLoad()

    // Your Url to play, could be hls type video
    let url = URL(string: "https://your_url.m3u8");

    let asset = AVAsset(url : url!)

    let playerItem = AVPlayerItem(asset: asset)

    player = AVPlayer(playerItem: playerItem)
}


override func viewDidAppear(_ animated: Bool) {
    playInUIView()
    pauseVideo()
}

// Creates An AVPlayerLayer and adds it as a sublayer in UIView.
func playInUIView(){
    // Creating player layer to render video.
    playerLayer = AVPlayerLayer(player: player)

    playerLayer?.frame = self.videoView.bounds

    playerLayer?.videoGravity = .resizeAspect

    // Adding a sub layer to display it in UIView
    self.videoView.layer.addSublayer(playerLayer!)
}

// Destroyes an AVPlayerLayer and Sublayer of your UIView
func destroyPlayInView(){
    self.videoView.layer.sublayers=nil
    playerLayer = nil
}
    
// On button click open AVPlayerViewController.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    let destination = segue.destination as! AVPlayerViewController

    // Destroy AVPlayerLayer before rendering video in AVPlayerViewController.
    destroyPlayInView()

    // Set same AVPlayer in AVPlayerViewController.
    destination.player = self.player

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

PS - I have not implemented any controls as of yet and there could be coding malpractices as I have just started with swift and IOS development couple of days back, so please let me know wherever I am wrong.

like image 111
Vaibhav Singh Avatar answered Oct 03 '22 08:10

Vaibhav Singh


Try this approach as I have encountered the same issue and solve it by this kind approach.

player.pause()
player.currentItem?.seek(to: CMTime(), completionHandler: { (_) in
    player.play()
})
like image 42
NFerocious Avatar answered Oct 03 '22 06:10

NFerocious