Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Change AVPlayer source in SwiftUI?

Tags:

swiftui

I have an AVPlayer that plays a video in the background of my SwiftUI app which works fine.

But I need to allow the users to change the video on a button tap/click.

This is my code for playing video:

var player = AVPlayer()
var bgVideoURL = "https://www.w3schools.com/html/movie.mp4"


struct PlayerView: UIViewRepresentable {
    func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<PlayerView>) {
    }

    func makeUIView(context: Context) -> UIView {
        return PlayerUIView(frame: .zero)
    }
}

class PlayerUIView: UIView {

    private let playerLayer = AVPlayerLayer()

    override init(frame: CGRect) {
        super.init(frame: frame)

        let url = URL(string: bgVideoURL)!
        player = AVPlayer(url: url)
        player.actionAtItemEnd = .none
        
        player.play()

        playerLayer.player = player
        playerLayer.videoGravity = .resizeAspectFill

        NotificationCenter.default.addObserver(self,
                                               selector: #selector(playerItemDidReachEnd(notification:)),
                                               name: .AVPlayerItemDidPlayToEndTime,
                                               object: player.currentItem)

        layer.addSublayer(playerLayer)
    }
    
    

    @objc func playerItemDidReachEnd(notification: Notification) {
        if let playerItem = notification.object as? AVPlayerItem {
            playerItem.seek(to: .zero, completionHandler: nil)
        }
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        playerLayer.frame = bounds
    }
}

and this is how I play the video:

var body: some View {
    PlayerView()
    .edgesIgnoringSafeArea(.all)
}

I need to change the video on button click/tap so I tried this:

 .onTapGesture {

   player.pause()
   player.seek(to: .zero)

   bgVideoURL = "https://media.w3.org/2010/05/sintel/trailer.mp4"

   player.play()
                                        
   }

The above code will restart the player but it doesn't change the video/source of the player!

Is there something else I need to do?

like image 798
drago Avatar asked Oct 27 '25 06:10

drago


1 Answers

First, create a proper PlayerUIView class (Remove global variables, etc.)

PlayerUIView

class PlayerUIView: UIView {
    
    // MARK: Class Property
    
    let playerLayer = AVPlayerLayer()
    
    // MARK: Init
    
    override init(frame: CGRect) {
        super.init(frame: frame)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    init(player: AVPlayer) {
        super.init(frame: .zero)
        self.playerSetup(player: player)
    }
    
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
    
    // MARK: Life-Cycle
    
    override func layoutSubviews() {
        super.layoutSubviews()
        playerLayer.frame = bounds
    }
    
    // MARK: Class Methods
    
    private func playerSetup(player: AVPlayer) {
        playerLayer.player = player
        player.actionAtItemEnd = .none
        layer.addSublayer(playerLayer)
        
        self.setObserver()
    }
    
    func setObserver() {
        NotificationCenter.default.removeObserver(self)
        NotificationCenter.default.addObserver(self, selector: #selector(playerItemDidReachEnd(notification:)),
                                               name: .AVPlayerItemDidPlayToEndTime,
                                               object: playerLayer.player?.currentItem)
    }
    
    @objc func playerItemDidReachEnd(notification: Notification) {
        if let playerItem = notification.object as? AVPlayerItem {
            playerItem.seek(to: .zero, completionHandler: nil)
            self.playerLayer.player?.play()
        }
    }
}

Now, use @Binding to bind your player in PlayerView

PlayerView

struct PlayerView: UIViewRepresentable {
    
    @Binding var player: AVPlayer
    
    func makeUIView(context: Context) -> PlayerUIView {
        return PlayerUIView(player: player)
    }
    
    func updateUIView(_ uiView: PlayerUIView, context: UIViewRepresentableContext<PlayerView>) {
        uiView.playerLayer.player = player
        
        //Add player observer.
        uiView.setObserver()
    }
}

in last, inside the content view make AVPlayer object, change your new URL with AVPlayer.

struct PlayerContentView: View {
    
    @State private var player = AVPlayer(url: URL(string: "https://media.w3.org/2010/05/sintel/trailer.mp4")!)
    
    var body: some View {
        PlayerView(player: $player)
            .onTapGesture {
                player.pause()
                player.seek(to: .zero)
                
                player = AVPlayer(url: Bundle.main.url(forResource: "temp_video", withExtension: "mp4")!) // or AVPlayer(url: URL(string: "https://media.w3.org/2010/05/sintel/trailer.mp4")!)
                player.play()
                
            }
            .onAppear {
                player.play()
            }
        .edgesIgnoringSafeArea(.all)
    }
}
like image 94
Raja Kishan Avatar answered Oct 29 '25 07:10

Raja Kishan