推迟 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
}
)
}
}
我找到了 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
}
)
}
}