我有一种方法可以更改我的应用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)")
}
当用户明确选择专辑或曲目并且曲目结束并且下一曲目自动开始时,我会调用此方法。锁定屏幕在用户设置专辑或曲目时正确显示曲目元数据,但在曲目结束且下一曲目自动设置时不正确显示。
我添加了print语句以确保我正确填充了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次思考"并发"的代码之后,我找到了罪魁祸首。我不知道为什么我之前没有对此表示怀疑:
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)
此代码更新锁定屏幕用户擦洗或向前/向后跳过的时间。如果我对其进行评论,则nowPlayingInfo
的{{1}}更新会在任何条件下按预期运行。
修改过的问题:当这两段代码在主队列上运行时,它们如何交互?我有什么方法可以setTrackNumber
对nowPlayingInfo
进行AVPlayerItemTimeJumpedNotification
更新,因为setTrackNumber
上的通话会有跳跃吗?
答案 0 :(得分:9)
问题是,在跟踪自动更改的同时,nowPlayingInfo
会在两个位置同时更新:setTrackNumber
方法由AVPlayerItemDidPlayToEndTimeNotification
和{{1}触发由playerTimeJumped
触发的方法。
这会导致竞争状况。更多详细信息由Apple员工here提供。
问题可以通过保留根据需要更新的本地AVPlayerItemTimeJumpedNotification
字典并始终从中设置nowPlayingInfo
而不是设置单个值来解决。
答案 1 :(得分:1)
对于背景信息更新,我的同事建议一些实施是必要的。也许您可以在视图控制器中检查并验证其中一些要求:
//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")
}
}
答案 2 :(得分:1)
你能试试这段代码吗?这适用于我的例子......
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.")
}
在这里,您也尝试将其激活,与上述相同。这是旧版本,可能无法正常工作......
答案 3 :(得分:1)
首先,确保在.plist文件中为您的应用启用后台模式。这将允许您的应用使用后台任务并在锁定时运行更新代码。
其次,如果你想在适当的时候更新它,我会让AVAudioPlayer委托函数 // That other party, the receiver, can then use JsonWebEncryption to decrypt the message.
JsonWebEncryption receiverJwe = new JsonWebEncryption();
// Set the compact serialization on new Json Web Encryption object
//This is the received payload JWE payload
receiverJwe.setCompactSerialization(result.toString());
// Symmetric encryption, like we are doing here, requires that both parties have the same key.
// The key will have had to have been securely exchanged out-of-band somehow.
receiverJwe.setKey(secretKeySpec);
// Set the "alg" header, which indicates the key management mode for this JWE.
// In this example we are using the direct key management mode, which means
// the given key will be used directly as the content encryption key.
//receiverJwe.setAlgorithmHeaderValue(KeyManagementAlgorithmIdentifiers.DIRECT);
//receiverJwe.setEncryptionMethodHeaderParameter(ContentEncryptionAlgorithmIdentifiers.AES_128_CBC_HMAC_SHA_256);
// Get the message that was encrypted in the JWE. This step performs the actual decryption steps.
String jwsPayload = receiverJwe.getPlaintextString();
// And do whatever you need to do with the clear text message.
System.out.println("plaintext: " + jwsPayload);
// Create a new JsonWebSignature object
JsonWebSignature jws = new JsonWebSignature();
jws.setCompactSerialization(jwsPayload);
jws.setKey(secretKeySpec);
boolean signatureVerified = jws.verifySignature();
// Do something useful with the result of signature verification
System.out.println("JWS Signature is valid: " + signatureVerified);
// Get the payload, or signed content, from the JWS
String payload = jws.getPayload();
// Do something useful with the content
System.out.println("JWS payload: " + payload);
调用更新函数。您也可以尝试注册作为替代方案完成的通知。
答案 4 :(得分:0)
我无法对上述答案发表评论,但在使用MPRemoteCommandCenter时,无需调用-[UIApplication beginReceivingRemoteControlEvents]
或-[UIResponder becomeFirstResponder]
来处理远程事件。上面的答案指的是不再推荐的旧实现。
建议在nowPlayingInfo字典中设置尽可能多的键。 MPMediaItemPropertyPlaybackDuration
,MPNowPlayingInfoPropertyPlaybackRate
和MPNowPlayingInfoPropertyElapsedPlaybackTime
会影响MPNowPlayingInfoCenter是否更新。
答案 5 :(得分:0)
这是我的情况:
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 _ { }
有效