MPNowPlayingInfoCenter nowPlayingInfo在轨道末尾不更新

时间:2016-01-19 01:56:15

标签: ios swift mpnowplayinginfocenter

我有一种方法可以更改我的应用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}}更新会在任何条件下按预期运行。

修改过的问题:当这两段代码在主队列上运行时,它们如何交互?我有什么方法可以setTrackNumbernowPlayingInfo进行AVPlayerItemTimeJumpedNotification更新,因为setTrackNumber上的通话会有跳跃吗?

6 个答案:

答案 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字典中设置尽可能多的键。 MPMediaItemPropertyPlaybackDurationMPNowPlayingInfoPropertyPlaybackRateMPNowPlayingInfoPropertyElapsedPlaybackTime会影响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 _ { }

有效