Skip to content

Commit

Permalink
Fix meda keys on macos and support navigator.mediaSession
Browse files Browse the repository at this point in the history
This enables extended metadata viewing in the system media player UI.
  • Loading branch information
bcomnes committed Dec 23, 2023
1 parent 47a9036 commit d9aa7b8
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 45 deletions.
29 changes: 0 additions & 29 deletions main/global-shortcuts.js

This file was deleted.

27 changes: 16 additions & 11 deletions main/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ const xtend = require('xtend')
const userConfig = require('./config')
const menu = require('./menu')
const artwork = require('./artwork')
const GlobalShortcuts = require('./global-shortcuts')
const makeTrackDict = require('./track-dict')
const audio = require('./windows/audio')
const player = require('./windows/player')
Expand All @@ -20,7 +19,6 @@ process.on('uncaughtException', (err) => {
log.error(err)
})

const globalShortcuts = new GlobalShortcuts()
const windows = [player, audio]

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

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

app.on('ready', function appReady () {
menu.init()
audio.init()
player.init()
artwork.init()

globalShortcuts.init({
MediaNextTrack: next,
MediaPreviousTrack: prev,
MediaPlayPause: playPause
})

electron.powerMonitor.on('suspend', function pauseOnWake () {
log.info('Entering sleep, pausing')
ipcMain.emit('pause')
Expand All @@ -81,7 +72,9 @@ app.on('ready', function appReady () {
ipcMain.on('volume', volume)
ipcMain.on('queue', queue)
ipcMain.on('play', play)
ipcMain.on('playing', playing)
ipcMain.on('pause', pause)
ipcMain.on('paused', paused)
ipcMain.on('prev', prev)
ipcMain.on('next', next)
ipcMain.on('mute', mute)
Expand Down Expand Up @@ -138,18 +131,31 @@ app.on('ready', function appReady () {
player.win.send('new-track', al.currentTrack)
player.win.send('new-index', al.index)
}
if (audio.win) {
audio.win.send('new-artwork', al.currentTrack)
}
}

function play () {
state.playing = true
broadcast('play')
}

function playing () {
state.playing = true
broadcast('playing')
}

function pause () {
state.playing = false
broadcast('pause')
}

function paused () {
state.playing = false
broadcast('paused')
}

function playPause () {
state.playing ? pause() : play()
}
Expand Down Expand Up @@ -244,11 +250,10 @@ app.on('activate', function activate () {
al.recall()
player.init()
}
globalShortcuts.reregister()
})

app.on('will-quit', function (e) {
globalShortcuts.unregisterAll()
// nothing
})

app.on('before-quit', function beforeQuit (e) {
Expand Down
73 changes: 72 additions & 1 deletion renderer/audio/audio-player.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const Nanobus = require('nanobus')
const window = require('global/window')
const get = require('lodash.get')
const fs = require('fs').promises
const fileUrlFromPath = require('../shared/file-url')
const setTimeout = window.setTimeout
const clearTimeout = window.clearTimeout

Expand All @@ -15,6 +17,7 @@ class AudioPlayer extends Nanobus {
this.seeking = false
this.seekDebounceTimer = null
this.timeupdate = null
this.currentTrack = null // Store current track

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

this._initMediaSession()

this.emit('initialized', this)
}

Expand All @@ -41,19 +46,24 @@ class AudioPlayer extends Nanobus {
}, 1000)
}

load (src) {
load (track) {
this.currentTrack = track // Update current track
this._updateMediaSessionMetadata()
const src = fileUrlFromPath(track.filepath)
this.emit('loading', src)
this.audio.src = src || ''
}

play () {
this.emit('playing')
this.audio.play()
navigator.mediaSession.playbackState = 'playing'
}

pause () {
this.emit('paused')
this.audio.pause()
navigator.mediaSession.playbackState = 'paused'
}

volume (lev) {
Expand All @@ -76,6 +86,49 @@ class AudioPlayer extends Nanobus {
this.emit('seek', newTime)
this.audio.currentTime = newTime
}

nextTrack () {
this.emit('next')
}

previousTrack () {
this.emit('prev')
}

_initMediaSession () {
navigator.mediaSession.setActionHandler('play', () => this.play())
navigator.mediaSession.setActionHandler('pause', () => this.pause())
navigator.mediaSession.setActionHandler('previoustrack', () => this.previousTrack())
navigator.mediaSession.setActionHandler('nexttrack', () => this.nextTrack())
navigator.mediaSession.setActionHandler('seekbackward', () => this.seek(this.audio.currentTime - 10)) // Example: seek back 10 seconds
navigator.mediaSession.setActionHandler('seekforward', () => this.seek(this.audio.currentTime + 10)) // Example: seek forward 10 seconds
}

_updateMediaSessionMetadata () {
if (this.currentTrack) {
navigator.mediaSession.metadata = new MediaMetadata({
title: this.currentTrack.title,
artist: this.currentTrack.artist,
album: this.currentTrack.album
// artwork: [{ src: fileUrlFromPath(this.currentTrack.artwork), sizes: '96x96', type: 'image/png' }]
})
}
}

async _updateArtwork (trackWithArt) {
if (this.currentTrack?.filepath === trackWithArt?.filepath && trackWithArt?.artwork) {
this.currentTrack = trackWithArt
const blob = await fileToBlobUrl(trackWithArt?.artwork)
if (blob) {
navigator.mediaSession.metadata = new MediaMetadata({
title: this.currentTrack.title,
artist: this.currentTrack.artist,
album: this.currentTrack.album,
artwork: [{ src: blob, sizes: '96x96', type: 'image/png' }]
})
}
}
}
}

module.exports = AudioPlayer
Expand All @@ -96,3 +149,21 @@ module.exports = AudioPlayer
// requireInteraction: false
// })
// }

async function fileToBlobUrl (filePath) {
try {
// Read file as buffer
const buffer = await fs.readFile(filePath)

// Convert buffer to blob (Electron specific)
const blob = new Blob([buffer], { type: 'image/png' }) // Adjust type based on your file's MIME type

// Create a blob URL (in renderer process of Electron)
const blobUrl = URL.createObjectURL(blob)

return blobUrl
} catch (error) {
console.error('Error converting file to Blob URL:', error)
return null
}
}
2 changes: 1 addition & 1 deletion renderer/audio/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<style>
h1 { text-align: center }
</style>
<meta http-equiv="Content-Security-Policy" content="default-src 'self'">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src 'self' blob:;">
</head>
<body>
<h1>🎵</h1>
Expand Down
24 changes: 21 additions & 3 deletions renderer/audio/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,28 @@ player.on('timeupdate', function (time) {
ipcRenderer.send('timeupdate', time)
})

player.on('playing', function () {
ipcRenderer.send('playing')
})

player.on('paused', function () {
ipcRenderer.send('paused')
})

player.on('next', function () {
ipcRenderer.send('next')
})

player.on('prev', function () {
ipcRenderer.send('prev')
})

ipcRenderer.on('new-track', function (ev, track = {}) {
// Might need to switch on different path format processing
const src = fileUrlFromPath(track.filepath)
player.load(src)
player.load(track)
})

ipcRenderer.on('new-artwork', (ev, track = {}) => {
player._updateArtwork(track)
})

ipcRenderer.on('play', function (ev, data) {
Expand Down
2 changes: 2 additions & 0 deletions renderer/player/stores/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,9 @@ function playerStore (state, emitter) {
}

ipcRenderer.on('play', () => playing(true))
ipcRenderer.on('playing', () => playing(true))
ipcRenderer.on('pause', () => playing(false))
ipcRenderer.on('paused', () => playing(false))
ipcRenderer.on('new-track', (ev, newTrack) => current(newTrack))
ipcRenderer.on('mute', () => muted(true))
ipcRenderer.on('unmute', () => muted(false))
Expand Down

0 comments on commit d9aa7b8

Please sign in to comment.