Search code examples
iosswiftavfoundationavplayeravkit

AVPlayer video disappears when navigating 'back' in Swift


When navigating to a subview, I have set it up so that a video plays automatically. At the bottom of the video, there is a group of links that go to related content. When clicking one of them, a new view is pushed onto the stack and a different video starts playing.

The problem happens when using the automatically generated '< Back' button to go back to the prior view (which had a different video). This original view can be operated using the player controls, but nothing shows up on the screen.

I've tried to update the CGRect frame, use onAppear to reinitialize the video player, and also followed the advice here.

So far nothing seems to work. Here is the code I am using for the actual video player (adapted from Chris Mash's website):

import SwiftUI
import AVKit
import UIKit
import AVFoundation

let playerLayer = AVPlayerLayer()
class PlayVideo: UIView {

    init(frame: CGRect, url: URL) {
        super.init(frame: frame)
        
        // Create the video player using the URL passed in.
        let player = AVPlayer(url: url)
        player.volume = 100 // Will play audio if you don't set to zero
        player.play() // Set to play once created
        
        // Add the player to our Player Layer
        playerLayer.player = player
        playerLayer.videoGravity = .resizeAspectFill // Resizes content to fill whole video layer.
        playerLayer.backgroundColor = UIColor.black.cgColor
        
        layer.addSublayer(playerLayer)
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        playerLayer.frame = bounds
    }
   
    static func pauseVideo() {
        playerLayer.player?.pause()
    }
}


struct ViewVideo: UIViewRepresentable {
    
    var videoURL:URL
    var previewLength:Double?
    
    func makeUIView(context: Context) -> UIView {
        return PlayVideo(frame: .zero, url: videoURL)
    }
    
    func updateUIView(_ uiView: UIView, context: Context) {
        
    }
}

This is called from the main view using: ViewVideo(videoURL: videoURL)

The only work around I can think of is to disable the back button and force the user to go back to the main view every time. That's a terrible option and I'm hoping someone will have some helpful advice here. Thanks -


Solution

  • Thank you to @dominik-105 for helping with this question. I was able to fix the problem using the suggestions that were made.

    Specifically, I removed the global definition of playerLayer and instead placed it as a local variable in my main view call:

    var playerLayer = AVPlayerLayer()
    

    I then call ViewVideo with the playerLayer: playerLayer tag, and the ViewVideo then calls PlayVideo with playerLayer:AVPlayerLayer as part of the init.

    Interestingly, this leads to problems with the override layout function I was using to define the size of the video box. I define the frame directly in the init now and removed the old override function code. The full code is now:

    import SwiftUI
    import AVKit
    import UIKit
    import AVFoundation
    
    
    class PlayVideo: UIView {
        init(frame: CGRect, url: URL, playerLayer: AVPlayerLayer, width: CGFloat, height: CGFloat) {
            super.init(frame: frame)
            
            // Create the video player using the URL passed in.
            let player = AVPlayer(url: url)
            player.volume = 100 // Will play audio if you don't set to zero
            player.play() // Set to play once created
            
            // Add the player to our Player Layer
            playerLayer.player = player
            playerLayer.videoGravity = .resizeAspectFill // Resizes content to fill whole video layer.
            playerLayer.backgroundColor = UIColor.black.cgColor
    
            playerLayer.player?.actionAtItemEnd = .pause
            layer.addSublayer(playerLayer)
            playerLayer.frame = CGRect(x: 0, y: 0, width: width, height: height)
        }
        
        required init?(coder: NSCoder) {
            super.init(coder: coder)
        }
    
    }
    
    
    struct ViewVideo: UIViewRepresentable {
        var videoURL:URL
        var playerLayer: AVPlayerLayer
        var width: CGFloat
        var height: CGFloat
        
        func makeUIView(context: Context) -> UIView {
            return PlayVideo(frame: .zero, url: videoURL, playerLayer: playerLayer, width: width, height: height)
        }
        
        func updateUIView(_ uiView: UIView, context: Context) {
            
        }
    }
    

    I use GeometryReader to define the size of the box, and then pass along the width and height to the struct, which passes it along to the class.