Борьба с NotificationCenter / Combine в SwiftUI / AVPlayer - PullRequest
0 голосов
/ 02 марта 2020

Я пытаюсь приостановить AVPlayer, когда игра заканчивается. Каков наилучший способ сделать это с SwiftUI? Я не знаю много об уведомлениях, где их объявлять, и т. Д. c. Есть ли способ использовать Combine для этого? Пример кода будет потрясающим! Заранее спасибо.

ОБНОВЛЕНИЕ:

С помощью приведенного ниже ответа мне удалось создать класс, который принимает 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

Подписаться на некоторые View:

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

1 Ответ

0 голосов

Я нашел одно решение для аналогичной проблемы:

  1. Я создал новый подкласс AVPlayer;
  2. Добавлен наблюдатель в currentItem;
  3. Переопределить fun c 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()"
    }

}

UPDATE : простой пример использования .onReceive (будьте осторожны, я написал это без игровой площадки / 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"
        }
    }

}

О Combine с AVPlayer: вы можете посмотреть на мой вопрос , там вы увидите несколько способов наблюдать время воспроизведения и функциональность, чтобы перематывать время с помощью ползунка в 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
    }
}
...