无法使用 MPMusicPlayerMediaItemQueueDescriptor 设置 MPMusicPlayerController 队列

Can't set MPMusicPlayerController queue with MPMusicPlayerMediaItemQueueDescriptor

这段代码导致沉默:

let query = MPMediaQuery.songs()
let result = query.items
guard let items = result, items.count > 0 else {return}
let song = items[0]

let player = MPMusicPlayerController.applicationQueuePlayer

let coll = MPMediaItemCollection(items: [song])
let q = MPMusicPlayerMediaItemQueueDescriptor(itemCollection: coll)
player.setQueue(with: q)
player.play()

我已逐步完成代码,我们到达 player.play()。我有一个 MPMediaItem,我已经正确地从中形成了一个 MPMediaItemCollection,从 that 中形成了一个 MPMusicPlayerMediaItemQueueDescriptor。那为什么我的播放器不播放?

您发现了一个错误。初始化器 MPMusicPlayerMediaItemQueueDescriptor(itemCollection:) 似乎完全损坏:它导致队列描述符不可用。

解决方法是尽可能使用其他初始化程序,即 MPMusicPlayerMediaItemQueueDescriptor(query:)

例如,在这种情况下,您可以编写(在原始代码的最后四行中提取):

let coll = MPMediaItemCollection(items: [song])
let predicate = MPMediaPropertyPredicate(
                value: song.persistentID,
                forProperty: MPMediaItemPropertyPersistentID)
let query = MPMediaQuery(filterPredicates: [predicate])
let q = MPMusicPlayerMediaItemQueueDescriptor(query: query)
player.setQueue(with: q)
player.play()

遗憾的是,在许多情况下,您无法形成单个查询来获取您真正想要的 MPMediaItemCollection。

在这个特定示例中,您也可以通过直接使用 MPMediaItemCollection 设置播放器队列而不是从 MPMediaItemCollection 制作的 MPMusicPlayerMediaItemQueueDescriptor 来解决这个问题。

但是,有些命令(例如 append(_:)需要 MPMusicPlayerMediaItemQueueDescriptor,对于类似的事情,整个 API 是基本上软管。它从 iOS 10.1 开始就被清理了,并且在 iOS 11.1 中仍然被清理。

"It has been hosed since iOS 10.1 and it remains hosed in iOS 11.1." 借调。

我一直在进行一场旷日持久的斗争,只是试图 a) 让 MPMusicPlayerController 简单地工作(终于成功了)和 b) 让它从偏移量播放歌曲(仍然没有)。这是在 iOS 10 之前按预期 100% 工作的应用程序。

现在,这是适合我的代码(至少在播放歌曲方面):

let currentPlayer = MPMusicPlayerController.applicationQueuePlayer()
currentPlayer.repeatMode = .none

self.currentCollection = MPMediaItemCollection(items: [mediaItem])
currentPlayer.beginGeneratingPlaybackNotifications()

let query = MPMediaQuery.songs()
let pred = MPMediaPropertyPredicate(value: mediaItem.persistentID, forProperty: MPMediaItemPropertyPersistentID)
query.addFilterPredicate(pred)
let desc = MPMusicPlayerMediaItemQueueDescriptor(query: query)
currentPlayer.setQueueWith(desc)

currentPlayer.prepareToPlay(completionHandler: {[unowned self] (error) in
    MPMusicPlayerController.applicationQueuePlayer().play()
})

这有点令人费解,因为我不得不使用我认为基于可用 API 的最复杂的方法。但是其他几种方法导致完成块永远不会被调用或得到一个完全无用的错误。现在我有 5 个关于 MPMusicPlayerController 的错误报告给 Apple,所以我现在不是一个快乐的开发者。我将再打开一个与此代码相关的代码,因为 setStartTime 和 setEndTime 在以指定偏移量开始播放方面似乎并不比 currentPlaybackTime 更好。

尽管如此,此代码确实可以可靠地播放同步和下载的 Apple Music 项目。

Swift 5

我来晚了一点,但这就是我想出的。 (我正在修剪歌曲以使用描述符播放)。

确保您 运行 在主线程上使用音乐播放器的每一件事。否则,您将 运行 陷入奇怪的错误。

通过 persistentId 获取歌曲:

/// Retrieves a song in the MPMediaItem format using the persistentId passed.
/// - Parameter persistentID: (UInt64) The songs persistentId.
/// - Returns: (MPMediaItem?) Nil if not found other wise the MPMediaItem (song).
private static func getSong(forId persistentID: UInt64) -> MPMediaItem? {
    let query = MPMediaQuery.songs()
    let predicate = MPMediaPropertyPredicate(value: persistentID, forProperty: MPMediaItemPropertyPersistentID)
    query.addFilterPredicate(predicate)
    return query.items?.first
}

正在按 persistentId 播放歌曲:

private var startTime: Double = 0
private var endTime: Double = 0


/// Plays the song with the identifier.
/// - Note: Trims song to start and end time.
/// - Parameter persistentId: (UInt64) The song persistent identifier.
private func play(forId persistentId: UInt64) {
    DispatchQueue.main.async {
        if let song = Self.getSong(forId: persistentId) {
            let identifier = MPMediaItemPropertyPersistentID
            let predicate = MPMediaPropertyPredicate(value: persistentId, forProperty: identifier)
            let query = MPMediaQuery(filterPredicates: [predicate])
            let descriptor = MPMusicPlayerMediaItemQueueDescriptor(query: query)
            descriptor.setStartTime(self.startTime, for: song)
            descriptor.setEndTime(self.endTime, for: song)
            self.musicPlayer.setQueue(with: descriptor)
            self.musicPlayer.prepareToPlay()
            self.musicPlayer.repeatMode = .none
            self.musicPlayer.play()
        }
    }
}

我还发现初始化音乐控制器的方式很重要:

private lazy var musicPlayer: MPMusicPlayerController = { MPMusicPlayerController.applicationQueuePlayer }()

请注意:部分代码不可拖放。您需要定义诸如开始和结束时间变量之类的内容。