From c89b8a9f803ca53a4f63c1ff5c07e895c63455fe Mon Sep 17 00:00:00 2001 From: R0nd Date: Sat, 3 Oct 2020 22:03:22 +0300 Subject: [PATCH 1/5] Added MediaSession support (notification controls on mobile) --- js/webui/src/5-seconds-of-silence.mp3 | Bin 0 -> 41239 bytes js/webui/src/index.html | 9 ++- js/webui/src/index.js | 85 ++++++++++++------------ js/webui/src/mediasession_controller.js | 78 ++++++++++++++++++++++ js/webui/webpack.config.js | 2 +- 5 files changed, 129 insertions(+), 45 deletions(-) create mode 100644 js/webui/src/5-seconds-of-silence.mp3 create mode 100644 js/webui/src/mediasession_controller.js diff --git a/js/webui/src/5-seconds-of-silence.mp3 b/js/webui/src/5-seconds-of-silence.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..866abc7eef8a62ffda9e3171af1b2fb35e891504 GIT binary patch literal 41239 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
noIXPo^`xC) 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 WZQNaW_nfwK^7x-$`EUG*@;?B9P27(F literal 0 HcmV?d00001 diff --git a/js/webui/src/index.html b/js/webui/src/index.html index 1a4adb52..aaddd9e0 100644 --- a/js/webui/src/index.html +++ b/js/webui/src/index.html @@ -5,10 +5,13 @@ - Loading... + Loading... -
-
+
+
+ diff --git a/js/webui/src/index.js b/js/webui/src/index.js index 899651f6..600dabc6 100644 --- a/js/webui/src/index.js +++ b/js/webui/src/index.js @@ -15,18 +15,19 @@ import urls, { getPathFromUrl } from './urls' import { playlistTableKey } from './playlist_content'; import { PlaybackState } from 'beefweb-client/src'; import { SettingsView, View } from './navigation_model'; +import MediaSessionController from './mediasession_controller'; const client = new PlayerClient(new RequestHandler()); const settingsStore = new SettingsStore(); const appModel = new AppModel(client, settingsStore); const { - playerModel, - playlistModel, - fileBrowserModel, - settingsModel, - scrollManager, - navigationModel, + playerModel, + playlistModel, + fileBrowserModel, + settingsModel, + scrollManager, + navigationModel, } = appModel; const mediaSizeController = new MediaSizeController(settingsModel); @@ -34,88 +35,89 @@ const touchModeController = new TouchModeController(settingsModel); const cssSettingsController = new CssSettingsController(settingsModel); const windowController = new WindowController(playerModel); const router = new Navigo(null, true); +const mediaSessionController = new MediaSessionController(client, playerModel); router.on({ '/': () => { - router.navigate(urls.viewCurrentPlaylist); - }, + router.navigate(urls.viewCurrentPlaylist); + }, '/playlists': () => { - if (playlistModel.currentPlaylistId) - router.navigate(urls.viewPlaylist(playlistModel.currentPlaylistId)); + if (playlistModel.currentPlaylistId) + router.navigate(urls.viewPlaylist(playlistModel.currentPlaylistId)); else navigationModel.setView(View.playlist); - }, + }, '/playlists/:id': params => { - playlistModel.setCurrentPlaylistId(params.id); - navigationModel.setView(View.playlist); - }, + playlistModel.setCurrentPlaylistId(params.id); + navigationModel.setView(View.playlist); + }, '/files': () => { - router.navigate(urls.browsePath(fileBrowserModel.currentPath)); - }, + router.navigate(urls.browsePath(fileBrowserModel.currentPath)); + }, '/files/!*': () => { - navigationModel.setView(View.fileBrowser); + navigationModel.setView(View.fileBrowser); - const path = getPathFromUrl(router.lastRouteResolved().url); + const path = getPathFromUrl(router.lastRouteResolved().url); if (path) fileBrowserModel.browse(path); - }, + }, '/settings': () => { - router.navigate(urls.viewSettings(navigationModel.settingsView)); - }, + router.navigate(urls.viewSettings(navigationModel.settingsView)); + }, '/settings/:view': params => { if (params.view in SettingsView) { - navigationModel.setView(View.settings); - navigationModel.setSettingsView(params.view); + navigationModel.setView(View.settings); + navigationModel.setSettingsView(params.view); } else navigationModel.setView(View.notFound); - }, + }, '/now-playing': () => { if (playerModel.playbackState === PlaybackState.stopped) { - router.navigate(urls.viewCurrentPlaylist); - return; - } + router.navigate(urls.viewCurrentPlaylist); + return; + } - const { playlistId, index } = playerModel.activeItem; + const { playlistId, index } = playerModel.activeItem; if (playlistId && index >= 0) { - scrollManager.scrollToItem(playlistTableKey(playlistId), index); - router.navigate(urls.viewPlaylist(playlistId)); + scrollManager.scrollToItem(playlistTableKey(playlistId), index); + router.navigate(urls.viewPlaylist(playlistId)); } else if (playlistId) { - router.navigate(urls.viewPlaylist(playlistId)); + router.navigate(urls.viewPlaylist(playlistId)); } else { - router.navigate(urls.viewCurrentPlaylist); - } + router.navigate(urls.viewCurrentPlaylist); + } } }); router.notFound(() => { - navigationModel.setView(View.notFound); + navigationModel.setView(View.notFound); }); playerModel.on('trackSwitch', () => { if (!settingsModel.cursorFollowsPlayback) return; - const { playlistId, index } = playerModel.activeItem; + const { playlistId, index } = playerModel.activeItem; if (playlistId !== '' && index >= 0) - scrollManager.scrollToItem(playlistTableKey(playlistId), index); + scrollManager.scrollToItem(playlistTableKey(playlistId), index); }); playlistModel.on('playlistsChange', () => { @@ -123,8 +125,8 @@ playlistModel.on('playlistsChange', () => { if (navigationModel.view !== View.playlist) return; - if (playlistModel.currentPlaylistId) - router.navigate(urls.viewPlaylist(playlistModel.currentPlaylistId)); + if (playlistModel.currentPlaylistId) + router.navigate(urls.viewPlaylist(playlistModel.currentPlaylistId)); else router.navigate(urls.viewCurrentPlaylist); }); @@ -135,12 +137,13 @@ touchModeController.start(); cssSettingsController.start(); appModel.start(); windowController.start(); +mediaSessionController.start(); router.resolve(); const appComponent = ( - - - + + + ); ReactDom.render(appComponent, document.getElementById('app-container')); diff --git a/js/webui/src/mediasession_controller.js b/js/webui/src/mediasession_controller.js new file mode 100644 index 00000000..d52e7e17 --- /dev/null +++ b/js/webui/src/mediasession_controller.js @@ -0,0 +1,78 @@ +import DataSource from "./data_source"; +import PlayerModel from "./player_model"; +import silenceMp3 from "./5-seconds-of-silence.mp3"; + +export default class MediaSessionController { + /** + * @param {any} client + * @param {PlayerModel} playerModel + */ + constructor(client, playerModel) { + this.dataSource = new DataSource(client); + this.playerModel = playerModel; + } + + start() { + if (!"mediaSession" in navigator) { + return; + } + + navigator.mediaSession.setActionHandler("play", () => + this.playerModel.play() + ); + navigator.mediaSession.setActionHandler("pause", () => + this.playerModel.pause() + ); + navigator.mediaSession.setActionHandler("previoustrack", () => + this.playerModel.previous() + ); + navigator.mediaSession.setActionHandler("nexttrack", () => + this.playerModel.next() + ); + + this.dataSource.on("player", (player) => this.updateMetadata(player)); + this.dataSource.watch("player", { + trcolumns: ["%artist%", "%album%", "%title%"], + }); + + this.playerModel.on("change", () => this.updatePlaybackState()); + + this.updatePlaybackState(); + this.dataSource.start(); + } + + updatePlaybackState() { + const playbackStateMap = { + playing: "playing", + paused: "paused", + stopped: "none", + }; + + const audioElement = document.getElementById("silence"); + + switch (this.playerModel.playbackState) { + case "playing": + audioElement.play(); + break; + case "paused": + audioElement.pause(); + break; + default: + break; + } + + navigator.mediaSession.playbackState = + playbackStateMap[this.playerModel.playbackState]; + } + + updateMetadata(player) { + const { activeItem } = player; + const [artist, album, title] = activeItem.columns; + + navigator.mediaSession.metadata = new MediaMetadata({ + artist, + album, + title, + }); + } +} diff --git a/js/webui/webpack.config.js b/js/webui/webpack.config.js index 3da5163c..4d588baf 100644 --- a/js/webui/webpack.config.js +++ b/js/webui/webpack.config.js @@ -21,7 +21,7 @@ function configCommon(config, params) }); config.module.rules.push({ - test: /(\.svg|\.png)$/, + test: /(\.svg|\.png|\.mp3)$/, loader: 'url-loader', options: { name: '[name].[ext]', From a8dc3e7c2f2d5fbb6a3e3115c5093e2e1c20da54 Mon Sep 17 00:00:00 2001 From: R0nd Date: Sat, 3 Oct 2020 23:09:43 +0300 Subject: [PATCH 2/5] Revert whitespace --- js/webui/src/index.html | 6 +-- js/webui/src/index.js | 82 ++++++++++++++++++++--------------------- 2 files changed, 44 insertions(+), 44 deletions(-) diff --git a/js/webui/src/index.html b/js/webui/src/index.html index aaddd9e0..e96cf458 100644 --- a/js/webui/src/index.html +++ b/js/webui/src/index.html @@ -5,11 +5,11 @@ - Loading... + Loading... -
-
+
+