From e641d6f5ea6614e323e8bf94e7c91d3838ed1777 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Tue, 10 Jun 2025 02:39:30 -0400 Subject: [PATCH 1/3] Fix rtl lyric support and simplify rendering --- src/controllers/lyrics.html | 2 +- src/controllers/lyrics.js | 62 ++++++++++++++----------------------- src/styles/lyrics.scss | 2 +- 3 files changed, 26 insertions(+), 40 deletions(-) diff --git a/src/controllers/lyrics.html b/src/controllers/lyrics.html index d1e7b662c..79b923678 100644 --- a/src/controllers/lyrics.html +++ b/src/controllers/lyrics.html @@ -1,6 +1,6 @@
-
+
diff --git a/src/controllers/lyrics.js b/src/controllers/lyrics.js index 36ae408bc..79dab9832 100644 --- a/src/controllers/lyrics.js +++ b/src/controllers/lyrics.js @@ -23,21 +23,17 @@ let savedLyrics; let isDynamicLyric = false; let autoScroll = AutoScroll.Instant; -function dynamicLyricHtmlReducer(htmlAccumulator, lyric, index) { - if (layoutManager.tv) { - htmlAccumulator += ``; - } else { - htmlAccumulator += `
${escapeHtml(lyric.Text)}
`; - } - return htmlAccumulator; -} +function lyricHtmlReducer(htmlAccumulator, lyric, index) { + const elem = layoutManager.tv ? 'button' : 'div'; + const classes = []; + if (isDynamicLyric) classes.push('dynamicLyric'); + if (layoutManager.tv) classes.push('listItem', 'show-focus'); + const lyricTime = typeof lyric.Start !== 'undefined' ? `data-lyrictime="${lyric.Start}"` : ''; + + htmlAccumulator += `<${elem} class="lyricsLine ${classes.join(' ')}" id="lyricPosition${index}" ${lyricTime}> + ${escapeHtml(lyric.Text)} +`; -function staticLyricHtmlReducer(htmlAccumulator, lyric, index) { - if (layoutManager.tv) { - htmlAccumulator += ``; - } else { - htmlAccumulator += `
${escapeHtml(lyric.Text)}
`; - } return htmlAccumulator; } @@ -96,37 +92,31 @@ export default function (view) { } function renderNoLyricMessage() { - const itemsContainer = view.querySelector('.dynamicLyricsContainer'); + const itemsContainer = view.querySelector('.lyricsContainer'); if (itemsContainer) { - const html = `

${globalize.translate('HeaderNoLyrics')}

`; + const html = `

${globalize.translate('HeaderNoLyrics')}

`; itemsContainer.innerHTML = html; } autoFocuser.autoFocus(); } - function renderDynamicLyrics(lyrics) { - const itemsContainer = view.querySelector('.dynamicLyricsContainer'); + function renderLyrics(lyrics) { + const itemsContainer = view.querySelector('.lyricsContainer'); if (itemsContainer) { - const html = lyrics.reduce(dynamicLyricHtmlReducer, ''); + const html = lyrics.reduce(lyricHtmlReducer, ''); itemsContainer.innerHTML = html; } - const lyricLineArray = itemsContainer.querySelectorAll('.lyricsLine'); + if (isDynamicLyric) { + const lyricLineArray = itemsContainer.querySelectorAll('.lyricsLine'); - // attaches click event listener to change playtime to lyric start - lyricLineArray.forEach(element => { - element.addEventListener('click', () => onLyricClick(element.getAttribute('data-lyrictime'))); - }); + // attaches click event listener to change playtime to lyric start + lyricLineArray.forEach(element => { + element.addEventListener('click', () => onLyricClick(element.getAttribute('data-lyrictime'))); + }); - const currentIndex = getLyricIndex(getCurrentPlayTime(), lyrics); - updateAllLyricLines(currentIndex, savedLyrics); - } - - function renderStaticLyrics(lyrics) { - const itemsContainer = view.querySelector('.dynamicLyricsContainer'); - if (itemsContainer) { - const html = lyrics.reduce(staticLyricHtmlReducer, ''); - itemsContainer.innerHTML = html; + const currentIndex = getLyricIndex(getCurrentPlayTime(), lyrics); + updateAllLyricLines(currentIndex, savedLyrics); } } @@ -135,11 +125,7 @@ export default function (view) { isDynamicLyric = Object.prototype.hasOwnProperty.call(lyrics[0], 'Start'); - if (isDynamicLyric) { - renderDynamicLyrics(savedLyrics); - } else { - renderStaticLyrics(savedLyrics); - } + renderLyrics(savedLyrics); autoFocuser.autoFocus(view); } diff --git a/src/styles/lyrics.scss b/src/styles/lyrics.scss index 0adf81ebd..4e9a7128e 100644 --- a/src/styles/lyrics.scss +++ b/src/styles/lyrics.scss @@ -6,7 +6,7 @@ justify-content: center; } -.dynamicLyricsContainer { +.lyricsContainer { display: flex; flex-direction: column; } From 188aed31abe8c12c25a4bc7bda580c5dbca1300f Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Tue, 10 Jun 2025 02:46:10 -0400 Subject: [PATCH 2/3] Use sdk to fetch lyrics --- src/controllers/lyrics.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/controllers/lyrics.js b/src/controllers/lyrics.js index 79dab9832..fd1597433 100644 --- a/src/controllers/lyrics.js +++ b/src/controllers/lyrics.js @@ -1,9 +1,10 @@ +import { getLyricsApi } from '@jellyfin/sdk/lib/utils/api/lyrics-api'; import escapeHtml from 'escape-html'; import autoFocuser from 'components/autoFocuser'; -import { appRouter } from '../components/router/appRouter'; +import { appRouter } from 'components/router/appRouter'; import layoutManager from 'components/layoutManager'; -import { playbackManager } from '../components/playback/playbackmanager'; +import { playbackManager } from 'components/playback/playbackmanager'; import scrollManager from 'components/scrollManager'; import focusManager from 'components/focusManager'; @@ -12,6 +13,7 @@ import globalize from 'lib/globalize'; import { ServerConnections } from 'lib/jellyfin-apiclient'; import LibraryMenu from 'scripts/libraryMenu'; import Events from 'utils/events'; +import { toApi } from 'utils/jellyfin-apiclient/compat'; import '../styles/lyrics.scss'; import { AutoScroll } from './lyrics.types'; @@ -132,17 +134,15 @@ export default function (view) { function getLyrics(serverId, itemId) { const apiClient = ServerConnections.getApiClient(serverId); + const lyricsApi = getLyricsApi(toApi(apiClient)); - return apiClient.ajax({ - url: apiClient.getUrl('Audio/' + itemId + '/Lyrics'), - type: 'GET', - dataType: 'json' - }).then((response) => { - if (!response.Lyrics) { - throw new Error(); - } - return response.Lyrics; - }); + return lyricsApi.getLyrics({ itemId }) + .then(({ data }) => { + if (!data.Lyrics?.length) { + throw new Error('No lyrics returned'); + } + return data.Lyrics; + }); } function bindToPlayer(player) { From 16a267f6dd83167266a2bc1ee6ac7187cc85004a Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Tue, 10 Jun 2025 02:50:09 -0400 Subject: [PATCH 3/3] Move autoscroll enum to bulletproof structure --- .../stable/features/lyrics/constants/autoScroll.ts} | 0 src/controllers/lyrics.js | 5 ++--- 2 files changed, 2 insertions(+), 3 deletions(-) rename src/{controllers/lyrics.types.ts => apps/stable/features/lyrics/constants/autoScroll.ts} (100%) diff --git a/src/controllers/lyrics.types.ts b/src/apps/stable/features/lyrics/constants/autoScroll.ts similarity index 100% rename from src/controllers/lyrics.types.ts rename to src/apps/stable/features/lyrics/constants/autoScroll.ts diff --git a/src/controllers/lyrics.js b/src/controllers/lyrics.js index fd1597433..74f674140 100644 --- a/src/controllers/lyrics.js +++ b/src/controllers/lyrics.js @@ -1,22 +1,21 @@ import { getLyricsApi } from '@jellyfin/sdk/lib/utils/api/lyrics-api'; import escapeHtml from 'escape-html'; +import { AutoScroll } from 'apps/stable/features/lyrics/constants/autoScroll'; import autoFocuser from 'components/autoFocuser'; import { appRouter } from 'components/router/appRouter'; import layoutManager from 'components/layoutManager'; import { playbackManager } from 'components/playback/playbackmanager'; import scrollManager from 'components/scrollManager'; import focusManager from 'components/focusManager'; - -import keyboardNavigation from 'scripts/keyboardNavigation'; import globalize from 'lib/globalize'; import { ServerConnections } from 'lib/jellyfin-apiclient'; +import keyboardNavigation from 'scripts/keyboardNavigation'; import LibraryMenu from 'scripts/libraryMenu'; import Events from 'utils/events'; import { toApi } from 'utils/jellyfin-apiclient/compat'; import '../styles/lyrics.scss'; -import { AutoScroll } from './lyrics.types'; let currentPlayer; let currentItem;