MPNowPlayingInfoCenter nowPlayingInfo 在曲目结束时不更新
MPNowPlayingInfoCenter nowPlayingInfo not updating at end of track
我有一种方法可以更改我的应用 AVPlayer
播放的音轨,并为新曲目设置 MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo
:
func setTrackNumber(trackNum: Int) {
self.trackNum = trackNum
player.replaceCurrentItemWithPlayerItem(tracks[trackNum])
var nowPlayingInfo: [String: AnyObject] = [ : ]
nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = tracks[trackNum].albumTitle
nowPlayingInfo[MPMediaItemPropertyTitle] = "Track \(trackNum)"
...
MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo = nowPlayingInfo
print("Now playing local: \(nowPlayingInfo)")
print("Now playing lock screen: \(MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo)")
}
当用户明确选择专辑或曲目以及曲目结束而下一首自动开始时,我会调用此方法。当用户设置专辑或曲目时,锁定屏幕会正确显示曲目元数据,但不会在曲目结束并自动设置下一曲目时显示。
我添加了打印语句以确保我正确地填充了 nowPlayingInfo
字典。正如预期的那样,当为用户启动的专辑或曲目更改调用此方法时,两个打印语句打印相同的字典内容。但是,在自动跟踪更改后调用该方法的情况下,本地 nowPlayingInfo
变量显示新的 trackNum
而 MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo
显示先前的 trackNum
:
Now playing local: ["title": Track 9, "albumTitle": Test Album, ...]
Now playing set: Optional(["title": Track 8, "albumTitle": Test Album, ...]
我发现当我在 MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo
到 nowPlayingInfo
的行上设置断点时,曲目编号会在锁定屏幕上正确更新。在该行之后添加 sleep(1)
还可以确保正确更新锁定屏幕上的曲目。
我已经验证 nowPlayingInfo
总是从主队列设置。我已经明确地 运行 在主队列或不同的队列中尝试使用此代码,但行为没有变化。
是什么阻碍了我对 MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo
的更改?如何确保设置 MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo
始终更新锁定屏幕信息?
编辑
在第 N 遍思考 "concurrency" 代码后,我找到了罪魁祸首。我不知道为什么我没有早点对此产生怀疑:
func playerTimeJumped() {
let currentTime = currentItem().currentTime()
dispatch_async(dispatch_get_main_queue()) {
MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo?[MPNowPlayingInfoPropertyElapsedPlaybackTime] = CMTimeGetSeconds(currentTime)
}
}
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: "playerTimeJumped",
name: AVPlayerItemTimeJumpedNotification,
object: nil)
当用户滑动或跳过时,此代码更新锁定屏幕的经过时间 forward/back。如果我将其注释掉,setTrackNumber
的 nowPlayingInfo
更新在任何情况下都会按预期工作。
修改后的问题:当这两段代码都运行在主队列中时它们是如何交互的?有什么办法可以在 AVPlayerItemTimeJumpedNotification
上进行 nowPlayingInfo
更新,因为在 setTrackNumber
上有呼叫时会有跳转?
对于背景信息更新,我的同事建议有必要进行一些实施。也许您可以在视图控制器中检查并验证其中一些要求:
//1: Set true for canBecomeFirstResponder func
override func canBecomeFirstResponder() -> Bool {
return true
}
//2: Set view controller becomeFirstResponder & allow to receive remote control events
override func viewDidLoad() {
super.viewDidLoad()
self.becomeFirstResponder()
UIApplication.sharedApplication().beginReceivingRemoteControlEvents()
....
}
//3: Implement actions after did receive events from remote control
override func remoteControlReceivedWithEvent(event: UIEvent?) {
guard let event = event else {
return
}
switch event.subtype {
case .RemoteControlPlay:
....
break
case .RemoteControlPause:
....
break
case .RemoteControlStop:
....
break
default:
print("default action")
}
}
我无法对上述答案发表评论,但使用 MPRemoteCommandCenter 时,无需调用 -[UIApplication beginReceivingRemoteControlEvents]
或 -[UIResponder becomeFirstResponder]
来处理远程事件。以上答案指的是不再推荐的旧实现。
建议在nowPlayingInfo字典中设置尽可能多的键。 MPMediaItemPropertyPlaybackDuration
、MPNowPlayingInfoPropertyPlaybackRate
和 MPNowPlayingInfoPropertyElapsedPlaybackTime
会影响 MPNowPlayingInfoCenter 是否更新。
你能试试这个代码吗?这适用于我的示例...
override func viewDidLoad() {
super.viewDidLoad()
if NSClassFromString("MPNowPlayingInfoCenter") != nil {
let albumArt = MPMediaItemArtwork(image: image) // any image
var songInfo: NSMutableDictionary = [
MPMediaItemPropertyTitle: "Whatever",
MPMediaItemPropertyArtist: "Whatever",
MPMediaItemPropertyArtwork: albumArt
]
MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo = songInfo as [NSObject : AnyObject]
}
try! AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, withOptions: [])
try! AVAudioSession.sharedInstance().setActive(true)
}
说明:
您必须检查 MPNowPlayingInfo 是否处于活动状态,因为它有时会进入后台。如果它在后台,那么你必须激活它来执行这行代码:
try! AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, withOptions: [])
try! AVAudioSession.sharedInstance().setActive(true)
如果有效请写信给我...
编辑
如果这不起作用,您也可以尝试此代码,但上面的代码是更现代的解决方案
if (AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, error: nil)) {
println("Receiving remote control events")
UIApplication.sharedApplication().beginReceivingRemoteControlEvents()
} else {
println("Audio Session error.")
}
这里也是尝试激活,同上。这是旧版本,可能无法使用...
首先,一定要在 .plist 文件中为您的应用启用后台模式。这将允许您的应用在锁定时使用后台任务和 运行 您的更新代码。
其次,如果您想在适当的时间更新它,我会让 AVAudioPlayer 委托函数调用更新函数 audioPlayerDidFinishPlaying:successfully:
。您也可以尝试注册一个已完成的通知作为替代。
问题是nowPlayingInfo
在轨道自动改变时同时在两个地方更新:在setTrackNumber
方法中,由AVPlayerItemDidPlayToEndTimeNotification
触发,在playerTimeJumped
方法由 AVPlayerItemTimeJumpedNotification
.
触发
这会导致竞争条件。 Apple 工作人员 here.
提供了更多详细信息
这个问题可以通过保留本地 nowPlayingInfo
字典来解决,该字典会根据需要进行更新,并始终根据该字典设置 MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo
而不是单独设置值。
这是我的案例:
AVAudioSession
很重要。
不是这个:
let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(AVAudioSession.Category.playback, options: AVAudioSession.CategoryOptions.mixWithOthers)
audioSession.requestRecordPermission({ (isGranted: Bool) in })
try AVAudioSession.sharedInstance().setActive(true, options: AVAudioSession.SetActiveOptions.notifyOthersOnDeactivation)
} catch {
}
没用
但是
do {
//keep alive audio at background
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback)
} catch _ { }
do {
try AVAudioSession.sharedInstance().setActive(true)
} catch _ { }
有效
我有一种方法可以更改我的应用 AVPlayer
播放的音轨,并为新曲目设置 MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo
:
func setTrackNumber(trackNum: Int) {
self.trackNum = trackNum
player.replaceCurrentItemWithPlayerItem(tracks[trackNum])
var nowPlayingInfo: [String: AnyObject] = [ : ]
nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = tracks[trackNum].albumTitle
nowPlayingInfo[MPMediaItemPropertyTitle] = "Track \(trackNum)"
...
MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo = nowPlayingInfo
print("Now playing local: \(nowPlayingInfo)")
print("Now playing lock screen: \(MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo)")
}
当用户明确选择专辑或曲目以及曲目结束而下一首自动开始时,我会调用此方法。当用户设置专辑或曲目时,锁定屏幕会正确显示曲目元数据,但不会在曲目结束并自动设置下一曲目时显示。
我添加了打印语句以确保我正确地填充了 nowPlayingInfo
字典。正如预期的那样,当为用户启动的专辑或曲目更改调用此方法时,两个打印语句打印相同的字典内容。但是,在自动跟踪更改后调用该方法的情况下,本地 nowPlayingInfo
变量显示新的 trackNum
而 MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo
显示先前的 trackNum
:
Now playing local: ["title": Track 9, "albumTitle": Test Album, ...]
Now playing set: Optional(["title": Track 8, "albumTitle": Test Album, ...]
我发现当我在 MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo
到 nowPlayingInfo
的行上设置断点时,曲目编号会在锁定屏幕上正确更新。在该行之后添加 sleep(1)
还可以确保正确更新锁定屏幕上的曲目。
我已经验证 nowPlayingInfo
总是从主队列设置。我已经明确地 运行 在主队列或不同的队列中尝试使用此代码,但行为没有变化。
是什么阻碍了我对 MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo
的更改?如何确保设置 MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo
始终更新锁定屏幕信息?
编辑
在第 N 遍思考 "concurrency" 代码后,我找到了罪魁祸首。我不知道为什么我没有早点对此产生怀疑:
func playerTimeJumped() {
let currentTime = currentItem().currentTime()
dispatch_async(dispatch_get_main_queue()) {
MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo?[MPNowPlayingInfoPropertyElapsedPlaybackTime] = CMTimeGetSeconds(currentTime)
}
}
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: "playerTimeJumped",
name: AVPlayerItemTimeJumpedNotification,
object: nil)
当用户滑动或跳过时,此代码更新锁定屏幕的经过时间 forward/back。如果我将其注释掉,setTrackNumber
的 nowPlayingInfo
更新在任何情况下都会按预期工作。
修改后的问题:当这两段代码都运行在主队列中时它们是如何交互的?有什么办法可以在 AVPlayerItemTimeJumpedNotification
上进行 nowPlayingInfo
更新,因为在 setTrackNumber
上有呼叫时会有跳转?
对于背景信息更新,我的同事建议有必要进行一些实施。也许您可以在视图控制器中检查并验证其中一些要求:
//1: Set true for canBecomeFirstResponder func
override func canBecomeFirstResponder() -> Bool {
return true
}
//2: Set view controller becomeFirstResponder & allow to receive remote control events
override func viewDidLoad() {
super.viewDidLoad()
self.becomeFirstResponder()
UIApplication.sharedApplication().beginReceivingRemoteControlEvents()
....
}
//3: Implement actions after did receive events from remote control
override func remoteControlReceivedWithEvent(event: UIEvent?) {
guard let event = event else {
return
}
switch event.subtype {
case .RemoteControlPlay:
....
break
case .RemoteControlPause:
....
break
case .RemoteControlStop:
....
break
default:
print("default action")
}
}
我无法对上述答案发表评论,但使用 MPRemoteCommandCenter 时,无需调用 -[UIApplication beginReceivingRemoteControlEvents]
或 -[UIResponder becomeFirstResponder]
来处理远程事件。以上答案指的是不再推荐的旧实现。
建议在nowPlayingInfo字典中设置尽可能多的键。 MPMediaItemPropertyPlaybackDuration
、MPNowPlayingInfoPropertyPlaybackRate
和 MPNowPlayingInfoPropertyElapsedPlaybackTime
会影响 MPNowPlayingInfoCenter 是否更新。
你能试试这个代码吗?这适用于我的示例...
override func viewDidLoad() {
super.viewDidLoad()
if NSClassFromString("MPNowPlayingInfoCenter") != nil {
let albumArt = MPMediaItemArtwork(image: image) // any image
var songInfo: NSMutableDictionary = [
MPMediaItemPropertyTitle: "Whatever",
MPMediaItemPropertyArtist: "Whatever",
MPMediaItemPropertyArtwork: albumArt
]
MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo = songInfo as [NSObject : AnyObject]
}
try! AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, withOptions: [])
try! AVAudioSession.sharedInstance().setActive(true)
}
说明: 您必须检查 MPNowPlayingInfo 是否处于活动状态,因为它有时会进入后台。如果它在后台,那么你必须激活它来执行这行代码:
try! AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, withOptions: [])
try! AVAudioSession.sharedInstance().setActive(true)
如果有效请写信给我...
编辑
如果这不起作用,您也可以尝试此代码,但上面的代码是更现代的解决方案
if (AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, error: nil)) {
println("Receiving remote control events")
UIApplication.sharedApplication().beginReceivingRemoteControlEvents()
} else {
println("Audio Session error.")
}
这里也是尝试激活,同上。这是旧版本,可能无法使用...
首先,一定要在 .plist 文件中为您的应用启用后台模式。这将允许您的应用在锁定时使用后台任务和 运行 您的更新代码。
其次,如果您想在适当的时间更新它,我会让 AVAudioPlayer 委托函数调用更新函数 audioPlayerDidFinishPlaying:successfully:
。您也可以尝试注册一个已完成的通知作为替代。
问题是nowPlayingInfo
在轨道自动改变时同时在两个地方更新:在setTrackNumber
方法中,由AVPlayerItemDidPlayToEndTimeNotification
触发,在playerTimeJumped
方法由 AVPlayerItemTimeJumpedNotification
.
这会导致竞争条件。 Apple 工作人员 here.
提供了更多详细信息这个问题可以通过保留本地 nowPlayingInfo
字典来解决,该字典会根据需要进行更新,并始终根据该字典设置 MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo
而不是单独设置值。
这是我的案例:
AVAudioSession
很重要。
不是这个:
let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(AVAudioSession.Category.playback, options: AVAudioSession.CategoryOptions.mixWithOthers)
audioSession.requestRecordPermission({ (isGranted: Bool) in })
try AVAudioSession.sharedInstance().setActive(true, options: AVAudioSession.SetActiveOptions.notifyOthersOnDeactivation)
} catch {
}
没用
但是
do {
//keep alive audio at background
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback)
} catch _ { }
do {
try AVAudioSession.sharedInstance().setActive(true)
} catch _ { }
有效