14

我有一种方法可以更改我的应用程序播放的音轨AVPlayerMPNowPlayingInfoCenter.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().nowPlayingInfonowPlayingInfo轨道号会在锁定屏幕上正确更新。在该行之后添加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更新setTrackNumber在任何情况下都会按预期工作。

修改后的问题:当这两段代码都在主队列上运行时,它们是如何交互的?有什么办法可以nowPlayingInfo更新,AVPlayerItemTimeJumpedNotification因为当有电话时会有跳跃setTrackNumber

4

6 回答 6

11

问题是nowPlayingInfo在track自动改变时同时在两个地方更新:在setTrackNumber触发AVPlayerItemDidPlayToEndTimeNotification的方法中和触发的playerTimeJumped方法中AVPlayerItemTimeJumpedNotification

这会导致竞争条件。更多详细信息由 Apple 工作人员在此处提供。

该问题可以通过保持本地nowPlayingInfo字典来解决,该字典根据需要进行更新并始终MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo从该字典进行设置,而不是设置单个值。

于 2016-02-12T12:03:11.140 回答
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.")
}

在这里,您还尝试使其处于活动状态,与上述相同。这是旧版本,可能无法使用...

于 2016-02-08T00:49:01.337 回答
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")
        }
    }
于 2016-02-04T13:30:53.543 回答
1

首先,一定要在 .plist 文件中为您的应用启用后台模式。这将允许您的应用程序使用后台任务并在锁定时运行您的更新代码。

audioPlayerDidFinishPlaying:successfully:其次,如果您想在适当的时间更新它,我会让 AVAudioPlayer 委托函数调用更新函数。您也可以尝试注册一个通知,它已完成作为替代。

于 2016-02-10T22:03:25.260 回答
0

我无法对上述答案发表评论,但是在使用 MPRemoteCommandCenter 时,无需调用-[UIApplication beginReceivingRemoteControlEvents]-[UIResponder becomeFirstResponder]处理远程事件。上面的答案是指不再推荐的旧实现。

建议在 nowPlayingInfo 字典中设置尽可能多的键。MPMediaItemPropertyPlaybackDuration, MPNowPlayingInfoPropertyPlaybackRate, 和MPNowPlayingInfoPropertyElapsedPlaybackTime可以影响 MPNowPlayingInfoCenter 是否更新。

于 2016-02-07T22:09:19.100 回答
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 _ { }

有用

于 2020-05-11T05:47:05.863 回答