Как управлять состоянием AVPlayer в SwiftUI

У меня есть список URL-адресов в SwiftUI. Когда я нажимаю на объект, я представляю полноэкранный видеопроигрыватель. У меня есть @EnvironmentObject, который обрабатывает некоторые параметры средства просмотра (например, показывать ли временной код). У меня также есть переключатель, который показывает и скрывает временной код (я включил переключатель только в этом примере, поскольку представление временного кода не имеет значения), но каждый раз, когда я меняю переключатель, представление создается снова, что повторно устанавливает AVPlayer . Это имеет смысл, поскольку я создаю игрока в инициализаторе представления.

Я думал о создании своего собственного ObserveredObject класса, содержащего AVPlayer, но я не уверен, как и где я его инициализирую, так как мне нужно дать ему URL-адрес, который я знаю только из инициализатора CustomPlayerView. Я также думал о настройке плеера как @EnvironmentObject, но мне кажется странным инициализировать то, что мне может не понадобиться (если пользователь не нажимает на URL-адрес для запуска плеера).

Как правильно создать AVPlayer в руки VideoPlayer AVKit? Вот мой пример кода:

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

person ADB    schedule 03.12.2020    source источник
comment
Пока я просто использую системный VideoPlayer - developer.apple.com/documentation/avkit/videoplayer   -  person ADB    schedule 04.12.2020
comment
Невоспроизводимо с предоставленным кодом - CustomPlayerView не создается повторно при переключении. Протестировано с Xcode 12.1 / iOS 14.1. Не могли бы вы предоставить больше контекста?   -  person Asperi    schedule 06.12.2020


Ответы (1)


Здесь вы можете воспользоваться несколькими подходами. Вы можете попробовать их и посмотреть, какой из них вам больше подходит.

Вариант 1. Как вы сказали, вы можете обернуть avPlayer в новый ObserveredObject класс

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 к уже существующему классу 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") }
        }
    }
}
person nishith Singh    schedule 09.12.2020
comment
Это великолепное спасибо - я уезжаю на этой неделе, поэтому не могу проверить это, но это отличная деталь и, похоже, она сделает именно то, что мне нужно. Спасибо! - person ADB; 10.12.2020
comment
Что, если мы хотим использовать iOS 13? потому что этот ответ поддерживается на iOS 14+ - person Ahmadreza; 15.04.2021