アプリの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
辞書に正しく入力されていることを確認するために、printステートメントを追加しました。予想どおり、ユーザーが開始したアルバムまたはトラックの変更に対してこのメソッドが呼び出されると、2つのprintステートメントは同じ辞書コンテンツを印刷します。ただし、自動トラック変更後にメソッドが呼び出された場合、ローカルの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
からのsetTrackNumber
アップデートはどのような条件下でも期待どおりに機能します。
改訂された質問:これら2つのコードは、両方ともメインキューで実行されている場合、どのように相互作用しますか? nowPlayingInfo
に呼び出しがあるとジャンプすることを考えると、AVPlayerItemTimeJumpedNotification
でsetTrackNumber
更新を行う方法はありますか?
問題は、トラックが自動的に変更されるときに、nowPlayingInfo
が2つの場所で同時に更新されることです。setTrackNumber
によってトリガーされるAVPlayerItemDidPlayToEndTimeNotification
メソッドと、playerTimeJumped
によってトリガーされるAVPlayerItemTimeJumpedNotification
メソッドです。
これにより、競合状態が発生します。詳細はAppleスタッフメンバー ここ 。
この問題は、必要に応じて更新されるローカルnowPlayingInfo
ディクショナリを保持し、個々の値を設定する代わりに、常にそこからMPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo
を設定することで解決できます。
背景情報の更新について、私の同僚はいくつかの実装が必要であると提案しています。たぶん、ViewControllerでこれらの要件のいくつかをチェックして確認することができます。
//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")
}
}
このコードを試していただけませんか?これは私の例ではうまくいきました...
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:
によって更新関数が呼び出されるようにします。また、代わりに終了の通知を登録してみることもできます。
上記の回答についてコメントすることはできませんが、MPRemoteCommandCenterを使用する場合、リモートイベントを処理するために-[UIApplication beginReceivingRemoteControlEvents]
または-[UIResponder becomeFirstResponder]
を呼び出す必要はありません。上記の回答は、推奨されなくなった古い実装を参照しています。
NowPlayingInfoディクショナリにできるだけ多くのキーを設定することをお勧めします。 MPMediaItemPropertyPlaybackDuration
、MPNowPlayingInfoPropertyPlaybackRate
、およびMPNowPlayingInfoPropertyElapsedPlaybackTime
は、MPNowPlayingInfoCenterが更新されるかどうかに影響を与える可能性があります。