【问题标题】:Struggling with NotificationCenter/Combine in SwiftUI/AVPlayer在 SwiftUI/AVPlayer 中与 NotificationCenter/Combine 斗争
【发布时间】:2020-06-14 06:22:13
【问题描述】:

我正在尝试在项目播放完毕后暂停我的 AVPlayer。使用 SwiftUI 执行此操作的最佳方法是什么?我不太了解通知,在哪里声明它们等。有没有办法为此使用组合?示例代码会很棒!提前谢谢你。

更新:

在下面答案的帮助下,我设法创建了一个类,它采用 AVPlayer 并在项目结束时发布通知。您可以通过以下方式订阅通知:

类:

import Combine
import AVFoundation

class PlayerFinishedObserver {

    let publisher = PassthroughSubject<Void, Never>()

    init(player: AVPlayer) {
        let item = player.currentItem

        var cancellable: AnyCancellable?
        cancellable = NotificationCenter.default.publisher(for: .AVPlayerItemDidPlayToEndTime, object: item).sink { [weak self] change in
            self?.publisher.send()
            print("Gotcha")
            cancellable?.cancel()
        }
    }
}

添加到您的结构中:

let finishedObserver: PlayerFinishedObserver

订阅一些观点:

.onReceive(finishedObserver.publisher) {
                print("Gotcha!")
            }

【问题讨论】:

    标签: swift swiftui avplayer combine notificationcenter


    【解决方案1】:

    我找到了一个类似问题的解决方案:

    1. 我创建了AVPlayer 的新子类;
    2. currentItem添加了观察者;
    3. 覆盖func observeValue,当玩家到达结束时间时为当前项目添加观察者;

    这里是简化的例子:

    import AVKit // for player
    import Combine // for observing and adding as environmentObject
    
    final class AudioPlayer: AVPlayer, ObservableObject {
    
        var songDidEnd = PassthroughSubject<Void, Never>() // you can use it in some View with .onReceive function
    
        override init() {
            super.init()
            registerObserves()
        }
    
        private func registerObserves() {
            self.addObserver(self, forKeyPath: "currentItem", options: [.new], context: nil)
            // example of using 
        }
    
        override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
            // currentItem could be nil in the player. I add observer to exist item
            if keyPath == "currentItem", let item = currentItem {
                NotificationCenter.default.addObserver(self, selector: #selector(playerDidFinishPlaying(_:)), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: item)
    
                // another way, using Combine
                var cancellable: AnyCancellable?
                cancellable = NotificationCenter.default.publisher(for: .AVPlayerItemDidPlayToEndTime, object: item).sink { [weak self] _ in
                    self?.songDidEnd.send()
                    cancellable?.cancel()
                }
            }
            // other observers
        }
    
        @objc private func playerDidFinishPlaying(_ notification: Notification) {
            playNextSong() // my implementation, here you can just write: "self.pause()"
        }
    
    }
    

    更新:使用.onReceive 的简单示例(注意,我在没有 Playground/Xcode 的情况下编写了它,所以它可能会出错):

    struct ContentView: View {
    
        @EnvironmentObject var audioPlayer: AudioPlayer
        @State private var someText: String = "song is playing"
    
        var body: some View {
            Text(someText)
                .onReceive(self.audioPlayer.songDidEnd) { // maybe you need "_ in" here
                    self.handleSongDidEnd()
            }
        }
    
        private func handleSongDidEnd() {
            print("song did end")
            withAnimation {
                someText = "song paused"
            }
        }
    
    }
    

    关于CombineAVPlayer:你可以看看我的question,在那里你会看到一些观察播放时间的方法和使用SwiftUI 中的滑块倒带时间的功能。

    我正在使用AudioPlayer 的一个实例,控制播放/暂停功能或更改currentItem(这意味着设置另一首歌曲),如下所示:

    class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    
        // other staff
        func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
            let homeView = ContentView()
                .environmentObject(AudioPlayer())
    
            // other staff of SceneDelegate
        }
    }
    

    【讨论】:

    • 非常感谢!我会尽快尝试的。你知道是否有办法使用更像这样的东西吗? NotificationCenter.default.publisher(for: .AVPlayerItemDidPlayToEndTime) 然后使用 .(onReceive: ) 响应?我想知道是否有办法避免@objc。我的很多 AVPlayer 函数都在我的 SwiftUI 视图中,它不允许我在那里使用 objc,因为它是一个结构。还是应该将这些功能移到 AudioPlayer 类中?
    • @StormTrooper 您可以在@objc 函数中更改一些Publisher,例如songDidEnd.send()(例如,您需要创建let songDidEnd = PassthroughSubject&lt;Void, Never&gt;())。所以你可以像往常一样使用.onReceive。关于NotificationCenter.default.publisher(for: .AVPlayerItemDidPlayToEndTime) - 有趣,也许我会尝试使用它。但是不,我不知道这种方式,所以我不能说,现在它是否有效
    • 好的,谢谢。我会搞砸它并回复你。非常感谢您的帮助。
    • @StormTrooper ,你的想法是对的,你可以这样写:NotificationCenter.default.publisher(for: .AVPlayerItemDidPlayToEndTime, object: item) 而不是我的变种。感谢这个想法)
    • @StormTrooper 我更新了代码 sn-p,有你想法的例子。我没测试过,你可以试试。它似乎会起作用
    猜你喜欢
    • 2020-09-18
    • 2018-05-23
    • 2013-11-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-03
    • 2021-10-30
    相关资源
    最近更新 更多