SwiftUI:如何正确呈现AVPlayerViewController模式?



正确的UIKit方法:

根据Apple关于该主题的WWDC 2019演讲,AVPlayerViewController应该以模式呈现,以利用API的所有最新全屏功能。这是建议从呈现的 UIKit 视图控制器调用的示例代码:

// 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)

这将按预期工作,并以漂亮的全屏启动视频。

使用 SwiftUI 达到相同的效果?

为了使用 SwiftUI 中的AVPlayerViewController,我创建了UIViewControllerRepresentable实现:

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()
}
}

我似乎无法弄清楚如何直接从 SwiftUI 呈现它 与直接呈现AVPlayerViewController的方式相同 来自UIKit。我的目标只是获得所有默认的全屏优势。

到目前为止,以下方法尚未奏效:

  • 如果我使用.sheet修饰符并从工作表中显示它,则播放器将嵌入到工作表中,而不是全屏显示。
  • 我还尝试在 UIKit 中创建一个自定义的空视图控制器,它只是从viewDidAppear方法以模式方式呈现我的AVPlayerViewController。这让播放器全屏显示,但它在显示视频之前也会显示一个空的视图控制器,我不希望用户看到。

任何想法将不胜感激!

只是一个想法,如果你喜欢像UIKit这样的全屏,你是否尝试了ContentView的以下代码。

import SwiftUI
import UIKit
import AVKit
struct ContentView: View {
let toPresent = UIHostingController(rootView: AnyView(EmptyView()))
@State private var vURL = URL(string: "https://www.radiantmediaplayer.com/media/bbb-360p.mp4")
var body: some View {
AVPlayerView(videoURL: self.$vURL).transition(.move(edge: .bottom)).edgesIgnoringSafeArea(.all)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

struct AVPlayerView: UIViewControllerRepresentable {
@Binding var videoURL: URL?
private var player: AVPlayer {
return AVPlayer(url: videoURL!)
}
func updateUIViewController(_ playerController: AVPlayerViewController, context: Context) {
playerController.modalPresentationStyle = .fullScreen
playerController.player = player
playerController.player?.play()
}
func makeUIViewController(context: Context) -> AVPlayerViewController {
return AVPlayerViewController()
}
}

Razib-Mollick 解释的解决方案对我来说是一个好的开始,但它缺少使用 SwiftUI.sheet()方法。我通过向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)
}
}

但问题是,当 SwiftUI 重新渲染 UI 时,AVPlayer 会一次又一次地实例化。 因此,AVPlayer 的状态必须移动到存储在环境中的class对象,以便我们可以从View struct.所以我的最新解决方案现在如下所示。我希望它能帮助其他人。

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
}
}

需要注意的事项(错误?(:每当使用.sheet()显示模态表时,环境对象都不会自动传递到子视图。它们必须使用environmentObject()添加。 以下是阅读有关此问题的更多信息的链接:https://oleb.net/2020/sheet-environment/

Xcode 12 · iOS 14

→ 使用.fullScreenCover而不是.sheet,你就可以开始了。

另请参阅:如何使用全屏覆盖呈现全屏模式视图

最新更新