diff --git a/src/apps/experimental/routes/legacyRoutes/user.ts b/src/apps/experimental/routes/legacyRoutes/user.ts index 1547f683590..12e137bc96f 100644 --- a/src/apps/experimental/routes/legacyRoutes/user.ts +++ b/src/apps/experimental/routes/legacyRoutes/user.ts @@ -13,6 +13,12 @@ export const LEGACY_USER_ROUTES: LegacyRoute[] = [ controller: 'list', view: 'list.html' } + }, { + path: 'lyrics', + pageProps: { + controller: 'lyrics', + view: 'lyrics.html' + } }, { path: 'mypreferencesmenu.html', pageProps: { diff --git a/src/apps/stable/routes/legacyRoutes/user.ts b/src/apps/stable/routes/legacyRoutes/user.ts index fa1fa43ad2d..19b87c7cd85 100644 --- a/src/apps/stable/routes/legacyRoutes/user.ts +++ b/src/apps/stable/routes/legacyRoutes/user.ts @@ -19,6 +19,12 @@ export const LEGACY_USER_ROUTES: LegacyRoute[] = [ controller: 'livetv/livetvsuggested', view: 'livetv.html' } + }, { + path: 'lyrics', + pageProps: { + controller: 'lyrics', + view: 'lyrics.html' + } }, { path: 'music.html', pageProps: { diff --git a/src/components/itemContextMenu.js b/src/components/itemContextMenu.js index c056b0d0ece..fd7d82b42e5 100644 --- a/src/components/itemContextMenu.js +++ b/src/components/itemContextMenu.js @@ -183,6 +183,14 @@ export function getCommands(options) { id: 'delete', icon: 'delete' }); + + if (item.Type === 'Audio' && item.HasLyrics && window.location.href.includes(item.Id)) { + commands.push({ + name: globalize.translate('DeleteLyrics'), + id: 'deleteLyrics', + icon: 'delete_sweep' + }); + } } // Books are promoted to major download Button and therefor excluded in the context menu @@ -313,6 +321,14 @@ export function getCommands(options) { }); } + if (item.HasLyrics) { + commands.push({ + name: globalize.translate('ViewLyrics'), + id: 'lyrics', + icon: 'lyrics' + }); + } + return commands; } @@ -495,6 +511,9 @@ function executeCommand(item, id, options) { case 'delete': deleteItem(apiClient, item).then(getResolveFunction(resolve, id, true, true), getResolveFunction(resolve, id)); break; + case 'deleteLyrics': + deleteLyrics(apiClient, item).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); + break; case 'share': navigator.share({ title: item.Name, @@ -510,6 +529,15 @@ function executeCommand(item, id, options) { appRouter.showItem(item.AlbumArtists[0].Id, item.ServerId); getResolveFunction(resolve, id)(); break; + case 'lyrics': { + if (options.isMobile) { + appRouter.show('lyrics'); + } else { + appRouter.showItem(item.Id, item.ServerId); + } + getResolveFunction(resolve, id)(); + break; + } case 'playallfromhere': getResolveFunction(resolve, id)(); break; @@ -636,6 +664,12 @@ function deleteItem(apiClient, item) { }); } +function deleteLyrics(apiClient, item) { + return import('../scripts/deleteHelper').then((deleteHelper) => { + return deleteHelper.deleteLyrics(item); + }); +} + function refresh(apiClient, item) { import('./refreshdialog/refreshdialog').then(({ default: RefreshDialog }) => { new RefreshDialog({ diff --git a/src/components/nowPlayingBar/nowPlayingBar.js b/src/components/nowPlayingBar/nowPlayingBar.js index cdeaea0cca3..ab3c9c7feb8 100644 --- a/src/components/nowPlayingBar/nowPlayingBar.js +++ b/src/components/nowPlayingBar/nowPlayingBar.js @@ -34,6 +34,7 @@ let positionSlider; let toggleAirPlayButton; let toggleRepeatButton; let toggleRepeatButtonIcon; +let lyricButton; let lastUpdateTime = 0; let lastPlayerState = {}; @@ -42,6 +43,9 @@ let currentRuntimeTicks = 0; let isVisibilityAllowed = true; +let lyricPageActive = false; +let isAudio = false; + function getNowPlayingBarHtml() { let html = ''; @@ -82,6 +86,8 @@ function getNowPlayingBarHtml() { html += ``; + html += ``; + html += ``; html += ``; @@ -146,6 +152,7 @@ function bindEvents(elem) { toggleRepeatButton = elem.querySelector('.toggleRepeatButton'); volumeSlider = elem.querySelector('.nowPlayingBarVolumeSlider'); volumeSliderContainer = elem.querySelector('.nowPlayingBarVolumeSliderContainer'); + lyricButton = nowPlayingBarElement.querySelector('.openLyricsButton'); muteButton.addEventListener('click', function () { if (currentPlayer) { @@ -212,6 +219,14 @@ function bindEvents(elem) { } }); + lyricButton.addEventListener('click', function() { + if (lyricPageActive) { + appRouter.back(); + } else { + appRouter.show('lyrics'); + } + }); + toggleRepeatButton = elem.querySelector('.toggleRepeatButton'); toggleRepeatButton.addEventListener('click', function () { switch (playbackManager.getRepeatMode()) { @@ -363,6 +378,7 @@ function updatePlayerStateInternal(event, state, player) { updateTimeDisplay(playState.PositionTicks, nowPlayingItem.RunTimeTicks, playbackManager.getBufferedRanges(player)); updateNowPlayingInfo(state); + updateLyricButton(); } function updateRepeatModeDisplay(repeatMode) { @@ -453,6 +469,22 @@ function updatePlayerVolumeState(isMuted, volumeLevel) { } } +function updateLyricButton() { + if (!isEnabled) { + return; + } + + isAudio ? showButton(lyricButton) : hideButton(lyricButton); + setLyricButtonActiveStatus(); +} + +function setLyricButtonActiveStatus() { + if (!isEnabled) { + return; + } + lyricButton.classList.toggle('buttonActive', lyricPageActive); +} + function seriesImageUrl(item, options) { if (!item) { throw new Error('item cannot be null!'); @@ -595,6 +627,9 @@ function updateNowPlayingInfo(state) { function onPlaybackStart(e, state) { console.debug('nowplaying event: ' + e.type); const player = this; + + isAudio = state.NowPlayingItem.Type === 'Audio'; + onStateChanged.call(player, e, state); } @@ -698,6 +733,7 @@ function onStateChanged(event, state) { } getNowPlayingBar(); + updateLyricButton(); updatePlayerStateInternal(event, state, player); } @@ -754,6 +790,7 @@ function refreshFromPlayer(player, type) { } function bindToPlayer(player) { + lyricPageActive = appRouter.currentRouteInfo.path.toLowerCase() === '/lyrics'; if (player === currentPlayer) { return; } @@ -786,6 +823,8 @@ Events.on(playbackManager, 'playerchange', function () { bindToPlayer(playbackManager.getCurrentPlayer()); document.addEventListener('viewbeforeshow', function (e) { + lyricPageActive = appRouter.currentRouteInfo.path.toLowerCase() === '/lyrics'; + setLyricButtonActiveStatus(); if (!e.detail.options.enableMediaControl) { if (isVisibilityAllowed) { isVisibilityAllowed = false; diff --git a/src/components/remotecontrol/remotecontrol.js b/src/components/remotecontrol/remotecontrol.js index 9406e16e2b3..2de6f2e2065 100644 --- a/src/components/remotecontrol/remotecontrol.js +++ b/src/components/remotecontrol/remotecontrol.js @@ -222,7 +222,8 @@ function updateNowPlayingInfo(context, state, serverId) { contextButton.addEventListener('click', function () { itemContextMenu.show(Object.assign({ item: fullItem, - user: user + user: user, + isMobile: layoutManager.mobile }, options)) .catch(() => { /* no-op */ }); }); @@ -323,6 +324,7 @@ export default function () { context.querySelector('.remoteControlSection').classList.add('hide'); } + buttonVisible(context.querySelector('.btnLyrics'), item?.Type === 'Audio' && !layoutManager.mobile); buttonVisible(context.querySelector('.btnStop'), item != null); buttonVisible(context.querySelector('.btnNextTrack'), item != null); buttonVisible(context.querySelector('.btnPreviousTrack'), item != null); @@ -769,6 +771,10 @@ export default function () { playbackManager.fastForward(currentPlayer); } }); + context.querySelector('.btnLyrics').addEventListener('click', function () { + appRouter.show('lyrics'); + }); + for (const shuffleButton of context.querySelectorAll('.btnShuffleQueue')) { shuffleButton.addEventListener('click', function () { if (currentPlayer) { diff --git a/src/controllers/itemDetails/index.html b/src/controllers/itemDetails/index.html index 0172fe659e5..935ac44ea48 100644 --- a/src/controllers/itemDetails/index.html +++ b/src/controllers/itemDetails/index.html @@ -187,6 +187,11 @@
+ +