According to Apple's WWDC 2019 talk on the subject, AVPlayerViewController
should be presented modally to take advantage of all the latest full-screen features of the API. This is the recommended sample code to be called from your presenting UIKit view controller:
// Create the player
let player = AVPlayer(url: videoURL)
// Create the player view controller and associate the player
let playerViewController = AVPlayerViewController()
playerViewController.player = player
// Present the player view controller modally
present(playerViewController, animated: true)
This works as expected and launches the video in beautiful full-screen.
In order to use the AVPlayerViewController
from SwiftUI, I created the UIViewControllerRepresentable
implementation:
struct AVPlayerView: UIViewControllerRepresentable {
@Binding var videoURL: URL
private var player: AVPlayer {
return AVPlayer(url: videoURL)
}
func updateUIViewController(_ playerController: AVPlayerViewController, context: Context) {
playerController.player = player
playerController.player?.play()
}
func makeUIViewController(context: Context) -> AVPlayerViewController {
return AVPlayerViewController()
}
}
I cannot seem to figure out how to present this directly from SwiftUI in the same way as the
AVPlayerViewController
is presented directly from UIKit. My goal is simply to get all of the default, full-screen benefits.
So far, the following has not worked:
.sheet
modifier and present it from within the sheet, then the player is embedded in a sheet and not presented full-screen. AVPlayerViewController
modally from the viewDidAppear
method. This gets the player to take on the full screen, but it also shows an empty view controller prior to display the video, which I do not want the user to see. Any thoughts would be much appreciated!
The solution explained by Razib-Mollick was a good start for me, but it was missing the use of the SwiftUI .sheet()
method. I have added this by adding the following to ContentView
:
@State private var showVideoPlayer = false
var body: some View {
Button(action: { self.showVideoPlayer = true }) {
Text("Start video")
}
.sheet(isPresented: $showVideoPlayer) {
AVPlayerView(videoURL: self.$vURL)
.edgesIgnoringSafeArea(.all)
}
}
But the problem is then, that the AVPlayer is instantiated again and again when SwiftUI re-renders the UI.
Therefore the state of the AVPlayer has to move to a class
object stored in the environment, so we can get hold of it from the View struct
. So my latest solution looks now as follows. I hope it helps somebody else.
class PlayerState: ObservableObject {
public var currentPlayer: AVPlayer?
private var videoUrl : URL?
public func player(for url: URL) -> AVPlayer {
if let player = currentPlayer, url == videoUrl {
return player
}
currentPlayer = AVPlayer(url: url)
videoUrl = url
return currentPlayer!
}
}
struct ContentView: View {
@EnvironmentObject var playerState : PlayerState
@State private var vURL = URL(string: "https://www.radiantmediaplayer.com/media/bbb-360p.mp4")
@State private var showVideoPlayer = false
var body: some View {
Button(action: { self.showVideoPlayer = true }) {
Text("Start video")
}
.sheet(isPresented: $showVideoPlayer, onDismiss: { self.playerState.currentPlayer?.pause() }) {
AVPlayerView(videoURL: self.$vURL)
.edgesIgnoringSafeArea(.all)
.environmentObject(self.playerState)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(PlayerState())
}
}
struct AVPlayerView: UIViewControllerRepresentable {
@EnvironmentObject var playerState : PlayerState
@Binding var videoURL: URL?
func updateUIViewController(_ playerController: AVPlayerViewController, context: Context) {
}
func makeUIViewController(context: Context) -> AVPlayerViewController {
let playerController = AVPlayerViewController()
playerController.modalPresentationStyle = .fullScreen
playerController.player = playerState.player(for: videoURL!)
playerController.player?.play()
return playerController
}
}
Something to be aware of (a bug?): whenever a modal sheet is shown using .sheet()
the environment objects are not automatically passed to the subviews. They have to be added using environmentObject()
.
Here is a link to read more about this problem: https://oleb.net/2020/sheet-environment/
Xcode 12 · iOS 14
→ Use .fullScreenCover
instead of .sheet
and you’re good to go.
See also: How to present a full screen modal view using fullScreenCover
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With