Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Lyric support #4733

Merged
merged 2 commits into from
Apr 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/apps/experimental/routes/legacyRoutes/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
6 changes: 6 additions & 0 deletions src/apps/stable/routes/legacyRoutes/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
34 changes: 34 additions & 0 deletions src/components/itemContextMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -313,6 +321,14 @@ export function getCommands(options) {
});
}

if (item.HasLyrics) {
commands.push({
name: globalize.translate('ViewLyrics'),
id: 'lyrics',
icon: 'lyrics'
});
}

return commands;
}

Expand Down Expand Up @@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -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({
Expand Down
39 changes: 39 additions & 0 deletions src/components/nowPlayingBar/nowPlayingBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ let positionSlider;
let toggleAirPlayButton;
let toggleRepeatButton;
let toggleRepeatButtonIcon;
let lyricButton;

let lastUpdateTime = 0;
let lastPlayerState = {};
Expand All @@ -42,6 +43,9 @@ let currentRuntimeTicks = 0;

let isVisibilityAllowed = true;

let lyricPageActive = false;
let isAudio = false;

function getNowPlayingBarHtml() {
let html = '';

Expand Down Expand Up @@ -82,6 +86,8 @@ function getNowPlayingBarHtml() {

html += `<button is="paper-icon-button-light" class="btnAirPlay mediaButton" title="${globalize.translate('AirPlay')}"><span class="material-icons airplay" aria-hidden="true"></span></button>`;

html += `<button is="paper-icon-button-light" class="openLyricsButton mediaButton" title="${globalize.translate('Lyrics')}"><span class="material-icons lyrics" style="top:0.1em" aria-hidden="true"></span></button>`;

html += `<button is="paper-icon-button-light" class="toggleRepeatButton mediaButton" title="${globalize.translate('Repeat')}"><span class="material-icons repeat" aria-hidden="true"></span></button>`;
html += `<button is="paper-icon-button-light" class="btnShuffleQueue mediaButton" title="${globalize.translate('Shuffle')}"><span class="material-icons shuffle" aria-hidden="true"></span></button>`;

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -363,6 +378,7 @@ function updatePlayerStateInternal(event, state, player) {
updateTimeDisplay(playState.PositionTicks, nowPlayingItem.RunTimeTicks, playbackManager.getBufferedRanges(player));

updateNowPlayingInfo(state);
updateLyricButton();
}

function updateRepeatModeDisplay(repeatMode) {
Expand Down Expand Up @@ -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!');
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -698,6 +733,7 @@ function onStateChanged(event, state) {
}

getNowPlayingBar();
updateLyricButton();
updatePlayerStateInternal(event, state, player);
}

Expand Down Expand Up @@ -754,6 +790,7 @@ function refreshFromPlayer(player, type) {
}

function bindToPlayer(player) {
lyricPageActive = appRouter.currentRouteInfo.path.toLowerCase() === '/lyrics';
if (player === currentPlayer) {
return;
}
Expand Down Expand Up @@ -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;
Expand Down
8 changes: 7 additions & 1 deletion src/components/remotecontrol/remotecontrol.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 */ });
});
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down
5 changes: 5 additions & 0 deletions src/controllers/itemDetails/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,11 @@ <h2 class="sectionTitle sectionTitle-cards padded-right"></h2>
</div>
</div>

<div id="lyricsSection" class="verticalSection-extrabottompadding detailVerticalSection lyricsContainer hide">
<h2 class="sectionTitle sectionTitle-cards padded-right">${Lyrics}</h2>
<div is="emby-itemscontainer" class="vertical-list itemsContainer"></div>
</div>

<div class="verticalSection detailVerticalSection moreFromArtistSection hide">
<h2 class="sectionTitle sectionTitle-cards padded-right"></h2>
<div is="emby-scroller" class="padded-top-focusscale padded-bottom-focusscale" data-centerfocus="true">
Expand Down
39 changes: 36 additions & 3 deletions src/controllers/itemDetails/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { intervalToDuration } from 'date-fns';
import DOMPurify from 'dompurify';
import markdownIt from 'markdown-it';
import escapeHtml from 'escape-html';
import markdownIt from 'markdown-it';
import isEqual from 'lodash-es/isEqual';

import { appHost } from 'components/apphost';
Expand Down Expand Up @@ -1055,6 +1055,7 @@ function renderDetails(page, item, apiClient, context) {
renderOverview(page, item);
renderMiscInfo(page, item);
reloadUserDataButtons(page, item);
renderLyricsContainer(page, item, apiClient);

// Don't allow redirection to other websites from the TV layout
if (!layoutManager.tv && appHost.supports('externallinks')) {
Expand All @@ -1069,6 +1070,38 @@ function enableScrollX() {
return browser.mobile && window.screen.availWidth <= 1000;
}

function renderLyricsContainer(view, item, apiClient) {
const lyricContainer = view.querySelector('.lyricsContainer');
if (lyricContainer && item.HasLyrics) {
if (item.Type !== 'Audio') {
lyricContainer.classList.add('hide');
return;
}
//get lyrics
apiClient.ajax({
url: apiClient.getUrl('Audio/' + item.Id + '/Lyrics'),
type: 'GET',
dataType: 'json'
}).then((response) => {
Comment on lines +1081 to +1085
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At some point, we must migrate to @jellyfin/sdk.

if (!response.Lyrics) {
lyricContainer.classList.add('hide');
return;
}
lyricContainer.classList.remove('hide');
const itemsContainer = lyricContainer.querySelector('.itemsContainer');
if (itemsContainer) {
const html = response.Lyrics.reduce((htmlAccumulator, lyric) => {
htmlAccumulator += escapeHtml(lyric.Text) + '<br/>';
return htmlAccumulator;
}, '');
itemsContainer.innerHTML = html;
}
}).catch(() => {
lyricContainer.classList.add('hide');
});
}
}

function renderMoreFromSeason(view, item, apiClient) {
const section = view.querySelector('.moreFromSeasonSection');

Expand Down Expand Up @@ -1119,7 +1152,7 @@ function renderMoreFromArtist(view, item, apiClient) {
const section = view.querySelector('.moreFromArtistSection');

if (section) {
if (item.Type !== 'MusicArtist' && (item.Type !== 'MusicAlbum' || !item.AlbumArtists || !item.AlbumArtists.length)) {
if (item.Type !== 'MusicArtist' && item.Type !== 'Audio' && (item.Type !== 'MusicAlbum' || !item.AlbumArtists || !item.AlbumArtists.length)) {
section.classList.add('hide');
return;
}
Expand Down Expand Up @@ -1174,7 +1207,7 @@ function renderSimilarItems(page, item, context) {
const similarCollapsible = page.querySelector('#similarCollapsible');

if (similarCollapsible) {
if (item.Type != 'Movie' && item.Type != 'Trailer' && item.Type != 'Series' && item.Type != 'Program' && item.Type != 'Recording' && item.Type != 'MusicAlbum' && item.Type != 'MusicArtist' && item.Type != 'Playlist') {
if (item.Type != 'Movie' && item.Type != 'Trailer' && item.Type != 'Series' && item.Type != 'Program' && item.Type != 'Recording' && item.Type != 'MusicAlbum' && item.Type != 'MusicArtist' && item.Type != 'Playlist' && item.Type != 'Audio') {
similarCollapsible.classList.add('hide');
return;
}
Expand Down
6 changes: 6 additions & 0 deletions src/controllers/lyrics.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<div id="lyricPage" data-role="page" class="page lyricPage" data-backbutton="true">
<div>
<div class="dynamicLyricsContainer padded-bottom-page">
</div>
</div>
</div>
Loading
Loading