iosswiftuiavplayeravplayerviewcontrolleruiviewrepresentable

SwiftUI AVPlayer - How to pause and resume playing?


So I made a view called "VideoElement" that will play a video in the background when the parent view loads. It worked well. However, I cannot figure out how to implement a pause/resume system. I want to change a variable (var "paused") which would pause the video when true and resume playing when false. It kinda works. Specifically, when "paused" is true, the video pauses. However, when it becomes false, the video does not resume playing.

Perhaps it may have something to do the "updateUIViewController"? Also, I have no idea why "paused" must be a binding variable and not a state variable (@State does not work). Any help will be greatly appreciated. Thanks!

import SwiftUI
import AVKit

struct VideoElement: View {

    var fileName: String
    @Binding var paused: Bool //this controls playback

    var body: some View {
    
        let player = AVPlayer(url: URL(fileURLWithPath: Bundle.main.path(forAuxiliaryExecutable: fileName)!))
    
        AVPlayerControllerRepresented(player: player)
            .onAppear {
                player.play()
            }
            .onChange(of: paused, perform: { paused in
                if paused {
                    player.pause()
                }
                else {
                    player.play() //should play the video, does not
                    print("resume") //this runs, so above should've run also
                }
            })
        }  
    }
}

struct AVPlayerControllerRepresented : UIViewControllerRepresentable {

    var player : AVPlayer

    func makeUIViewController(context: Context) -> AVPlayerViewController {
        let controller = AVPlayerViewController()
        controller.player = player
        controller.showsPlaybackControls = false
        return controller
    }

    func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {
    }
}

Solution

  • I wouldn't recommend creating the AVPlayer in the body -- that will guarantee that any time the view is re-rendered, the player will be recreated. Instead, store it somewhere that will survive re-renders. I've chosen an ObservableObject for this.

    class PlayerManager : ObservableObject {
        let player = AVPlayer(url: URL(string: "https://media.w3.org/2010/05/sintel/trailer.mp4")!)
        @Published private var playing = false
        
        func play() {
            player.play()
            playing = true
        }
        
        func playPause() {
            if playing {
                player.pause()
            } else {
                player.play()
            }
            playing.toggle()
        }
    }
    
    struct ContentView: View {
        @StateObject var playerManager = PlayerManager()
        
        var body: some View {
            VStack {
                AVPlayerControllerRepresented(player: playerManager.player)
                    .onAppear {
                        playerManager.play()
                    }
                Button("Play/Pause") {
                    playerManager.playPause()
                }
            }
        }
    }
    
    struct AVPlayerControllerRepresented : UIViewControllerRepresentable {
        var player : AVPlayer
        
        func makeUIViewController(context: Context) -> AVPlayerViewController {
            let controller = AVPlayerViewController()
            controller.player = player
            controller.showsPlaybackControls = false
            return controller
        }
        
        func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {
            
        }
    }