2

I have created a UIViewRepresentable version of AVPlayerViewController to play movies using SwiftUI, with a provided URL and optionally enabling/disabling controls. Here is that code:

struct PlayerViewController: UIViewRepresentable {
    // Provide the player to this swiftui view
    var url: URL
    var showControls: Bool = true

    typealias UIViewType = UIView

    func makeUIView(context: UIViewRepresentableContext<PlayerViewController>) -> UIView {
        let controller = AVPlayerViewController()
        controller.player = AVPlayer(url: url)
        controller.showsPlaybackControls = showControls
        context.coordinator.controller = controller
        return controller.view
    }

    func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<PlayerViewController>) {
    }

    func makeCoordinator() -> PlayerViewController.Coordinator {
        Coordinator()
    }

    class Coordinator: NSObject {
        var controller: AVPlayerViewController?

        deinit {
            print("deinit coordinator")
        }
    }
}

The player is part of a detail view that is access from a List embedded in a NavigationView, like this:

NavigationView {
    List {
        NavigationLink(destination: DetailView(url: URL("url to movie")))
    }
}

DetailView simply instantiates PlayerViewController with the provided url.

The problem I'm having is that if I play the movie in DetailView, when I hit the back button to return from DetailView to the main screen, I can still hear the audio from the movieit's clearly still playing off-screen.

Other SO questions have indicated that this is because a reference to the AVPlayer is kept after the view is dismissed. If true, I'm not sure why; or, it doesn't seem like I have control over that.

If, after returning to the main view and hearing the video playing in the background, I then tap another list item, the playback immediately stops and I see the "deinit coordinator" debug message.

How can I stop playback when the PlayerViewController is dismissed and the user returns to the main view?

drootang
  • 2,351
  • 1
  • 20
  • 30

2 Answers2

1

You can pause playback through the viewWillDisappear method by adding an extension

extension AVPlayerViewController {
    open override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        self.player?.pause()
        print("*** Video paused.")
    }
}
marlicheng
  • 11
  • 1
  • This solution will create problems when going in full screen mode. It will pause the video again. – saad_nad Jun 24 '20 at 10:21
  • 1
    The docs clearly states `The framework doesn’t support subclassing AVPlayerViewController.` which means you should not even override, unless you like troubles – coder.pm Aug 21 '21 at 16:29
  • This will not work in this case because SwiftUI does not call any method on the controller. – Raphaël Oct 14 '21 at 20:58
0

You might want to have your UIViewControllerType not AVPlayerViewController but a custom ContainerViewController. Then you add as its child, an instance of AVPlayerViewController and keep a reference to it. Then in the override viewXDisappear of ContainerViewController you would have avPlayerViewControllerInstance.stop(). That should do it.

coder.pm
  • 71
  • 2
  • 8