自定义AVPlayer接入SharePlay后如何实现播放控制与进度同步
问题原因
同步失效的核心bug在自定义播放器封装层:
CustomVideoPlayer中的player是计算属性,每一次访问都会生成全新的AVPlayer实例- SwiftUI刷新视图、
updateUIViewController触发时,会不断给AVPlayerViewController替换新创建的player实例,你在configureGroupSession中给旧player绑定的playbackCoordinator同播协调关系会直接失效,新的player没有加入同播会话,自然无法同步暂停、拖动进度等操作。 - 切换播放资源时如果新建player实例,也会直接断开同播协调链路。
修复步骤
- 持久化唯一AVPlayer实例,全程复用,禁止在播放流程中随意新建player
- 把AVPlayer的管理逻辑上移到
EpisodeDetail层级,避免UIViewControllerRepresentable生命周期变化导致player重建 - 同播协调逻辑始终绑定这一个持久化的player实例
- 切换播放资源时仅调用
replaceCurrentItem替换播放项,不新建player
修正后的代码
1. 重构CustomVideoPlayer
移除内部新建player的逻辑,改为接收外部传入的持久化player实例:
import SwiftUI import AVFoundation import AVKit struct CustomVideoPlayer: UIViewControllerRepresentable { let player: AVPlayer func makeUIViewController(context: Context) -> AVPlayerViewController { let controller = AVPlayerViewController() controller.modalPresentationStyle = .fullScreen controller.allowsPictureInPicturePlayback = true controller.canStartPictureInPictureAutomaticallyFromInline = true controller.player = player return controller } func updateUIViewController(_ playerController: AVPlayerViewController, context: Context) { // 仅当player实例变化时才重新赋值,避免重复设置 guard playerController.player !== player else { return } playerController.player = player } }
2. 调整EpisodeDetail中的player管理逻辑
把AVPlayer作为单独的状态持久化,初始化时和自定义播放器绑定同一个实例,同播协调逻辑直接绑定这个持久化实例:
import SwiftUI import AVFoundation import AVKit import GroupActivities import Combine struct EpisodeDetail: View { @State var episode: CommonResponse<Episode> // 全局唯一持久化player实例,全程复用不重建 @State private var player: AVPlayer @State private var videoPlayer: CustomVideoPlayer @State private var groupSession: GroupSession<EpisodeActivity>? @State private var subscriptions = Set<AnyCancellable>() init(episode: CommonResponse<Episode>) { self._episode = State(initialValue: episode) let sharedPlayer = AVPlayer() self._player = State(initialValue: sharedPlayer) self._videoPlayer = State(initialValue: CustomVideoPlayer(player: sharedPlayer)) } var body: some View { VStack { videoPlayer .transition(.move(edge: .bottom)) .edgesIgnoringSafeArea(.all) ScrollView { VStack(alignment: .center) { Button { prepareToPlay(episode.attributes) } label: { Label("同播共享", systemImage: "shareplay") } .buttonStyle(.bordered) .cornerRadius(20) } VStack(alignment: .leading, spacing: 20) { Text(episode.attributes.title) .font(.title2) .fontWeight(.bold) Text(episode.attributes.description) .font(.body) .foregroundColor(.gray) } } .padding(12) } .task { for await session in EpisodeActivity.sessions() { configureGroupSession(session) } } } private func configureGroupSession(_ session: GroupSession<EpisodeActivity>) { groupSession = session // 绑定持久化的唯一player实例,协调关系不会因视图刷新断开 player.playbackCoordinator.coordinateWithSession(session) session.$state .sink { state in if case .invalidated = state { groupSession = nil subscriptions.removeAll() } } .store(in: &subscriptions) session.$activity .sink { activity in print("Activity Changed: \(activity.metadata.title ?? "无标题")") } .store(in: &subscriptions) session.join() } private func prepareToPlay(_ playerEpisodeData: Episode) { let activity = EpisodeActivity(episode: playerEpisodeData) // 复用现有player替换播放项,不新建实例 player.replaceCurrentItem(with: AVPlayerItem(url: playerEpisodeData.videoUrl)) Task { switch await activity.prepareForActivation() { case .activationDisabled: break case .activationPreferred: _ = try await activity.activate() case .cancelled: break @unknown default: break } } } }
验证注意事项
- 确保
EpisodeActivity正确实现GroupActivity协议,关联的元数据配置无误 - 测试设备需升级到iOS 15及以上版本,参与同播的设备登录了有效Apple ID,且已开启同播共享功能
- 播放流程中不要调用
AVPlayer()初始化方法新建实例,所有播放操作都基于持久化的那个player属性执行
内容的提问来源于stack exchange,提问作者Jimmy
相关产品推荐
相关产品推荐

