iOS 17 Beta 在应用程序和小部件之间共享 AVAudioPlayer 状态

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

我正在为我的应用程序创建一个音频播放小部件,旨在实现与 Apple Music 小部件类似的功能。具体来说,我在小部件中实现了一个播放按钮,用于触发 AudioPlaybackIntent。然后,此意图与管理我的 AVAudioPlayer 的单例类进行交互。

我面临的问题是我的主应用程序中的 AVAudioPlayer 实例和我的小部件扩展中的实例似乎不共享相同的状态。

我注意到Apple Music小部件能够显示实时变化,这意味着它有某种方式在主应用程序和小部件之间共享播放状态。

如何在我的 AVAudioPlayer 实例的主应用程序和小部件之间实现共享状态? Apple Music 是否使用特定方法或 API 来实现这一目标?

这是我的代码流程:

struct PlayBalanceTrackIntent: AudioPlaybackIntent {
    init() {
        do {
            try AVAudioSession.sharedInstance().setCategory(.playback)
            try AVAudioSession.sharedInstance().setActive(true)
        } catch {
            print("Failed to set up audio session category: \(error)")
        }
    }

    init(trackURL: String) {
        self.trackURL = trackURL
    }

    func perform() async throws -> some IntentResult {
        if PlayerModel.shared.isPlaying {
            PlayerModel.shared.pauseAudio()
        } else {
            PlayerModel.shared.setupAudio(from: trackURL)
        }
        return .result()
    }
}

然后是简单的播放器

class PlayerModel: ObservableObject {
    static let shared = PlayerModel()
    @Published var mainPlayer: AVPlayer?
    @Published var isPlaying: Bool = false

    private var timeObserver: Any?

    private init() {
        setupCommandCenter()
    }

    deinit {
        if let timeObserver = timeObserver {
            mainPlayer?.removeTimeObserver(timeObserver)
        }
    }

    private func setupCommandCenter() {
        let commandCenter = MPRemoteCommandCenter.shared()

        commandCenter.playCommand.addTarget { [unowned self] _ in
            self.playAudio()
            return .success
        }

        commandCenter.pauseCommand.addTarget { [unowned self] _ in
            self.pauseAudio()
            return .success
        }
    }

    func setupAudio(from url: String) {
        guard let url = URL(string: url) else {
            print("Invalid URL")
            return
        }

        let playerItem = AVPlayerItem(url: url)
        mainPlayer = AVPlayer(playerItem: playerItem)

        timeObserver = mainPlayer?.addPeriodicTimeObserver(forInterval: CMTime(seconds: 1, preferredTimescale: 1), queue: .main) { [weak self] time in
            let currentTime = CMTimeGetSeconds(time)
            self?.time = self?.formatTime(time: currentTime) ?? "00:00"
            self?.updateNowPlayingInfo()
        }

        playAudio()
    }

    func playAudio() {
        DispatchQueue.main.async {
            self.mainPlayer?.play()
            self.isPlaying = true
            self.updateNowPlayingInfo()
        }
    }

    func pauseAudio() {
        DispatchQueue.main.async {
            self.mainPlayer?.pause()
            self.isPlaying = false
        }
    }

    private func updateNowPlayingInfo() {
        let currentTime = CMTimeGetSeconds(mainPlayer?.currentTime() ?? CMTime.zero)
        time = formatTime(time: currentTime)

        var nowPlayingInfo = [String: Any]()
        nowPlayingInfo[MPMediaItemPropertyTitle] = "Your Title"
        nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = currentTime
        nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = CMTimeGetSeconds(mainPlayer?.currentItem?.duration ?? CMTime.zero)
        nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = mainPlayer?.rate ?? 0.0
        MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
    }
swift widget avaudioplayer ios17
1个回答
0
投票

如果您使用

AudioPlaybackIntent
,那么意图将使用主应用程序进程。这意味着
perform
方法中的代码将访问与主应用程序相同的
PlayerModel
实例。

您需要的是将一些数据传递给 TimelineProvider 来更新视图 - 通常

UserDefaults
就足够了。

这是一个简单的例子:

class PlayerModel {
    static let shared = PlayerModel()

    func play() {
        isPlaying = true
        // ...
    }

    func pause() {
        isPlaying = false
        // ...
    }
}
struct PlayIntent: AudioPlaybackIntent {
    static var title: LocalizedStringResource = "Play Audio"

    init() {}

    func perform() async throws -> some IntentResult {
        PlayerModel.shared.play()
        UserDefaults.appGroup.set(
            PlayerModel.shared.isPlaying,
            forKey: "isAudioPlaying"
        )
        return .result()
    }
}
struct Provider: TimelineProvider {
    func placeholder(in context: Context) -> Entry {
        .placeholder
    }

    func getSnapshot(in context: Context, completion: @escaping (Entry) -> Void) {
        completion(.placeholder)
    }

    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> Void) {
        let isPlaying = UserDefaults.appGroup.bool(forKey: "isAudioPlaying")
        let entry = Entry(isPlaying: isPlaying)
        completion(.init(entries: [entry], policy: .never))
    }
}

这里是一个 GitHub 存储库,其中包含不同的小部件示例,包括音频播放小部件。

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