推迟 init() AVPlayer SwiftUI

Postpone init() AVPlayer SwitUI

我找到了 OObject 的代码,可以用作基本的音频播放器(带滑块) 工作正常,但是到目前为止我可以像这样在 ContentView 中使用它:

@ObservedObject var player: AudioPlayerAV

let audioFileURL = URL(string: "https://file-examples-com.github.io/uploads/2017/11/file_example_MP3_5MG.mp3")


init(){
    
    var playerItem = AVPlayerItem(url: audioFileURL!)
    player = AudioPlayerAV(avPlayer: AVPlayer(playerItem: playerItem))
}

如果我有 URL 或本地路径,我可以使用它。我不知道如何推迟初始化,例如,如果我想在同一 ContentView.

中记录的元素上使用它
//OObject

import AVFoundation
import Combine

let timeScale = CMTimeScale(1000)
let time = CMTime(seconds: 0.5, preferredTimescale: timeScale)

enum PlayerScrubState {
    case reset
    case scrubStarted
    case scrubEnded(TimeInterval)
}


final class AudioPlayerAV: NSObject, ObservableObject {

 
    @Published var displayTime: TimeInterval = 0
    @Published var observedTime: TimeInterval = 0
    @Published var itemDuration: TimeInterval = 0
    @Published var audioFinishedPlaying: Bool = false
    @Published var timeControlStatus: AVPlayer.TimeControlStatus = .paused
   
    
    fileprivate var itemDurationKVOPublisher: AnyCancellable!
    fileprivate var timeControlStatusKVOPublisher: AnyCancellable!
    fileprivate var avPlayer: AVPlayer
    fileprivate var periodicTimeObserver: Any?

    var scrubState: PlayerScrubState = .reset {
        didSet {
            switch scrubState {
            case .reset:
                return
            case .scrubStarted:
                return
            case .scrubEnded(let seekTime):
                avPlayer.seek(to: CMTime(seconds: seekTime, preferredTimescale: 1000))
            }
        }
    }

    init(avPlayer: AVPlayer) {
        self.avPlayer = avPlayer
        super.init()

        addPeriodicTimeObserver()
        addTimeControlStatusObserver()
        addItemDurationPublisher()
        
        NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil, queue: .main) { (_) in
            self.audioFinishedPlaying = true

        }
        
        
    }

    deinit {
        removePeriodicTimeObserver()
        timeControlStatusKVOPublisher.cancel()
        itemDurationKVOPublisher.cancel()
        
        NotificationCenter.default.removeObserver(self)
    }

    func play() {
        self.avPlayer.play()
    }

    func pause() {
        self.avPlayer.pause()
    }

    fileprivate func addPeriodicTimeObserver() {
        self.periodicTimeObserver = avPlayer.addPeriodicTimeObserver(forInterval: time, queue: .main) { [weak self] (time) in
            guard let self = self else { return }

            self.observedTime = time.seconds

            switch self.scrubState {
            case .reset:
                self.displayTime = time.seconds
            case .scrubStarted:
                break
            case .scrubEnded(let seekTime):
                self.scrubState = .reset
                self.displayTime = seekTime
            }
        }
    }

    fileprivate func removePeriodicTimeObserver() {
        guard let periodicTimeObserver = self.periodicTimeObserver else {
            return
        }
        avPlayer.removeTimeObserver(periodicTimeObserver)
        self.periodicTimeObserver = nil
    }

    fileprivate func addTimeControlStatusObserver() {
        timeControlStatusKVOPublisher = avPlayer
            .publisher(for: \.timeControlStatus)
            .receive(on: DispatchQueue.main)
            .sink(receiveValue: { [weak self] (newStatus) in
                guard let self = self else { return }
                self.timeControlStatus = newStatus
                }
        )
    }

    fileprivate func addItemDurationPublisher() {
        itemDurationKVOPublisher = avPlayer
            .publisher(for: \.currentItem?.duration)
            .receive(on: DispatchQueue.main)
            .sink(receiveValue: { [weak self] (newStatus) in
                guard let newStatus = newStatus,
                    let self = self else { return }
                self.itemDuration = newStatus.seconds
                }
        )
    }

}

在非 SwiftUI 的情况下,我通常建议将 player 设置为可选并稍后加载它,但是,正如您可能已经发现的那样,您不能设置 @ObservedObject@StateObject 可选。

我建议重构 AudioPlayerAV,以便它在与 init 不同的功能中完成重要工作。这样,您就可以随时加载内容。

例如:


struct ContentView: View {
    @StateObject var player: AudioPlayerAV = AudioPlayerAV()
    @State private var urlText = "https://file-examples-com.github.io/uploads/2017/11/file_example_MP3_5MG.mp3"
    
    var body: some View {
        TextField("", text: $urlText)
        Button(action: {
            if let url = URL(string: urlText) {
                player.loadPlayer(avPlayer: AVPlayer(url: url))
                player.play()
            } else {
                print("not valid")
            }
        }) {
            Text("Load")
        }
    }
}

struct Response: Codable {
    // ... some fields here
}

let timeScale = CMTimeScale(1000)
let time = CMTime(seconds: 0.5, preferredTimescale: timeScale)

enum PlayerScrubState {
    case reset
    case scrubStarted
    case scrubEnded(TimeInterval)
}


final class AudioPlayerAV: NSObject, ObservableObject {
    @Published var displayTime: TimeInterval = 0
    @Published var observedTime: TimeInterval = 0
    @Published var itemDuration: TimeInterval = 0
    @Published var audioFinishedPlaying: Bool = false
    @Published var timeControlStatus: AVPlayer.TimeControlStatus = .paused
    
    
    fileprivate var itemDurationKVOPublisher: AnyCancellable?
    fileprivate var timeControlStatusKVOPublisher: AnyCancellable?
    fileprivate var avPlayer: AVPlayer?
    fileprivate var periodicTimeObserver: Any?
    
    var scrubState: PlayerScrubState = .reset {
        didSet {
            switch scrubState {
            case .reset:
                return
            case .scrubStarted:
                return
            case .scrubEnded(let seekTime):
                avPlayer?.seek(to: CMTime(seconds: seekTime, preferredTimescale: 1000))
            }
        }
    }
    
    func loadPlayer(avPlayer: AVPlayer) {
        removeObservers()
        self.avPlayer = avPlayer
        
        addPeriodicTimeObserver()
        addTimeControlStatusObserver()
        addItemDurationPublisher()
        
        NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil, queue: .main) { (_) in
            self.audioFinishedPlaying = true
            
        }
    }
    
    func removeObservers() {
        removePeriodicTimeObserver()
        timeControlStatusKVOPublisher?.cancel()
        itemDurationKVOPublisher?.cancel()
        
        NotificationCenter.default.removeObserver(self)
    }
    
    deinit {
        removeObservers()
    }
    
    func play() {
        self.avPlayer?.play()
    }
    
    func pause() {
        self.avPlayer?.pause()
    }
    
    fileprivate func addPeriodicTimeObserver() {
        self.periodicTimeObserver = avPlayer?.addPeriodicTimeObserver(forInterval: time, queue: .main) { [weak self] (time) in
            guard let self = self else { return }
            
            self.observedTime = time.seconds
            
            switch self.scrubState {
            case .reset:
                self.displayTime = time.seconds
            case .scrubStarted:
                break
            case .scrubEnded(let seekTime):
                self.scrubState = .reset
                self.displayTime = seekTime
            }
        }
    }
    
    fileprivate func removePeriodicTimeObserver() {
        guard let periodicTimeObserver = self.periodicTimeObserver else {
            return
        }
        avPlayer?.removeTimeObserver(periodicTimeObserver)
        self.periodicTimeObserver = nil
    }
    
    fileprivate func addTimeControlStatusObserver() {
        guard let avPlayer = avPlayer else {
            return
        }
        timeControlStatusKVOPublisher = avPlayer
            .publisher(for: \.timeControlStatus)
            .receive(on: DispatchQueue.main)
            .sink(receiveValue: { [weak self] (newStatus) in
                guard let self = self else { return }
                self.timeControlStatus = newStatus
            }
            )
    }
    
    fileprivate func addItemDurationPublisher() {
        guard let avPlayer = avPlayer else {
            return
        }
        itemDurationKVOPublisher = avPlayer
            .publisher(for: \.currentItem?.duration)
            .receive(on: DispatchQueue.main)
            .sink(receiveValue: { [weak self] (newStatus) in
                guard let newStatus = newStatus,
                      let self = self else { return }
                self.itemDuration = newStatus.seconds
            }
            )
    }
    
}