我在商店里有一个应用程序至少有六个月的时间,它的目标是 iOS,另一个目标是 Apple Watch。两个目标都使用
AVFoundation
播放视频。
我用于播放视频的代码给我带来了问题。我在网上找到了一些代码,对其进行了修改并设法使其适用于 iOS。现在我正在尝试为手表做这件事。
这是代码:
#自定义视频播放器
import SwiftUI
import Combine
import AVKit
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 {
return 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] in
guard let self = self,
let controller = self.controller else { return }
if $0 {
if controller.isPictureInPictureActive == false {
controller.startPictureInPicture()
}
} else if 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
}
}
}
#自定义控件视图
import SwiftUI
public struct CustomControlsView: View {
@ObservedObject private var playerVM: PlayerViewModel
public init(playerVM: PlayerViewModel) {
self.playerVM = playerVM
}
public var body: some View {
HStack {
if playerVM.isPlaying == false {
Button(action: {
playerVM.player.play()
}, label: {
Image(systemName: "play.circle")
.renderingMode(.template)
.font(.system(size: 25))
.foregroundColor(.black)
})
} else {
Button(action: {
playerVM.player.pause()
}, label: {
Image(systemName: "pause.circle")
.renderingMode(.template)
.font(.system(size: 25))
.foregroundColor(.black)
})
}
if let duration = playerVM.duration {
Slider(value: $playerVM.currentTime, in: 0...duration, onEditingChanged: { isEditing in
playerVM.isEditingCurrentTime = isEditing
})
} else {
Spacer()
}
}
.padding()
.background(.thinMaterial)
}
}
#PlayerViewModel
import AVFoundation
import Combine
final public class PlayerViewModel: ObservableObject {
public let player = AVPlayer()
@Published var isInPipMode: Bool = false
@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 == false })
.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
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 == false {
self.currentTime = time.seconds
}
}
}
private var videoName = ""
private func videoURL(_ filename:String) -> URL? {
guard let fileURL = Bundle.main.url(forResource:filename,
withExtension: "mp4")
else { return nil }
return fileURL
}
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)
}
}
#玩家视图
import AVFoundation
import UIKit
final public class PlayerView: UIView {
public override static var layerClass: AnyClass {
return AVPlayerLayer.self
}
var playerLayer: AVPlayerLayer { layer as! AVPlayerLayer }
var player: AVPlayer? {
get {
playerLayer.player
}
set {
playerLayer.videoGravity = .resizeAspectFill
playerLayer.player = newValue
}
}
}
使用方法:
CustomVideoPlayer(playerVM)
.clipShape(RoundedRectangle(cornerRadius: 20, style: .continuous))
.onAppear {
if let filename = item.filename {
playerVM.setCurrentItem("beach") // load 'beach.mp4' from the bundle
playerVM.player.play()
}
}
我的问题是
PlayerView
使用UIKit
,而Apple Watch没有该框架。
如何修改此代码以与 Apple Watch 配合使用?
提前致谢?
您尝试在 watchOS 上使用 watchOS 上不可用的视图和类型。
仅举几例:
你不能那样做。您要么需要在 watchOS 上找到它们的等效项,要么将它们提供的某些功能仅限于 iOS(例如画中画 - 这在 watchOS 上实际上没有意义)。
如果您希望能够在 iOS 上保留现有的视频播放器功能,则必须在 watchOS 上使用不同的视图来播放视频 - 这种方法无论如何都是有意义的,因为完全不同的屏幕尺寸意味着用户不会期望两个平台上的视频功能相同。
您可以在 iOS 上继续使用当前仅限 iOS 的组件,并在 watchOS 上使用单独的视频播放器视图。您有多种选择:
UIViewRepresentable
并包装原生 WatchKit
接口对象来播放视频,例如 WKInterfaceMovie