web-dev-qa-db-ja.com

MPNowPlayingInfoCenternowPlayingInfoがトラックの終わりで更新されない

アプリの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().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アップデートはどのような条件下でも期待どおりに機能します。

改訂された質問:これら2つのコードは、両方ともメインキューで実行されている場合、どのように相互作用しますか? nowPlayingInfoに呼び出しがあるとジャンプすることを考えると、AVPlayerItemTimeJumpedNotificationsetTrackNumber更新を行う方法はありますか?

13
Hélène Martin

問題は、トラックが自動的に変更されるときに、nowPlayingInfoが2つの場所で同時に更新されることです。setTrackNumberによってトリガーされるAVPlayerItemDidPlayToEndTimeNotificationメソッドと、playerTimeJumpedによってトリガーされるAVPlayerItemTimeJumpedNotificationメソッドです。

これにより、競合状態が発生します。詳細はAppleスタッフメンバー ここ

この問題は、必要に応じて更新されるローカルnowPlayingInfoディクショナリを保持し、個々の値を設定する代わりに、常にそこからMPNowPlayingInfoCenter.defaultCenter().nowPlayingInfoを設定することで解決できます。

10
Hélène Martin

背景情報の更新について、私の同僚はいくつかの実装が必要であると提案しています。たぶん、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")
        }
    }
1
Allen

このコードを試していただけませんか?これは私の例ではうまくいきました...

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.")
}

ここでも、上記と同じように、アクティブにしようとしています。これは動作しない可能性のある古いバージョンです...

1
Tom el Safadi

まず、.plistファイルでアプリのバックグラウンドモードを確実に有効にします。これにより、アプリはバックグラウンドタスクを使用し、ロックされた状態で更新コードを実行できるようになります。

次に、適切なタイミングで更新する場合は、AVAudioPlayerデリゲート関数audioPlayerDidFinishPlaying:successfully:によって更新関数が呼び出されるようにします。また、代わりに終了の通知を登録してみることもできます。

1
rikola

上記の回答についてコメントすることはできませんが、MPRemoteCommandCenterを使用する場合、リモートイベントを処理するために-[UIApplication beginReceivingRemoteControlEvents]または-[UIResponder becomeFirstResponder]を呼び出す必要はありません。上記の回答は、推奨されなくなった古い実装を参照しています。

NowPlayingInfoディクショナリにできるだけ多くのキーを設定することをお勧めします。 MPMediaItemPropertyPlaybackDurationMPNowPlayingInfoPropertyPlaybackRate、およびMPNowPlayingInfoPropertyElapsedPlaybackTimeは、MPNowPlayingInfoCenterが更新されるかどうかに影響を与える可能性があります。

0
xianwenx