如何修改基于UIKit的AVPlayerView以适配Apple Watch?
适配Apple Watch的视频播放代码修改方案
核心问题分析
现有代码依赖UIKit框架(UIViewRepresentable、PlayerView继承自UIView),而Apple Watch使用WatchKit框架,无法直接兼容UIKit组件,需要通过平台条件编译分别实现iOS和WatchOS的适配层。
分步修改方案
1. 重构CustomVideoPlayer实现跨平台适配
通过条件编译分别实现iOS的UIViewRepresentable和WatchOS的WKInterfaceObjectRepresentable:
import SwiftUI import Combine import AVKit // iOS平台专属播放器视图 #if os(iOS) public struct CustomVideoPlayer: UIViewRepresentable { @ObservedObject private var playerVM: PlayerViewModel public init(_ playerVM: PlayerViewModel) { self.playerVM = playerVM } public func makeUIView(context: Context) -> PlayerView { let view = PlayerView() view.player = playerVM.player context.coordinator.setController(view.playerLayer) return view } public func updateUIView(_ uiView: PlayerView, context: Context) { } public func makeCoordinator() -> Coordinator { Coordinator(self) } public class Coordinator: NSObject, AVPictureInPictureControllerDelegate { private let parent: CustomVideoPlayer private var controller: AVPictureInPictureController? private var cancellable: AnyCancellable? init(_ parent: CustomVideoPlayer) { self.parent = parent super.init() cancellable = parent.playerVM.$isInPipMode .sink { [weak self] isPip in guard let self = self, let controller = self.controller else { return } if isPip, !controller.isPictureInPictureActive { controller.startPictureInPicture() } else if !isPip, controller.isPictureInPictureActive { controller.stopPictureInPicture() } } } public func setController(_ playerLayer: AVPlayerLayer) { controller = AVPictureInPictureController(playerLayer: playerLayer) controller?.canStartPictureInPictureAutomaticallyFromInline = true controller?.delegate = self } public func pictureInPictureControllerDidStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { parent.playerVM.isInPipMode = true } public func pictureInPictureControllerWillStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { parent.playerVM.isInPipMode = false } } } #endif // WatchOS平台专属播放器视图 #if os(watchOS) public struct CustomVideoPlayer: WKInterfaceObjectRepresentable { @ObservedObject private var playerVM: PlayerViewModel public init(_ playerVM: PlayerViewModel) { self.playerVM = playerVM } public func makeWKInterfaceObject(context: Context) -> WKInterfaceMovie { let movie = WKInterfaceMovie() movie.setPlayer(playerVM.player) movie.setVideoGravity(.resizeAspectFill) return movie } public func updateWKInterfaceObject(_ movie: WKInterfaceMovie, context: Context) { } public func makeCoordinator() -> Coordinator { Coordinator(self) } public class Coordinator: NSObject { private let parent: CustomVideoPlayer init(_ parent: CustomVideoPlayer) { self.parent = parent super.init() } } } #endif
2. 修改PlayerViewModel移除WatchOS不支持的功能
WatchOS没有画中画(PIP)功能,通过条件编译屏蔽相关属性:
import AVFoundation import Combine final public class PlayerViewModel: ObservableObject { public let player = AVPlayer() #if os(iOS) @Published var isInPipMode: Bool = false #endif @Published var isPlaying = false @Published var isEditingCurrentTime = false @Published var currentTime: Double = .zero @Published var duration: Double? private var subscriptions: Set<AnyCancellable> = [] private var timeObserver: Any? deinit { if let timeObserver = timeObserver { player.removeTimeObserver(timeObserver) } } public init() { $isEditingCurrentTime .dropFirst() .filter({ !$0 }) .sink(receiveValue: { [weak self] _ in guard let self = self else { return } self.player.seek(to: CMTime(seconds: self.currentTime, preferredTimescale: 1), toleranceBefore: .zero, toleranceAfter: .zero) if self.player.rate != 0 { self.player.play() } }) .store(in: &subscriptions) player.publisher(for: \.timeControlStatus) .sink { [weak self] status in guard let self = self else { return } switch status { case .playing: self.isPlaying = true case .paused: self.isPlaying = false case .waitingToPlayAtSpecifiedRate: break @unknown default: break } } .store(in: &subscriptions) timeObserver = player.addPeriodicTimeObserver(forInterval: CMTime(seconds: 1, preferredTimescale: 600), queue: .main) { [weak self] time in guard let self = self else { return } if !self.isEditingCurrentTime { self.currentTime = time.seconds } } } private func videoURL(_ filename: String) -> URL? { Bundle.main.url(forResource: filename, withExtension: "mp4") } public func setCurrentItem(_ filename: String){ currentTime = .zero duration = nil guard let videoURL = videoURL(filename) else { return } let avPlayerItem = AVPlayerItem(url: videoURL) player.replaceCurrentItem(with: avPlayerItem) avPlayerItem.publisher(for: \.status) .filter({ $0 == .readyToPlay }) .sink(receiveValue: { [weak self] _ in self?.duration = avPlayerItem.asset.duration.seconds }) .store(in: &subscriptions) } }
3. 调整PlayerView仅在iOS编译
将原PlayerView用iOS专属编译条件包裹,避免WatchOS编译错误:
#if os(iOS) import AVFoundation import UIKit final public class PlayerView: UIView { public override static var layerClass: AnyClass { AVPlayerLayer.self } var playerLayer: AVPlayerLayer { layer as! AVPlayerLayer } var player: AVPlayer? { get { playerLayer.player } set { playerLayer.videoGravity = .resizeAspectFill playerLayer.player = newValue } } } #endif
4. 适配CustomControlsView的WatchOS界面
针对WatchOS小屏幕调整控件尺寸:
import SwiftUI public struct CustomControlsView: View { @ObservedObject private var playerVM: PlayerViewModel public init(playerVM: PlayerViewModel) { self.playerVM = playerVM } // 根据平台设置按钮尺寸 private let buttonSize: CGFloat = { #if os(iOS) return 25 #elseif os(watchOS) return 20 #endif }() public var body: some View { HStack { if !playerVM.isPlaying { Button(action: { playerVM.player.play() }) { Image(systemName: "play.circle") .renderingMode(.template) .font(.system(size: buttonSize)) .foregroundColor(.black) } } else { Button(action: { playerVM.player.pause() }) { Image(systemName: "pause.circle") .renderingMode(.template) .font(.system(size: buttonSize)) .foregroundColor(.black) } } if let duration = playerVM.duration { Slider(value: $playerVM.currentTime, in: 0...duration, onEditingChanged: { isEditing in playerVM.isEditingCurrentTime = isEditing }) #if os(watchOS) .frame(height: 10) // WatchOS下调整Slider高度 #endif } else { Spacer() } } .padding() .background(.thinMaterial) } }
使用方式
原有使用代码无需修改,条件编译会自动根据平台加载对应实现:
CustomVideoPlayer(playerVM) .clipShape(RoundedRectangle(cornerRadius: 20, style: .continuous)) .onAppear { playerVM.setCurrentItem("beach") playerVM.player.play() }
关键修改说明
- 平台隔离:通过
#if os()编译指令,将UIKit和WatchKit的代码完全隔离,避免跨平台编译冲突。 - WatchOS播放器选型:使用
WKInterfaceMovie作为WatchOS的视频载体,这是Apple官方提供的适配组件,原生支持AVPlayer。 - 功能裁剪:移除WatchOS不支持的画中画功能,避免无效代码执行。
- 界面适配:针对WatchOS的小屏幕调整控件尺寸,保证交互可用性。
内容的提问来源于stack exchange,提问作者Duck
相关产品推荐
相关产品推荐

