Rockbox open source high quality audio player as a Music Player Daemon
mpris rockbox mpd libadwaita audio rust zig deno
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Enable media key handling via silent audio

Play a loop of inaudible audio with AVAudioEngine so macOS
marks the app as Now Playing and MPRemoteCommandCenter receives
media key events. Add onTogglePlayPause callback and wire it to
PlayerState.

+47 -11
macos/Rockbox.xcodeproj/project.xcworkspace/xcuserdata/tsirysandratraina.xcuserdatad/UserInterfaceState.xcuserstate

This is a binary file and will not be displayed.

+43 -10
macos/Rockbox/Manager/MediaControlsManager.swift
··· 6 6 // 7 7 import MediaPlayer 8 8 import AppKit 9 + import AVFoundation 9 10 10 11 class MediaControlsManager { 11 - 12 + 12 13 static let shared = MediaControlsManager() 13 - 14 + 14 15 var onPlay: (() -> Void)? 15 16 var onPause: (() -> Void)? 17 + var onTogglePlayPause: (() -> Void)? 16 18 var onNext: (() -> Void)? 17 19 var onPrevious: (() -> Void)? 18 20 var onSeek: ((TimeInterval) -> Void)? 19 - 21 + 22 + private var audioEngine: AVAudioEngine? 23 + private var playerNode: AVAudioPlayerNode? 24 + 20 25 private init() { 26 + setupSilentAudio() 21 27 setupRemoteCommandCenter() 22 28 } 23 - 29 + 30 + // Playing silent audio makes macOS designate this app as the active 31 + // "Now Playing" app so that MPRemoteCommandCenter receives media key events. 32 + private func setupSilentAudio() { 33 + let engine = AVAudioEngine() 34 + let player = AVAudioPlayerNode() 35 + engine.attach(player) 36 + 37 + let format = AVAudioFormat(standardFormatWithSampleRate: 44100, channels: 2)! 38 + engine.connect(player, to: engine.mainMixerNode, format: format) 39 + engine.mainMixerNode.outputVolume = 0 40 + 41 + let frameCount = AVAudioFrameCount(4410) // 0.1 s of silence 42 + guard let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: frameCount) else { return } 43 + buffer.frameLength = frameCount 44 + 45 + do { 46 + try engine.start() 47 + player.scheduleBuffer(buffer, at: nil, options: .loops) 48 + player.play() 49 + audioEngine = engine 50 + playerNode = player 51 + } catch { 52 + print("MediaControlsManager: could not start silent audio engine: \(error)") 53 + } 54 + } 55 + 24 56 func setupRemoteCommandCenter() { 25 57 let commandCenter = MPRemoteCommandCenter.shared() 26 - 58 + 27 59 commandCenter.playCommand.isEnabled = true 28 60 commandCenter.playCommand.addTarget { [weak self] event in 29 61 self?.onPlay?() 30 62 return .success 31 63 } 32 - 64 + 33 65 commandCenter.pauseCommand.isEnabled = true 34 66 commandCenter.pauseCommand.addTarget { [weak self] event in 35 67 self?.onPause?() 36 68 return .success 37 69 } 38 - 70 + 39 71 commandCenter.togglePlayPauseCommand.isEnabled = true 40 72 commandCenter.togglePlayPauseCommand.addTarget { [weak self] event in 73 + self?.onTogglePlayPause?() 41 74 return .success 42 75 } 43 - 76 + 44 77 commandCenter.nextTrackCommand.isEnabled = true 45 78 commandCenter.nextTrackCommand.addTarget { [weak self] event in 46 79 self?.onNext?() 47 80 return .success 48 81 } 49 - 82 + 50 83 commandCenter.previousTrackCommand.isEnabled = true 51 84 commandCenter.previousTrackCommand.addTarget { [weak self] event in 52 85 self?.onPrevious?() 53 86 return .success 54 87 } 55 - 88 + 56 89 commandCenter.changePlaybackPositionCommand.isEnabled = true 57 90 commandCenter.changePlaybackPositionCommand.addTarget { [weak self] event in 58 91 if let event = event as? MPChangePlaybackPositionCommandEvent {
+4 -1
macos/Rockbox/State/PlayerState.swift
··· 59 59 } 60 60 } 61 61 62 + manager.onTogglePlayPause = { [weak self] in 63 + self?.playOrPause() 64 + } 65 + 62 66 manager.onNext = { 63 67 Task { 64 68 try? await next() ··· 76 80 try? await play(elapsed: Int64(position) * 1000) 77 81 } 78 82 } 79 - 80 83 } 81 84 82 85 private func setInitialNowPlayingInfo() {