You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:35:18