SwiftUI中AVPlayer播放控制优化及进度音量滑块实现
优化AVPlayer播放控制与实现滑块方案
哥们,你的基础实现思路是对的,但确实存在状态不同步和UI绑定的问题,我给你梳理下更可靠的优化方案,以及如何搞定进度和音量滑块:
一、重构播放控制逻辑,解决状态同步问题
你现在手动维护isPlaying很容易出现不同步(比如播放器因加载失败自动暂停,你的isPlaying还会停在true),而且直接访问player?.currentItem的属性无法触发UI更新。我们可以把需要UI响应的属性包装成@Published,并通过监听AVPlayer的状态自动同步:
import AVFoundation import Combine final class UserData: ObservableObject { @Published var player: AVPlayer? = nil @Published var isPlaying: Bool = false // 新增用于滑块的响应式属性 @Published var currentPlaybackTime: Double = 0.0 @Published var trackDuration: Double = 0.0 @Published var playbackVolume: Float = 1.0 private var cancellables = Set<AnyCancellable>() private var timeObserverToken: Any? func setupPlayer(for song: Song) { guard let url = URL(string: song.audioUrl) else { return } let player = AVPlayer(url: url) self.player = player // 监听播放速率,自动同步isPlaying状态 player.publisher(for: \.rate) .sink { [weak self] rate in self?.isPlaying = rate > 0 } .store(in: &cancellables) // 监听当前播放项变化,更新总时长 player.publisher(for: \.currentItem) .sink { [weak self] item in // 处理无效时长的情况 self?.trackDuration = item?.duration.isValid == true ? item!.duration.seconds : 0.0 // 重新添加时间观察者 self?.removeTimeObserver() self?.addTimeObserver() } .store(in: &cancellables) // 同步初始音量 player.volume = playbackVolume } func togglePlayPause() { guard let player = player else { return } isPlaying ? player.pause() : player.play() } // 定时更新当前播放时间,用于进度滑块 private func addTimeObserver() { guard let player = player else { return } // 每0.5秒更新一次,平衡性能和流畅度 let updateInterval = CMTimeMakeWithSeconds(0.5, preferredTimescale: 1) timeObserverToken = player.addPeriodicTimeObserver(forInterval: updateInterval, queue: .main) { [weak self] currentTime in self?.currentPlaybackTime = currentTime.seconds } } private func removeTimeObserver() { if let token = timeObserverToken { player?.removeTimeObserver(token) timeObserverToken = nil } } // 清理资源,避免内存泄漏 deinit { removeTimeObserver() cancellables.forEach { $0.cancel() } } }
二、实现进度与音量滑块
现在有了响应式的currentPlaybackTime、trackDuration和playbackVolume,就可以安全地绑定滑块,同时处理用户拖动滑块的跳转逻辑:
struct PlayerControlsView: View { @EnvironmentObject var userData: UserData var body: some View { VStack(spacing: 20) { // 播放进度滑块 Slider( value: $userData.currentPlaybackTime, in: 0...userData.trackDuration, step: 1.0 ) .onChange(of: userData.currentPlaybackTime) { targetTime in // 用户拖动滑块时,让播放器跳转到对应时间 let cmTime = CMTimeMakeWithSeconds(targetTime, preferredTimescale: 1) userData.player?.seek(to: cmTime) } .disabled(userData.trackDuration == 0) // 时长未加载时禁用滑块 // 播放暂停按钮 Button(action: { userData.togglePlayPause() }) { Image(systemName: userData.isPlaying ? "pause.circle.fill" : "play.circle.fill") .font(.system(size: 60)) .foregroundColor(.blue) } // 音量滑块 Slider( value: $userData.playbackVolume, in: 0...1, step: 0.01 ) .onChange(of: userData.playbackVolume) { newVolume in userData.player?.volume = newVolume } } .padding() } }
三、额外优化建议
- 处理加载错误:可以监听
AVPlayerItem的status属性,当状态为.failed时给用户提示错误信息。 - 预加载处理:如果是播放列表,可以提前预加载下一首歌曲,提升播放流畅度。
- 内存优化:在切换歌曲时,记得销毁旧的
AVPlayer和时间观察者,避免内存堆积。
这样修改后,不仅解决了滑块绑定的问题,播放状态的同步也更可靠,UI会自动响应所有播放相关属性的变化。
内容的提问来源于stack exchange,提问作者Hrabovskyi Oleksandr
相关产品推荐
相关产品推荐

