Swift/SwiftUI/AVFoundation:有没有办法在新的 AVPlayer 中获取两个 AVPlayer 的 "output" 和 combine/composite?

Swift/SwiftUI/AVFoundation: Is there a way to take the "output" of two AVPlayers and combine/composite that in a new AVPlayer?

假设您有两个(已配置的)AVPlayer 实例(即,它们的 playerItems 加载了一些 AVAsset,并且呈现出来以便您可以 play/pause,等等)

我想知道是否有任何方法可以获取每个玩家展示的内容(如果愿意,也就是他们的“输出”)并将它们组合成另一个单独的两个“层”AVPlayer 例如,如果您暂停其中一个“源”播放器,该层也会在合成播放器中暂停,或者如果您对其中一个源应用效果,该效果也会反映在合成播放器中 AVPlayer?

我一直在研究 CADisplayLink 并且想知道这是否是一个选择 - 我应该说我对 AVFoundation 及其相关 API 的经验非常有限,因此我的问题。

提前致谢。

以防万一有人发现这个,下面是我最终解决这个需求的方法:

  1. CADisplayLink 当然与它无关——我认为这个名字暗示了一些事实,但事实并非如此; CADisplayLink 就所有意图和目的而言,是一个与显示刷新率“链接”的计时器 - 非常有用,只是不是我需要的;
  2. 由于我需要准确显示两个播放器输出的内容,包括应用于媒体的任何处理和过滤器,我还研究了AVVideoComposition,并且尽管 可以 用于在有限的媒体(即特定时间长度)内实现类似的效果,但它不适用于我的应用程序,它需要连续显示 - 想想认为它是一种无限长度的媒体,就像直播一样,但它不是直播;
  3. 经过一些概念和想法的斗争,我意识到我的做法是错误的,而且解决方案其实很简单,想想看。

这是我的解决方案(请记住,我正在开发 SwiftUI 生命周期应用程序,因此有一些选择):

  • 我最终创建了一个 UIViewRepresentable,它创建了一个自定义 UIKit 视图的实例 - 我们称它为 CompositePlayer;
  • 这是一个自定义 UIView(实际上是一个子 class),它接收两个 AVQueuePlayer 对象作为参数(这些是我加载媒体、应用过滤器的播放器,等等)——让我们称它们为 player_1player_2(我也将它们声明为 var,尽管我确信你 不需要 如果你不打算稍后更改对象 - 我做了);
  • class 实例化了两个“新的”AVPlayerLayer 对象(分别为 playerLayer_1playerLayer_2
  • 然后将这两个接收到的玩家指定为各自的 AVPlayerLayer.player 属性;
  • 然后作为 subLayer 以所需的顺序添加到视图 layer 中(即,首先 player_1,然后 player_2,这使得它呈现“on player_1)
  • 的顶部"

伪代码(ish)类似于:

class CompositePlayer: UIView {

    var player_1: AVQueuePlayer
    var player_2: AVQueuePlayer

    private let playerLayer_1: AVPlayerLayer()
    private let playerLayer_2: AVPlayerLayer()

    // You need to provide any initialiser - it is not mandatory
    // to override the default init(frame: CGRect); I'm sure you
    // knew that, but n00bs like me don't, really...

    init(frame: CGRect, player_1: AVQueuePlayer, player_2: AVQueuePlayer) {
        self.player_1 = player_1
        self.player_2 = player_2
        super.init(frame: frame)

        // Now that we are initialised, assign the players to the
        // respective player layers...
        self.playerLayer_1.player = player_1
        self.playerLayer_2.player = player_2

        // ... and add these to the view's layer as sublayers in
        // the desired order - here, player_2 will render "on top"
        // of player_1:
        layer.addSublayer(playerLayer_1)
        layer.addSubLayer(playerLayer_2)
    }

    // This is required (as noted by the keyword...) when subclassing
    // UIView
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // Finally, you need to set the frame size for each layer, which
    // is easily achievable by overriding the layoutSubviews() method:
    override func layoutSubviews() {
        super.layoutSubviews()
        playerLayer_1.frame = bounds
        playerLayer_2.frame = bounds
    }
}

你得到了什么?

你得到一个视图,然后如前所述,通过 UIViewRepresentable 的方式实例化,它能够呈现与普通玩家在其自己的“原始”实例中呈现的完全相同的东西(缺点和所有),因为 AVQueuePlayer 个对象 可以有效地输出到多个视图 ;如果您正确地将正确的对象分配给 UIViewRepresentable 结构的成员(通过使用 EnvironmentObject),您将获得额外的好处,即能够在其他地方访问两个 playerLayer 对象并且能够设置,例如,该层的不透明度,对其进行转换等

就像变魔术一样,您对“原始”玩家所做的任何操作都将 100% 反映在这个视图上 - 一些 视图固有的显着例外,而不是播放器:例如,设置播放器的 videoGravity 不会影响 此视图的显示,因为您不会对其几何体进行“操作”。

呸。那很长(我真的缩短了它!)。 我希望它对某人有所帮助!