3

I have a simple MIDI player (Swift 5, SwiftUI, iOS14) and would like to toggle a play/pause command with MPRemoteCommandCenter. The control from the lock screen works fine with the following setup, but I'm struggling to find how to notify MPRemoteCommandCenter of my player's state when play/pause is toggled from inside the app and not the lock screen. Playing or pausing from within my app doesn't change the play/pause icon on the lock screen, however I can stop the displayed playback position and progress on the lock screen by setting the playback rate to 0. In other words, the only issue is that the play/pause icon on the lock screen is out of sync.

I feel like I should be emitting a notification here, but I'm not finding the documentation. I have a similar audio media player that uses AVAudioPlayer and this just magically works, and I'm guessing AVAudioPlayer is spitting out the required notifications. Does anyone have a tutorial or advice on how to do this with my own player that doesn't use AVAudioPlayer? Thanks.

//  ContentView.swift

import SwiftUI
import AudioKit
import MediaPlayer

struct ContentView: View {
    let engine: AudioEngine
    let sampler: MIDISampler
    let sequencer: AppleSequencer
    @State var playing: Bool = false

    init() {
        self.engine = AudioEngine()
        self.sampler = MIDISampler()
        self.sequencer = AppleSequencer()
        let url = Bundle.main.url(forResource: "mytrack", withExtension: "mid")!
        sequencer.loadMIDIFile(fromURL: url)
        sequencer.setGlobalMIDIOutput(sampler.midiIn)
        engine.output = sampler
        do {
            try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback)
            try AVAudioSession.sharedInstance().setActive(true)
            try engine.start()
        } catch let error {
            print(error)
        }
        
    }

    var body: some View {
        Text("Play a MIDI file")
            .padding()

        Button(action: {
            self.togglePlayPause()
        }) {
            if playing {
                Text("Pause")
            } else {
                Text("Play")
            }
        }.onAppear {
            self.setupRemoteTransportControls()
        }
    }

    func togglePlayPause() {
        if playing {
            sequencer.stop()
        } else {
            sequencer.play()
        }
        self.playing = playing ? false : true
        self.setupNowPlayingInfo()
    }

    func setupNowPlayingInfo() {
        var nowPlayingInfo = [String: Any]()
        nowPlayingInfo[MPMediaItemPropertyTitle] = "My track"
        nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = sequencer.currentPosition.seconds
        nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = sequencer.length.seconds
        nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = NSNumber(value: self.playing ? 1 : 0)
        MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
    }

    func setupRemoteTransportControls() {
        let commandCenter = MPRemoteCommandCenter.shared()

        commandCenter.togglePlayPauseCommand.addTarget { [self] _ in
            self.togglePlayPause()
            return .success
        }

        commandCenter.togglePlayPauseCommand.isEnabled = true
    }

}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
Jonathan
  • 61
  • 4

0 Answers0