如何在 SwiftUI 中管理 AVPlayer 状态

How to manage AVPlayer state in SwiftUI

我在 SwiftUI 中有一个 URL 的列表。当我点击一个项目时,我会呈现一个全屏视频播放器。我有一个 @EnvironmentObject 来处理一些查看器选项(例如,是否显示时间码)。我还有一个显示和隐藏时间码的开关(我只在这个例子中包含了开关,因为时间码视图无关紧要)但是每次我更改开关时都会再次创建视图,这会重新设置 AVPlayer。这是有道理的,因为我在视图的初始化程序中创建播放器。

我考虑过创建自己的 ObserveredObject class 来包含 AVPlayer 但我不确定如何或在哪里初始化它,因为我需要给它一个URL,我只从 CustomPlayerView 的初始化程序中知道。我还考虑过将播放器设置为 @EnvironmentObject,但初始化我可能不需要的东西似乎很奇怪(如果用户没有点击 URL 来启动播放器)。

请问创建 AVPlayer 交给 AVKit 的 VideoPlayer 的正确方法是什么?这是我的示例代码:

class ViewerOptions: ObservableObject {
    @Published var showTimecode = false
}

struct CustomPlayerView: View {
    
    @EnvironmentObject var viewerOptions: ViewerOptions
    
    private let avPlayer: AVPlayer
    
    init(url: URL) {
        avPlayer = AVPlayer(url: url)
    }
    
    var body: some View {
        HStack {
            VideoPlayer(player: avPlayer)
            Toggle(isOn: $viewerOptions.showTimecode) { Text("Show Timecode") }
        }
    }
}

您可以在此处采用多种方法。您可以尝试一下,看看哪一款最适合您。

选项 1:正如您所说,您可以将 avPlayer 包装在新的 ObserveredObject class

class PlayerViewModel: ObservableObject {
    @Published var avPlayer: AVPlayer? = nil
}

class ViewerOptions: ObservableObject {
    @Published var showTimecode = false
}


@main
struct DemoApp: App {
    var playerViewModel = PlayerViewModel()
    var viewerOptions = ViewerOptions()

    var body: some Scene {
        WindowGroup {
            CustomPlayerView(url: URL(string: "Your URL here")!)
                .environmentObject(playerViewModel)
                .environmentObject(viewerOptions)
        }
    }
}

struct CustomPlayerView: View {
    @EnvironmentObject var viewerOptions: ViewerOptions
    @EnvironmentObject var playerViewModel: PlayerViewModel

    init(url: URL) {
        if playerViewModel.avPlayer == nil {
            playerViewModel.avPlayer = AVPlayer(url: url)
        } else {
            playerViewModel.avPlayer?.pause()
            playerViewModel.avPlayer?.replaceCurrentItem(with: AVPlayerItem(url: url))
        }
    }

    var body: some View {
        HStack {
            VideoPlayer(player: playerViewModel.avPlayer)
            Toggle(isOn: $viewerOptions.showTimecode) { Text("Show Timecode") }
        }
    }
}

选项 2:您可以将 avPlayer 添加到您已经存在的 class ViewerOptions 作为可选 属性 然后在需要时初始化它

class ViewerOptions: ObservableObject {
    @Published var showTimecode = false
    @Published var avPlayer: AVPlayer? = nil
}

struct CustomPlayerView: View {

    @EnvironmentObject var viewerOptions: ViewerOptions

    init(url: URL) {
        if viewerOptions.avPlayer == nil {
            viewerOptions.avPlayer = AVPlayer(url: url)
        } else {
            viewerOptions.avPlayer?.pause()
            viewerOptions.avPlayer?.replaceCurrentItem(with: AVPlayerItem(url: url))
        }
    }

    var body: some View {
        HStack {
            VideoPlayer(player: viewerOptions.avPlayer)
            Toggle(isOn: $viewerOptions.showTimecode) { Text("Show Timecode") }
        }
    }
}

选项 3:使您的 avPlayer 成为一个状态对象,这样它的内存将由系统管理,在您的视图存在之前,它不会重新设置并为您保持活动状态。

class ViewerOptions: ObservableObject {
    @Published var showTimecode = false
}

struct CustomPlayerView: View {

    @EnvironmentObject var viewerOptions: ViewerOptions
    @State private var avPlayer: AVPlayer

    init(url: URL) {
        _avPlayer = .init(wrappedValue: AVPlayer(url: url))
    }

    var body: some View {
        HStack {
            VideoPlayer(player: avPlayer)
            Toggle(isOn: $viewerOptions.showTimecode) { Text("Show Timecode") }
        }
    }
}

选项 4:在需要时创建您的 avPlayer 对象然后忘记它(不确定这是否是最适合您的方法,但如果您不需要播放器对象来执行自定义操作,那么您可以使用这个选项)

class ViewerOptions: ObservableObject {
    @Published var showTimecode = false
}

struct CustomPlayerView: View {

    @EnvironmentObject var viewerOptions: ViewerOptions
    private let url: URL

    init(url: URL) {
        self.url = url
    }

    var body: some View {
        HStack {
            VideoPlayer(player: AVPlayer(url: url))
            Toggle(isOn: $viewerOptions.showTimecode) { Text("Show Timecode") }
        }
    }
}