转换 AVPlayerView for Apple Watch

问题描述 投票:0回答:1

我在商店里有一个应用程序至少有六个月的时间,它的目标是 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 配合使用?

提前致谢?

swift swiftui avfoundation apple-watch
1个回答
0
投票

您尝试在 watchOS 上使用 watchOS 上不可用的视图和类型。

仅举几例:

  • AVPlayer层
  • AVPictureInPictureController
  • UI视图

你不能那样做。您要么需要在 watchOS 上找到它们的等效项,要么将它们提供的某些功能仅限于 iOS(例如画中画 - 这在 watchOS 上实际上没有意义)。

如果您希望能够在 iOS 上保留现有的视频播放器功能,则必须在 watchOS 上使用不同的视图来播放视频 - 这种方法无论如何都是有意义的,因为完全不同的屏幕尺寸意味着用户不会期望两个平台上的视频功能相同。

您可以在 iOS 上继续使用当前仅限 iOS 的组件,并在 watchOS 上使用单独的视频播放器视图。您有多种选择:

© www.soinside.com 2019 - 2024. All rights reserved.