diff --git a/.erb/scripts/ElectronRebuild.js b/.erb/scripts/ElectronRebuild.js index ce277d74..42da595d 100644 --- a/.erb/scripts/ElectronRebuild.js +++ b/.erb/scripts/ElectronRebuild.js @@ -10,7 +10,7 @@ if ( fs.existsSync(nodeModulesPath) ) { const electronRebuildCmd = - '../node_modules/.bin/electron-rebuild --parallel --force --types prod,dev,optional --module-dir .'; + '../node_modules/.bin/electron-rebuild --no-parallel --force --types prod,dev,optional --module-dir .'; const cmd = process.platform === 'win32' ? electronRebuildCmd.replace(/\//g, '\\') diff --git a/src/components/settings/ConfigPanels/PlayerConfig.tsx b/src/components/settings/ConfigPanels/PlayerConfig.tsx index 9bf7525d..8a3704ae 100644 --- a/src/components/settings/ConfigPanels/PlayerConfig.tsx +++ b/src/components/settings/ConfigPanels/PlayerConfig.tsx @@ -23,6 +23,7 @@ import { appendPlaybackFilter, setAudioDeviceId } from '../../../redux/configSli import { notifyToast } from '../../shared/toast'; import ConfigOption from '../ConfigOption'; import { Server } from '../../../types'; +import { isWindows, isWindows10 } from '../../../shared/utils'; const getAudioDevice = async () => { const devices = await navigator.mediaDevices.enumerateDevices(); @@ -74,6 +75,9 @@ const PlayerConfig = () => { const [globalMediaHotkeys, setGlobalMediaHotkeys] = useState( Boolean(settings.getSync('globalMediaHotkeys')) ); + const [systemMediaTransportControls, setSystemMediaTransportControls] = useState( + Boolean(settings.getSync('systemMediaTransportControls')) + ); const [scrobble, setScrobble] = useState(Boolean(settings.getSync('scrobble'))); const [audioDevices, setAudioDevices] = useState(); const audioDevicePickerContainerRef = useRef(null); @@ -220,6 +224,10 @@ const PlayerConfig = () => { setGlobalMediaHotkeys(e); if (e) { ipcRenderer.send('enableGlobalHotkeys'); + + settings.setSync('systemMediaTransportControls', !e); + setSystemMediaTransportControls(!e); + ipcRenderer.send('disableSystemMediaTransportControls'); } else { ipcRenderer.send('disableGlobalHotkeys'); } @@ -227,6 +235,39 @@ const PlayerConfig = () => { /> } /> + + {isWindows() && isWindows10() && ( + + Enable or disable the Windows System Media Transport Controls (play/pause, next, + previous, stop). This will show the Windows Media Popup (Windows 10 only) when + pressing a media key. This feauture will override the Global Media Hotkeys option. + + } + option={ + { + settings.setSync('systemMediaTransportControls', e); + setSystemMediaTransportControls(e); + if (e) { + ipcRenderer.send('enableSystemMediaTransportControls'); + + settings.setSync('globalMediaHotkeys', !e); + setGlobalMediaHotkeys(!e); + ipcRenderer.send('disableGlobalHotkeys'); + } else { + ipcRenderer.send('disableSystemMediaTransportControls'); + } + }} + /> + } + /> + )} + { settings.setSync('globalMediaHotkeys', false); } + if (force || !settings.hasSync('systemMediaTransportControls')) { + settings.setSync('systemMediaTransportControls', false); + } + if (force || !settings.hasSync('cachePath')) { settings.setSync('cachePath', path.join(path.dirname(settings.file()))); } diff --git a/src/main.dev.js b/src/main.dev.js index af402901..29a328df 100644 --- a/src/main.dev.js +++ b/src/main.dev.js @@ -32,7 +32,7 @@ import playQueueReducer, { } from './redux/playQueueSlice'; import multiSelectReducer from './redux/multiSelectSlice'; import MenuBuilder from './menu'; -import { getCurrentEntryList } from './shared/utils'; +import { getCurrentEntryList, isWindows, isWindows10, isMacOS, isLinux } from './shared/utils'; import setDefaultSettings from './components/shared/setDefaultSettings'; settings.configure({ @@ -40,11 +40,6 @@ settings.configure({ numSpaces: 2, }); -const isWindows = process.platform === 'win32'; -const isWindows10 = os.release().match(/^10\.*/g); -const isMacOS = process.platform === 'darwin'; -const isLinux = process.platform === 'linux'; - setDefaultSettings(false); export const store = configureStore({ @@ -101,7 +96,7 @@ const getAssetPath = (...paths) => { }; const createWinThumbnailClip = () => { - if (isWindows) { + if (isWindows()) { // Set the current song image as thumbnail mainWindow.setThumbnailClip({ x: 15, @@ -174,7 +169,7 @@ const previousTrack = () => { } }; -if (isLinux) { +if (isLinux()) { const mprisPlayer = Player({ name: 'Sonixd', identity: 'Sonixd', @@ -336,8 +331,106 @@ if (isLinux) { }); } +if (isWindows() && isWindows10()) { + const windowsMedia = require('@nodert-win10-au/windows.media'); + const windowsMediaPlayback = require('@nodert-win10-au/windows.media.playback'); + const windowsStorageStreams = require('@nodert-win10-au/windows.storage.streams'); + const windowsFoundation = require('@nodert-win10-au/windows.foundation'); + + const Controls = windowsMediaPlayback.BackgroundMediaPlayer.current.systemMediaTransportControls; + + if (settings.getSync('systemMediaTransportControls')) { + Controls.isEnabled = true; + } else { + Controls.isEnabled = false; + } + + ipcMain.on('enableSystemMediaTransportControls', () => { + Controls.isEnabled = true; + }); + + ipcMain.on('disableSystemMediaTransportControls', () => { + Controls.isEnabled = false; + }); + + Controls.isChannelUpEnabled = false; + Controls.isChannelDownEnabled = false; + Controls.isFastForwardEnabled = false; + Controls.isRewindEnabled = false; + Controls.isRecordEnabled = false; + Controls.isPlayEnabled = true; + Controls.isPauseEnabled = true; + Controls.isStopEnabled = true; + Controls.isNextEnabled = true; + Controls.isPreviousEnabled = true; + + Controls.playbackStatus = windowsMedia.MediaPlaybackStatus.closed; + Controls.displayUpdater.type = windowsMedia.MediaPlaybackType.music; + + Controls.displayUpdater.musicProperties.title = 'Sonixd'; + Controls.displayUpdater.musicProperties.artist = 'No Track Playing'; + Controls.displayUpdater.musicProperties.albumTitle = 'No Album Playing'; + Controls.displayUpdater.update(); + + Controls.on('buttonpressed', (sender, eventArgs) => { + switch (eventArgs.button) { + case windowsMedia.SystemMediaTransportControlsButton.play: + play(); + Controls.playbackStatus = windowsMedia.MediaPlaybackStatus.playing; + break; + case windowsMedia.SystemMediaTransportControlsButton.pause: + pause(); + Controls.playbackStatus = windowsMedia.MediaPlaybackStatus.paused; + break; + case windowsMedia.SystemMediaTransportControlsButton.stop: + stop(); + Controls.playbackStatus = windowsMedia.MediaPlaybackStatus.stopped; + break; + case windowsMedia.SystemMediaTransportControlsButton.next: + nextTrack(); + break; + case windowsMedia.SystemMediaTransportControlsButton.previous: + previousTrack(); + break; + default: + break; + } + }); + + ipcMain.on('playpause', (_event, arg) => { + if (arg.status === 'PLAYING') { + Controls.playbackStatus = windowsMedia.MediaPlaybackStatus.playing; + } else { + Controls.playbackStatus = windowsMedia.MediaPlaybackStatus.paused; + } + }); + + ipcMain.on('current-song', (_event, arg) => { + if (Controls.playbackStatus !== windowsMedia.MediaPlaybackStatus.playing) { + Controls.playbackStatus = windowsMedia.MediaPlaybackStatus.playing; + } + + Controls.displayUpdater.musicProperties.title = arg.title || 'Unknown Title'; + Controls.displayUpdater.musicProperties.artist = + arg.artist?.length !== 0 + ? arg.artist?.map((artist) => artist.title).join(', ') + : 'Unknown Artist'; + Controls.displayUpdater.musicProperties.albumTitle = arg.album || 'Unknown Album'; + + Controls.displayUpdater.thumbnail = windowsStorageStreams.RandomAccessStreamReference.createFromUri( + new windowsFoundation.Uri( + arg.image.includes('placeholder') + ? 'https://raw.githubusercontent.com/jeffvli/sonixd/main/src/img/placeholder.png' + : arg.image + ) + ); + + Controls.displayUpdater.update(); + }); +} + const createWinThumbarButtons = () => { - if (isWindows) { + if (isWindows()) { mainWindow.setThumbarButtons([ { tooltip: 'Previous Track', @@ -405,7 +498,7 @@ const createWindow = async () => { globalShortcut.register('MediaPreviousTrack', () => { previousTrack(); }); - } else { + } else if (!settings.getSync('systemMediaTransportControls')) { electronLocalshortcut.register(mainWindow, 'MediaStop', () => { stop(); }); @@ -424,6 +517,8 @@ const createWindow = async () => { } ipcMain.on('enableGlobalHotkeys', () => { + electronLocalshortcut.unregisterAll(mainWindow); + globalShortcut.register('MediaStop', () => { stop(); }); @@ -443,21 +538,24 @@ const createWindow = async () => { ipcMain.on('disableGlobalHotkeys', () => { globalShortcut.unregisterAll(); - electronLocalshortcut.register(mainWindow, 'MediaStop', () => { - stop(); - }); - electronLocalshortcut.register(mainWindow, 'MediaPlayPause', () => { - playPause(); - }); + if (!settings.getSync('systemMediaTransportControls')) { + electronLocalshortcut.register(mainWindow, 'MediaStop', () => { + stop(); + }); - electronLocalshortcut.register(mainWindow, 'MediaNextTrack', () => { - nextTrack(); - }); + electronLocalshortcut.register(mainWindow, 'MediaPlayPause', () => { + playPause(); + }); - electronLocalshortcut.register(mainWindow, 'MediaPreviousTrack', () => { - previousTrack(); - }); + electronLocalshortcut.register(mainWindow, 'MediaNextTrack', () => { + nextTrack(); + }); + + electronLocalshortcut.register(mainWindow, 'MediaPreviousTrack', () => { + previousTrack(); + }); + } }); mainWindow.loadURL(`file://${__dirname}/index.html#${settings.getSync('startPage')}`); @@ -493,7 +591,7 @@ const createWindow = async () => { mainWindow.hide(); } - if (isWindows && isWindows10) { + if (isWindows() && isWindows10()) { mainWindow.setThumbnailClip({ x: 0, y: 0, @@ -504,7 +602,7 @@ const createWindow = async () => { }); mainWindow.on('restore', () => { - if (isWindows && isWindows10) { + if (isWindows() && isWindows10()) { createWinThumbnailClip(); } }); @@ -516,7 +614,7 @@ const createWindow = async () => { } }); - if (isWindows) { + if (isWindows()) { mainWindow.on('resize', () => { const window = mainWindow.getContentBounds(); @@ -540,7 +638,7 @@ const createWindow = async () => { }); } - if (isMacOS) { + if (isMacOS()) { mainWindow.on('resize', () => { const window = mainWindow.getContentBounds(); @@ -592,11 +690,11 @@ const createWindow = async () => { }; const createTray = () => { - if (isMacOS) { + if (isMacOS()) { return; } - tray = isLinux ? new Tray(getAssetPath('icon.png')) : new Tray(getAssetPath('icon.ico')); + tray = isLinux() ? new Tray(getAssetPath('icon.png')) : new Tray(getAssetPath('icon.ico')); const contextMenu = Menu.buildFromTemplate([ { label: 'Open main window', diff --git a/src/package.json b/src/package.json index e548f107..e6bf406a 100644 --- a/src/package.json +++ b/src/package.json @@ -14,6 +14,10 @@ }, "license": "GPL-3.0", "dependencies": { + "@nodert-win10-au/windows.foundation": "^0.4.4", + "@nodert-win10-au/windows.media": "^0.4.4", + "@nodert-win10-au/windows.media.playback": "^0.4.4", + "@nodert-win10-au/windows.storage.streams": "^0.4.4", "mpris-service": "^2.1.2" } } diff --git a/src/shared/mockSettings.ts b/src/shared/mockSettings.ts index 67de614a..b2cb28fd 100644 --- a/src/shared/mockSettings.ts +++ b/src/shared/mockSettings.ts @@ -4,6 +4,7 @@ export const mockSettings = { theme: 'defaultDark', showDebugWindow: false, globalMediaHotkeys: true, + systemMediaTransportControls: false, cachePath: 'C:\\Users\\jli\\AppData\\Roaming\\Electron', legacyAuth: false, volume: 0.93, diff --git a/src/shared/utils.ts b/src/shared/utils.ts index 95ca4c66..4f0aec5f 100644 --- a/src/shared/utils.ts +++ b/src/shared/utils.ts @@ -1,5 +1,6 @@ import fs from 'fs'; import _ from 'lodash'; +import os from 'os'; import path from 'path'; import moment from 'moment'; import arrayMove from 'array-move'; @@ -611,3 +612,19 @@ export const smoothScroll = ( }; animateScroll(); }; + +export const isWindows = () => { + return process.platform === 'win32'; +}; + +export const isWindows10 = () => { + return os.release().match(/^10\.*/g); +}; + +export const isMacOS = () => { + return process.platform === 'darwin'; +}; + +export const isLinux = () => { + return process.platform === 'linux'; +}; diff --git a/src/yarn.lock b/src/yarn.lock index 62e26358..e6dc2f1f 100644 --- a/src/yarn.lock +++ b/src/yarn.lock @@ -2,6 +2,34 @@ # yarn lockfile v1 +"@nodert-win10-au/windows.foundation@^0.4.4": + version "0.4.4" + resolved "https://registry.yarnpkg.com/@nodert-win10-au/windows.foundation/-/windows.foundation-0.4.4.tgz#13e9fe3a233cae8f6ea3f50d677860b3da8c9220" + integrity sha512-M31DRrLb3DWTtInxwrZqTjvTl4d7O2EqfPHOcgoch2gA1JDgLr+cpTYxDRar+MvkKEO2dCNDhsV2eoDRZFHkiw== + dependencies: + nan latest + +"@nodert-win10-au/windows.media.playback@^0.4.4": + version "0.4.4" + resolved "https://registry.yarnpkg.com/@nodert-win10-au/windows.media.playback/-/windows.media.playback-0.4.4.tgz#6fda817c1fbbcbfe21ab9c324f6a093d461ef7c9" + integrity sha512-z8FjQEA2bxBhbwO5vb0hnFTfOa5cass6dZvERU5aGs7iVFmw+bplY3Wz8B6ZuaypUB0p1BXU04Xchn2gjY098g== + dependencies: + nan latest + +"@nodert-win10-au/windows.media@^0.4.4": + version "0.4.4" + resolved "https://registry.yarnpkg.com/@nodert-win10-au/windows.media/-/windows.media-0.4.4.tgz#34afc6d2563adc1d0f867a03b60588d0fde3a9dc" + integrity sha512-5Apk9Wxks5qtYNd2ug1uOleekDf/r8oBZa67uMySMLr2IgdDbWZxu+bGZWH0MTL74Ik77KmTvYujPF1kgc4SHA== + dependencies: + nan latest + +"@nodert-win10-au/windows.storage.streams@^0.4.4": + version "0.4.4" + resolved "https://registry.yarnpkg.com/@nodert-win10-au/windows.storage.streams/-/windows.storage.streams-0.4.4.tgz#277e9004b3802b6bce8d96b6bfb018506e82e725" + integrity sha512-8dUSkAHZJEQAa/bcMEbotfym3Uxq01LFEfcyjCbIbTwJfW1ZNElDpMIrqizLCVr03i9mrrJ5dYCcYeABdtj+vA== + dependencies: + nan latest + "@nornagon/put@0.0.8": version "0.0.8" resolved "https://registry.yarnpkg.com/@nornagon/put/-/put-0.0.8.tgz#9d497ec46c9364acc3f8b59aa3cf8ee4134ae337" @@ -182,7 +210,7 @@ mpris-service@^2.1.2: deep-equal "^1.0.1" source-map-support "^0.5.11" -nan@^2.12.1: +nan@^2.12.1, nan@latest: version "2.15.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==