macOS SwiftUI应用中AVPlayer加载新剧集后意外自动暂停问题
macOS SwiftUI应用中AVPlayer加载新剧集后意外自动暂停问题
嘿,我之前做macOS播客应用的时候也踩过AVPlayer自动暂停的坑,结合你贴的代码片段,大概率是这几个细节没处理好,咱们一步步来解决:
先帮你补全并梳理下你的现有代码(你贴的片段里有小疏漏)
首先看你ContentView里的播放按钮代码应该是打错了,playerViewMode...应该是playerViewModel,补全后的完整片段是这样的:
import SwiftUI import AVFoundation struct ContentView: View { @StateObject private var playerViewModel = PlayerViewModel() var body: some View { VStack { // 剧集列表 List(playerViewModel.episodeURLs.indices, id: \.self) { index in Button(action: { playerViewModel.loadEpisode(at: index) }) { Text("Episode \(index + 1)") } } // 播放/暂停按钮 Button(action: playerViewModel.togglePlayPause) { Image(systemName: playerViewModel.isPlaying ? "pause.circle.fill" : "play.circle.fill") .font(.largeTitle) } } .padding() } }
而你的PlayerViewModel大概率是类似这样的实现(我猜的,因为你没贴全),这也是问题的高发区:
class PlayerViewModel: ObservableObject { @Published var episodeURLs: [URL] = [/* 你的剧集URL数组 */] @Published var isPlaying = false private var player: AVPlayer? func loadEpisode(at index: Int) { let url = episodeURLs[index] let playerItem = AVPlayerItem(url: url) player = AVPlayer(playerItem: playerItem) // 这里很可能没处理item的就绪状态,也没主动触发播放 } func togglePlayPause() { guard let player = player else { return } if isPlaying { player.pause() } else { player.play() } isPlaying.toggle() } }
问题到底出在哪?
- 每次加载新剧集都新建AVPlayer实例:新创建的AVPlayer默认处于暂停状态,你如果没主动调用
play(),自然会停住; - 没监听AVPlayerItem的就绪状态:如果直接在加载item后调用
play(),item可能还没准备好,播放请求会被系统忽略; - macOS音频会话未激活:如果没有正确激活音频会话,系统可能会自动暂停你的播放请求。
具体的修复方案
1. 激活macOS音频会话
在PlayerViewModel的初始化方法里添加音频会话激活逻辑,确保系统允许你的应用播放音频:
class PlayerViewModel: ObservableObject { @Published var episodeURLs: [URL] = [/* 你的剧集URL */] @Published var isPlaying = false private var player: AVPlayer? private var playerItemObservation: NSKeyValueObservation? // 用来监听item状态 init() { // 激活音频会话 do { try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default) try AVAudioSession.sharedInstance().setActive(true) } catch { print("音频会话激活失败: \(error.localizedDescription)") } } // ... 其他方法 }
2. 复用AVPlayer实例,替换AVPlayerItem而非新建Player
每次新建Player不仅会导致状态重置,还会浪费资源,复用同一个Player只替换item更稳定:
func loadEpisode(at index: Int) { let url = episodeURLs[index] let playerItem = AVPlayerItem(url: url) // 监听AVPlayerItem的就绪状态,确保item准备好后再处理播放 playerItemObservation = playerItem.observe(\.status, options: [.new]) { [weak self] item, _ in guard let self = self else { return } switch item.status { case .readyToPlay: // 如果之前处于播放状态,加载新item后自动继续播放 if self.isPlaying { self.player?.play() } case .failed: print("剧集加载失败: \(item.error?.localizedDescription ?? "未知错误")") case .unknown: break @unknown default: break } } // 复用现有Player,只替换item if let existingPlayer = player { existingPlayer.replaceCurrentItem(with: playerItem) } else { // 第一次加载时创建Player player = AVPlayer(playerItem: playerItem) } // 如果当前是播放状态,先尝试触发播放(item就绪后会自动续上) if isPlaying { player?.play() } }
3. 同步播放状态与Player的真实状态
原来的togglePlayPause方法只依赖自己的isPlaying变量,容易出现状态不同步,改成用Player的timeControlStatus来判断真实状态:
func togglePlayPause() { guard let player = player else { return } switch player.timeControlStatus { case .playing: player.pause() isPlaying = false case .paused, .waitingToPlayAtSpecifiedRate: player.play() isPlaying = true @unknown default: break } }
最后再测试下
按照上面的修改后,加载新剧集时:
- 如果之前是播放状态,新item准备好后会自动继续播放;
- 如果之前是暂停状态,新item加载后也会保持暂停,完全符合你的需求。
备注:内容来源于stack exchange,提问作者THEBONESMITH
相关产品推荐
相关产品推荐

