Skip to content

Commit d9aa7b8

Browse files
committed
Fix meda keys on macos and support navigator.mediaSession
This enables extended metadata viewing in the system media player UI.
1 parent 47a9036 commit d9aa7b8

File tree

6 files changed

+112
-45
lines changed

6 files changed

+112
-45
lines changed

main/global-shortcuts.js

Lines changed: 0 additions & 29 deletions
This file was deleted.

main/index.js

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ const xtend = require('xtend')
88
const userConfig = require('./config')
99
const menu = require('./menu')
1010
const artwork = require('./artwork')
11-
const GlobalShortcuts = require('./global-shortcuts')
1211
const makeTrackDict = require('./track-dict')
1312
const audio = require('./windows/audio')
1413
const player = require('./windows/player')
@@ -20,7 +19,6 @@ process.on('uncaughtException', (err) => {
2019
log.error(err)
2120
})
2221

23-
const globalShortcuts = new GlobalShortcuts()
2422
const windows = [player, audio]
2523

2624
const persist = new Config({ name: 'hyperamp-persist' })
@@ -58,20 +56,13 @@ app.on('second-instance', (commandLine, workingDirectory) => {
5856
})
5957

6058
app.commandLine.appendSwitch('autoplay-policy', 'no-user-gesture-required')
61-
app.commandLine.appendSwitch('disable-features', 'HardwareMediaKeyHandling')
6259

6360
app.on('ready', function appReady () {
6461
menu.init()
6562
audio.init()
6663
player.init()
6764
artwork.init()
6865

69-
globalShortcuts.init({
70-
MediaNextTrack: next,
71-
MediaPreviousTrack: prev,
72-
MediaPlayPause: playPause
73-
})
74-
7566
electron.powerMonitor.on('suspend', function pauseOnWake () {
7667
log.info('Entering sleep, pausing')
7768
ipcMain.emit('pause')
@@ -81,7 +72,9 @@ app.on('ready', function appReady () {
8172
ipcMain.on('volume', volume)
8273
ipcMain.on('queue', queue)
8374
ipcMain.on('play', play)
75+
ipcMain.on('playing', playing)
8476
ipcMain.on('pause', pause)
77+
ipcMain.on('paused', paused)
8578
ipcMain.on('prev', prev)
8679
ipcMain.on('next', next)
8780
ipcMain.on('mute', mute)
@@ -138,18 +131,31 @@ app.on('ready', function appReady () {
138131
player.win.send('new-track', al.currentTrack)
139132
player.win.send('new-index', al.index)
140133
}
134+
if (audio.win) {
135+
audio.win.send('new-artwork', al.currentTrack)
136+
}
141137
}
142138

143139
function play () {
144140
state.playing = true
145141
broadcast('play')
146142
}
147143

144+
function playing () {
145+
state.playing = true
146+
broadcast('playing')
147+
}
148+
148149
function pause () {
149150
state.playing = false
150151
broadcast('pause')
151152
}
152153

154+
function paused () {
155+
state.playing = false
156+
broadcast('paused')
157+
}
158+
153159
function playPause () {
154160
state.playing ? pause() : play()
155161
}
@@ -244,11 +250,10 @@ app.on('activate', function activate () {
244250
al.recall()
245251
player.init()
246252
}
247-
globalShortcuts.reregister()
248253
})
249254

250255
app.on('will-quit', function (e) {
251-
globalShortcuts.unregisterAll()
256+
// nothing
252257
})
253258

254259
app.on('before-quit', function beforeQuit (e) {

renderer/audio/audio-player.js

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
const Nanobus = require('nanobus')
22
const window = require('global/window')
33
const get = require('lodash.get')
4+
const fs = require('fs').promises
5+
const fileUrlFromPath = require('../shared/file-url')
46
const setTimeout = window.setTimeout
57
const clearTimeout = window.clearTimeout
68

@@ -15,6 +17,7 @@ class AudioPlayer extends Nanobus {
1517
this.seeking = false
1618
this.seekDebounceTimer = null
1719
this.timeupdate = null
20+
this.currentTrack = null // Store current track
1821

1922
this._endedListener = this.audio.addEventListener('ended',
2023
() => { if (!this.seeking) this.emit('ended') })
@@ -27,6 +30,8 @@ class AudioPlayer extends Nanobus {
2730
this.emit('timeupdate', this.timeupdate)
2831
})
2932

33+
this._initMediaSession()
34+
3035
this.emit('initialized', this)
3136
}
3237

@@ -41,19 +46,24 @@ class AudioPlayer extends Nanobus {
4146
}, 1000)
4247
}
4348

44-
load (src) {
49+
load (track) {
50+
this.currentTrack = track // Update current track
51+
this._updateMediaSessionMetadata()
52+
const src = fileUrlFromPath(track.filepath)
4553
this.emit('loading', src)
4654
this.audio.src = src || ''
4755
}
4856

4957
play () {
5058
this.emit('playing')
5159
this.audio.play()
60+
navigator.mediaSession.playbackState = 'playing'
5261
}
5362

5463
pause () {
5564
this.emit('paused')
5665
this.audio.pause()
66+
navigator.mediaSession.playbackState = 'paused'
5767
}
5868

5969
volume (lev) {
@@ -76,6 +86,49 @@ class AudioPlayer extends Nanobus {
7686
this.emit('seek', newTime)
7787
this.audio.currentTime = newTime
7888
}
89+
90+
nextTrack () {
91+
this.emit('next')
92+
}
93+
94+
previousTrack () {
95+
this.emit('prev')
96+
}
97+
98+
_initMediaSession () {
99+
navigator.mediaSession.setActionHandler('play', () => this.play())
100+
navigator.mediaSession.setActionHandler('pause', () => this.pause())
101+
navigator.mediaSession.setActionHandler('previoustrack', () => this.previousTrack())
102+
navigator.mediaSession.setActionHandler('nexttrack', () => this.nextTrack())
103+
navigator.mediaSession.setActionHandler('seekbackward', () => this.seek(this.audio.currentTime - 10)) // Example: seek back 10 seconds
104+
navigator.mediaSession.setActionHandler('seekforward', () => this.seek(this.audio.currentTime + 10)) // Example: seek forward 10 seconds
105+
}
106+
107+
_updateMediaSessionMetadata () {
108+
if (this.currentTrack) {
109+
navigator.mediaSession.metadata = new MediaMetadata({
110+
title: this.currentTrack.title,
111+
artist: this.currentTrack.artist,
112+
album: this.currentTrack.album
113+
// artwork: [{ src: fileUrlFromPath(this.currentTrack.artwork), sizes: '96x96', type: 'image/png' }]
114+
})
115+
}
116+
}
117+
118+
async _updateArtwork (trackWithArt) {
119+
if (this.currentTrack?.filepath === trackWithArt?.filepath && trackWithArt?.artwork) {
120+
this.currentTrack = trackWithArt
121+
const blob = await fileToBlobUrl(trackWithArt?.artwork)
122+
if (blob) {
123+
navigator.mediaSession.metadata = new MediaMetadata({
124+
title: this.currentTrack.title,
125+
artist: this.currentTrack.artist,
126+
album: this.currentTrack.album,
127+
artwork: [{ src: blob, sizes: '96x96', type: 'image/png' }]
128+
})
129+
}
130+
}
131+
}
79132
}
80133

81134
module.exports = AudioPlayer
@@ -96,3 +149,21 @@ module.exports = AudioPlayer
96149
// requireInteraction: false
97150
// })
98151
// }
152+
153+
async function fileToBlobUrl (filePath) {
154+
try {
155+
// Read file as buffer
156+
const buffer = await fs.readFile(filePath)
157+
158+
// Convert buffer to blob (Electron specific)
159+
const blob = new Blob([buffer], { type: 'image/png' }) // Adjust type based on your file's MIME type
160+
161+
// Create a blob URL (in renderer process of Electron)
162+
const blobUrl = URL.createObjectURL(blob)
163+
164+
return blobUrl
165+
} catch (error) {
166+
console.error('Error converting file to Blob URL:', error)
167+
return null
168+
}
169+
}

renderer/audio/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<style>
77
h1 { text-align: center }
88
</style>
9-
<meta http-equiv="Content-Security-Policy" content="default-src 'self'">
9+
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src 'self' blob:;">
1010
</head>
1111
<body>
1212
<h1>🎵</h1>

renderer/audio/index.js

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,28 @@ player.on('timeupdate', function (time) {
2727
ipcRenderer.send('timeupdate', time)
2828
})
2929

30+
player.on('playing', function () {
31+
ipcRenderer.send('playing')
32+
})
33+
34+
player.on('paused', function () {
35+
ipcRenderer.send('paused')
36+
})
37+
38+
player.on('next', function () {
39+
ipcRenderer.send('next')
40+
})
41+
42+
player.on('prev', function () {
43+
ipcRenderer.send('prev')
44+
})
45+
3046
ipcRenderer.on('new-track', function (ev, track = {}) {
31-
// Might need to switch on different path format processing
32-
const src = fileUrlFromPath(track.filepath)
33-
player.load(src)
47+
player.load(track)
48+
})
49+
50+
ipcRenderer.on('new-artwork', (ev, track = {}) => {
51+
player._updateArtwork(track)
3452
})
3553

3654
ipcRenderer.on('play', function (ev, data) {

renderer/player/stores/player.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,9 @@ function playerStore (state, emitter) {
141141
}
142142

143143
ipcRenderer.on('play', () => playing(true))
144+
ipcRenderer.on('playing', () => playing(true))
144145
ipcRenderer.on('pause', () => playing(false))
146+
ipcRenderer.on('paused', () => playing(false))
145147
ipcRenderer.on('new-track', (ev, newTrack) => current(newTrack))
146148
ipcRenderer.on('mute', () => muted(true))
147149
ipcRenderer.on('unmute', () => muted(false))

0 commit comments

Comments
 (0)