您好,我对Swift中的MPMusicPlayerController
有疑问。我目前正在开发音乐应用,希望通过歌曲随机播放音乐。因此,当应用程序启动时,它基本上会设置播放队列,然后设置随机播放模式。我可以成功设置队列(并播放歌曲),但是在设置随机播放模式时出现错误:
musicPlayer.musicPlayer.shuffleMode = .songs
错误:
2018-07-03 15:01:36.450977+0200 Hitbeat[29053:8378883] [SDKPlayback] -[MPMusicPlayerController setShuffleMode:2] completed error: Error Domain=MPCPlayerRequestErrorDomain Code=1 "No commands provided." UserInfo={NSDebugDescription=No commands provided.}
那是什么意思? 我的想法可能是因为在设置shuffleMode时队列未完全设置,但我不确定,因此必须先设置歌曲队列才能设置顺序的模式是没有意义的。播放歌曲。也许还有其他问题吗?
一切都在主线程上进行。 (始终必须在主线程中调用MPMusicPlayerController)
非常感谢,希望大家能帮助我。
以下是一些代码段:
MusicPlayerManager.swift
import os.log
import MediaPlayer
import NotificationCenter
class MusicPlayerManager {
let musicPlayer: MPMusicPlayerController
lazy var musicPickerAndAdder = MusicPickerAndAdder()
init() {
// Instantiate a new music player
musicPlayer = MPMusicPlayerApplicationController.applicationQueuePlayer
// Add a playback queue containing all songs on the device
switch MPMediaLibrary.authorizationStatus() {
case .authorized:
let catalogSongStoreID: String = ""
let catalogQueueDescriptor = MPMusicPlayerStoreQueueDescriptor(storeIDs: [catalogSongStoreID])
musicPlayer.setQueue(with: catalogQueueDescriptor)
default:
break
}
Timer.scheduledTimer(withTimeInterval: 15, repeats: false) {_ in
print("shuffle mode setter")
self.musicPlayer.shuffleMode = MPMusicShuffleMode.songs
}
}
func updateOnlineMusicQueue() {
var musicPickerIds = [String]()
DispatchQueue.global(qos: .userInitiated).sync {
musicPickerIds = musicPickerAndAdder.ids
}
if !musicPickerIds.isEmpty{
musicPlayer.setQueue(with: musicPickerIds)
}else {
updateOfflineMusicQueue()
}
musicPlayer.pause()
}
func play() {
if musicPlayer.playbackState == .playing {
musicPlayer.pause()
musicPlayer.skipToBeginning()
}
if !musicPlayer.isPreparedToPlay {
musicPlayer.prepareToPlay { (error) in
if error == nil {
self.musicPlayer.play()
self.startSongMasterTimer()
}
}
}else {
musicPlayer.play()
startSongMasterTimer()
}
}
func pauseAndSkip() {
// if self.musicPlayer.shuffleMode.rawValue != 2 { // does not work here would work on pause and skip
// self.musicPlayer.shuffleMode = MPMusicShuffleMode.songs
// }
//print("shuffler \(self.musicPlayer.shuffleMode.rawValue)")
//print("At \(musicPlayer.currentPlaybackTime) of \((musicPlayer.nowPlayingItem?.playbackDuration!)")
musicPlayer.pause()
//if musicPlayer.nowPlayingItem != nil {
musicPlayer.skipToNextItem()
//}
musicPlayer.prepareToPlay { (error) in
if error == nil {
self.musicPlayer.pause()
}
}
}
func currentSongInfo() -> SongInfo {
let songTitle = musicPlayer.nowPlayingItem?.title?.replacingOccurrences(of: "-", with: " ") ?? "" // To guarantee there is only one - between Song and Artist
let artistName = musicPlayer.nowPlayingItem?.artist?.replacingOccurrences(of: "-", with: " ") ?? ""
let songInfo = SongInfo(title: songTitle, artist: artistName)
return songInfo
}
func addSongToLibrary() {
//print("Id of Item to Add: \(musicPlayer.nowPlayingItem?.playbackStoreID)")
if musicPlayer.nowPlayingItem != nil {
musicPickerAndAdder.addResourceToUserMusicLibrary(resourceId: (musicPlayer.nowPlayingItem?.playbackStoreID)!)
}
//ToDo add to myHitbeat Playlist
}
}
class SongInfo {
let title: String
let artist: String
init(title:String,artist:String) {
self.title = title
self.artist = artist
}
}
MusicPickerAndAdder.swift
import Foundation
class MusicPickerAndAdder {
lazy var authorizationManager: AuthorizationManager = {
return AuthorizationManager(appleMusicManager: self.appleMusicManager)
}()
var appleMusicManager = AppleMusicManager()
private var idsArraySize = 100
static var idCategoriesStakes = ["Chart_Ids" : 0.10,
"Recently_Played_Ids" : 0.10,
"Experiment_Ids" : 0.30,
"Recommendations_Ids" : 0.50,] // Addition of all Values must be 1 (100%)
private var chartIds: [String] {
var chartsIds = [String]()
let chartsIdsGroup = DispatchGroup()
chartsIdsGroup.enter()
let limit = Int(Double(idsArraySize) * MusicPickerAndAdder.idCategoriesStakes["Recently_Played_Ids"]!)
appleMusicManager.performAppleMusicGetChartSongs(regionCode: Locale.current.regionCode?.lowercased() ?? "us", limit: limit) { (storeIds, error) in
if error != nil {
print("There was an Error getting Charts")
chartsIdsGroup.leave()
return
}
chartsIds = storeIds
chartsIdsGroup.leave()
}
chartsIdsGroup.wait()
print("Charts sucessfully fetched")
return chartsIds
}
private var recentlyPlayedIds: [String] {
var recentIds = [String]()
let recentIdsGroup = DispatchGroup()
recentIdsGroup.enter()
let limit = Int(Double(idsArraySize) * MusicPickerAndAdder.idCategoriesStakes["Recently_Played_Ids"]!)
appleMusicManager.performAppleMusicGetRecentlyPlayed(userToken: authorizationManager.userToken, limit: limit) {
(storeIds, error) in
if error != nil {
print("There was an Error getting Recently Played")
recentIdsGroup.leave()
return
}
recentIds = storeIds
recentIdsGroup.leave()
}
recentIdsGroup.wait()
print("Recently Played sucessfully fetched: \(recentIds)")
return recentIds
}
private var experimentIds: [String] {
return ["pl.u-XkD04oZIY0Kxrl"]
}
private var recommendationsIds: [String] {
return [String]()
}
// Never request Ids in Main (UI) Thread
var ids: [String] {
var ids = [String]()
ids += recentlyPlayedIds
ids += chartIds
ids += experimentIds
ids += recommendationsIds
print("Store Ids for Songs \(ids)")
return ids.shuffled() // shuffles list of items
}
init() {
requestAppleMusicAuthorization()
}
//MARK: Private Methods
private func requestAppleMusicAuthorization() {
UserDefaults.standard.register(defaults: ["tutorial": true])
if !UserDefaults.standard.bool(forKey: "tutorial") {
authorizationManager.requestCloudServiceAuthorization()
authorizationManager.requestMediaLibraryAuthorization()
}
}
}
extension MusicPickerAndAdder { // to Add Songs
func addResourceToUserMusicLibrary(resourceId: String) {
appleMusicManager.performAddResourceToLibrary(resourceId: resourceId, userToken: authorizationManager.userToken)
}
}
extension MutableCollection {
/// Shuffles the contents of this collection.
mutating func shuffle() {
let c = count
guard c > 1 else { return }
for (firstUnshuffled, unshuffledCount) in zip(indices, stride(from: c, to: 1, by: -1)) {
// Change `Int` in the next line to `IndexDistance` in < Swift 4.1
let d: Int = numericCast(arc4random_uniform(numericCast(unshuffledCount)))
let i = index(firstUnshuffled, offsetBy: d)
swapAt(firstUnshuffled, i)
}
}
}
extension Sequence {
/// Returns an array with the contents of this sequence, shuffled.
func shuffled() -> [Element] {
var result = Array(self)
result.shuffle()
return result
}
}
PS:MusicPickerAndAdder看起来有些混乱,尽管我认为问题不在于此!它的基本作用是从Apple Music API中获取一些可以正常工作的数据,并将歌曲添加到也可以工作的用户库中。</ p>
答案 0 :(得分:0)
好吧,在尝试了一切可能之后,我想出了两个对我有用的解决方案。奇怪的是,我发现只有当到目前为止没有歌曲播放时,才会发生界面冻结。如果当前正在播放一首歌曲,或者即使歌曲已播放并且随后被暂停,也不会冻结用户界面。所以我想出了这个功能:
private func setShuffleMode() { // does work though startup and restarting takes longer
musicPlayer.play()
Timer.scheduledTimer(withTimeInterval: 1.5, repeats: false) {_ in
print("shuffle mode setter")
self.musicPlayer.pause()
//self.musicPlayer.pause()// may stop interface freezing if occuring
self.musicPlayer.shuffleMode = MPMusicShuffleMode.songs // freeze of ui only occurs when no song played before
}
}
我尝试了几个时间间隔,但有时仍然失败,即使是一秒钟,它在1.5秒内从未失败,所以我把它留在那里了。
问题是,启动和重新启动应用程序需要花费更长的时间。所以我想出了第二种解决方案
private func setShuffleMode2 () { // still in test if shuffle mode gets set fast or even ever set
Timer.scheduledTimer(withTimeInterval: 5, repeats: true) {timer in
if self.musicPlayer.playbackState == .playing && self.musicPlayer.currentPlaybackTime > 3{
self.musicPlayer.shuffleMode = .songs
print("shuffle mode setter")
timer.invalidate()
}
}
}
在这里,我有一个重复计时器,该计时器始终检查项目是否正在播放以及是否已经播放了一定时间,如果设置了随机播放模式并停止重复播放。我已经测试了第二个函数,尽管有一个折衷方案,但总有一段时间没有调用它,所以它很好用。多长时间取决于时间间隔和currentPlaybackTime> someTime值。