adding playing time for a playlist
This commit is contained in:
+4
-1
@@ -984,11 +984,14 @@
|
|||||||
'name (browser-entry-name entry)
|
'name (browser-entry-name entry)
|
||||||
'kind (symbol->string (browser-entry-kind entry))))
|
'kind (symbol->string (browser-entry-kind entry))))
|
||||||
|
|
||||||
|
;;; Include the sum of known track durations in each tab's browser summary.
|
||||||
(define (tab->jsexpr tab index)
|
(define (tab->jsexpr tab index)
|
||||||
(hasheq 'index index
|
(hasheq 'index index
|
||||||
'id (playlist-tab-id tab)
|
'id (playlist-tab-id tab)
|
||||||
'name (playlist-tab-name tab)
|
'name (playlist-tab-name tab)
|
||||||
'count (length (playlist-tab-tracks tab))))
|
'count (length (playlist-tab-tracks tab))
|
||||||
|
'duration (apply + (map (λ (item) (or (track-duration item) 0))
|
||||||
|
(playlist-tab-tracks tab)))))
|
||||||
|
|
||||||
(define (normal-device-id device)
|
(define (normal-device-id device)
|
||||||
(let ((id (upnp-device-udn device)))
|
(let ((id (upnp-device-udn device)))
|
||||||
|
|||||||
+23
-1
@@ -94,6 +94,27 @@ function formatTime(value) {
|
|||||||
return `${hours}:${minutes}:${seconds}`;
|
return `${hours}:${minutes}:${seconds}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Formats total playing time in the interface language, rounded to minutes.
|
||||||
|
function formatPlayingTime(value) {
|
||||||
|
const totalMinutes = Number.isFinite(value) ? Math.max(0, Math.round(value / 60)) : 0;
|
||||||
|
const hours = Math.floor(totalMinutes / 60);
|
||||||
|
const minutes = totalMinutes % 60;
|
||||||
|
const language = window.RktTranslate.language();
|
||||||
|
const parts = [];
|
||||||
|
if (hours > 0) {
|
||||||
|
parts.push(new Intl.NumberFormat(language, {
|
||||||
|
style: "unit", unit: "hour", unitDisplay: "long",
|
||||||
|
}).format(hours));
|
||||||
|
}
|
||||||
|
if (minutes > 0 || hours === 0) {
|
||||||
|
parts.push(new Intl.NumberFormat(language, {
|
||||||
|
style: "unit", unit: "minute", unitDisplay: "long",
|
||||||
|
}).format(minutes));
|
||||||
|
}
|
||||||
|
const duration = new Intl.ListFormat(language, { style: "long", type: "conjunction" }).format(parts);
|
||||||
|
return t("playingTime", { duration });
|
||||||
|
}
|
||||||
|
|
||||||
// Replaces the current status message, using an empty string for no message.
|
// Replaces the current status message, using an empty string for no message.
|
||||||
function setStatus(message) {
|
function setStatus(message) {
|
||||||
elements.status.textContent = message || "";
|
elements.status.textContent = message || "";
|
||||||
@@ -504,7 +525,8 @@ function renderPlaylist(nextState) {
|
|||||||
|
|
||||||
elements.playlistEmpty.hidden = nextState.tracks.length > 0;
|
elements.playlistEmpty.hidden = nextState.tracks.length > 0;
|
||||||
const trackLabel = nextState.tracks.length === 1 ? "oneTrack" : "manyTracks";
|
const trackLabel = nextState.tracks.length === 1 ? "oneTrack" : "manyTracks";
|
||||||
elements.count.textContent = `${nextState.tracks.length} ${t(trackLabel)}`;
|
const tab = nextState.tabs[nextState.currentTab];
|
||||||
|
elements.count.textContent = `${nextState.tracks.length} ${t(trackLabel)}, ${formatPlayingTime(tab.duration)}`;
|
||||||
elements.playlistClear.disabled = nextState.tracks.length === 0;
|
elements.playlistClear.disabled = nextState.tracks.length === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
const translations = {
|
const translations = {
|
||||||
en: {
|
en: {
|
||||||
|
playingTime: "{duration} playing time",
|
||||||
output: "OUTPUT", searchPlayers: "Search for network players", logout: "LOG OUT",
|
output: "OUTPUT", searchPlayers: "Search for network players", logout: "LOG OUT",
|
||||||
rendererLocal: "LOCAL", defaultPlaylist: "Default",
|
rendererLocal: "LOCAL", defaultPlaylist: "Default",
|
||||||
playbackControls: "Playback controls", previousTrack: "Previous track",
|
playbackControls: "Playback controls", previousTrack: "Previous track",
|
||||||
@@ -33,6 +34,7 @@
|
|||||||
"dlna-renderer-unreachable": "The DLNA renderer is unreachable.",
|
"dlna-renderer-unreachable": "The DLNA renderer is unreachable.",
|
||||||
},
|
},
|
||||||
nl: {
|
nl: {
|
||||||
|
playingTime: "{duration} speeltijd",
|
||||||
output: "UITVOER", searchPlayers: "Netwerkspelers zoeken", logout: "UITLOGGEN",
|
output: "UITVOER", searchPlayers: "Netwerkspelers zoeken", logout: "UITLOGGEN",
|
||||||
rendererLocal: "LOKAAL", defaultPlaylist: "Standaard",
|
rendererLocal: "LOKAAL", defaultPlaylist: "Standaard",
|
||||||
playbackControls: "Afspeelbediening", previousTrack: "Vorige track",
|
playbackControls: "Afspeelbediening", previousTrack: "Vorige track",
|
||||||
@@ -63,6 +65,7 @@
|
|||||||
"dlna-renderer-unreachable": "De DLNA-renderer is niet bereikbaar.",
|
"dlna-renderer-unreachable": "De DLNA-renderer is niet bereikbaar.",
|
||||||
},
|
},
|
||||||
de: {
|
de: {
|
||||||
|
playingTime: "{duration} Spielzeit",
|
||||||
output: "AUSGABE", searchPlayers: "Netzwerkplayer suchen", logout: "ABMELDEN",
|
output: "AUSGABE", searchPlayers: "Netzwerkplayer suchen", logout: "ABMELDEN",
|
||||||
rendererLocal: "LOKAL", defaultPlaylist: "Standard",
|
rendererLocal: "LOKAL", defaultPlaylist: "Standard",
|
||||||
playbackControls: "Wiedergabesteuerung", previousTrack: "Vorheriger Titel",
|
playbackControls: "Wiedergabesteuerung", previousTrack: "Vorheriger Titel",
|
||||||
@@ -93,6 +96,7 @@
|
|||||||
"dlna-renderer-unreachable": "Der DLNA-Renderer ist nicht erreichbar.",
|
"dlna-renderer-unreachable": "Der DLNA-Renderer ist nicht erreichbar.",
|
||||||
},
|
},
|
||||||
fr: {
|
fr: {
|
||||||
|
playingTime: "{duration} de lecture",
|
||||||
output: "SORTIE", searchPlayers: "Rechercher les lecteurs réseau", logout: "DÉCONNEXION",
|
output: "SORTIE", searchPlayers: "Rechercher les lecteurs réseau", logout: "DÉCONNEXION",
|
||||||
rendererLocal: "LOCAL", defaultPlaylist: "Par défaut",
|
rendererLocal: "LOCAL", defaultPlaylist: "Par défaut",
|
||||||
playbackControls: "Commandes de lecture", previousTrack: "Piste précédente",
|
playbackControls: "Commandes de lecture", previousTrack: "Piste précédente",
|
||||||
@@ -123,6 +127,7 @@
|
|||||||
"dlna-renderer-unreachable": "Le lecteur DLNA est inaccessible.",
|
"dlna-renderer-unreachable": "Le lecteur DLNA est inaccessible.",
|
||||||
},
|
},
|
||||||
es: {
|
es: {
|
||||||
|
playingTime: "{duration} de reproducción",
|
||||||
output: "SALIDA", searchPlayers: "Buscar reproductores de red", logout: "CERRAR SESIÓN",
|
output: "SALIDA", searchPlayers: "Buscar reproductores de red", logout: "CERRAR SESIÓN",
|
||||||
rendererLocal: "LOCAL", defaultPlaylist: "Predeterminada",
|
rendererLocal: "LOCAL", defaultPlaylist: "Predeterminada",
|
||||||
playbackControls: "Controles de reproducción", previousTrack: "Pista anterior",
|
playbackControls: "Controles de reproducción", previousTrack: "Pista anterior",
|
||||||
@@ -153,6 +158,7 @@
|
|||||||
"dlna-renderer-unreachable": "No se puede acceder al renderizador DLNA.",
|
"dlna-renderer-unreachable": "No se puede acceder al renderizador DLNA.",
|
||||||
},
|
},
|
||||||
it: {
|
it: {
|
||||||
|
playingTime: "{duration} di riproduzione",
|
||||||
output: "USCITA", searchPlayers: "Cerca lettori di rete", logout: "ESCI",
|
output: "USCITA", searchPlayers: "Cerca lettori di rete", logout: "ESCI",
|
||||||
rendererLocal: "LOCALE", defaultPlaylist: "Predefinita",
|
rendererLocal: "LOCALE", defaultPlaylist: "Predefinita",
|
||||||
playbackControls: "Controlli di riproduzione", previousTrack: "Traccia precedente",
|
playbackControls: "Controlli di riproduzione", previousTrack: "Traccia precedente",
|
||||||
@@ -183,6 +189,7 @@
|
|||||||
"dlna-renderer-unreachable": "Il renderer DLNA non è raggiungibile.",
|
"dlna-renderer-unreachable": "Il renderer DLNA non è raggiungibile.",
|
||||||
},
|
},
|
||||||
sv: {
|
sv: {
|
||||||
|
playingTime: "{duration} speltid",
|
||||||
output: "UTGÅNG", searchPlayers: "Sök efter nätverksspelare", logout: "LOGGA UT",
|
output: "UTGÅNG", searchPlayers: "Sök efter nätverksspelare", logout: "LOGGA UT",
|
||||||
rendererLocal: "LOKAL", defaultPlaylist: "Standard",
|
rendererLocal: "LOKAL", defaultPlaylist: "Standard",
|
||||||
playbackControls: "Uppspelningskontroller", previousTrack: "Föregående spår",
|
playbackControls: "Uppspelningskontroller", previousTrack: "Föregående spår",
|
||||||
@@ -213,6 +220,7 @@
|
|||||||
"dlna-renderer-unreachable": "DLNA-renderaren kan inte nås.",
|
"dlna-renderer-unreachable": "DLNA-renderaren kan inte nås.",
|
||||||
},
|
},
|
||||||
no: {
|
no: {
|
||||||
|
playingTime: "{duration} spilletid",
|
||||||
output: "UTGANG", searchPlayers: "Søk etter nettverksspillere", logout: "LOGG UT",
|
output: "UTGANG", searchPlayers: "Søk etter nettverksspillere", logout: "LOGG UT",
|
||||||
rendererLocal: "LOKAL", defaultPlaylist: "Standard",
|
rendererLocal: "LOKAL", defaultPlaylist: "Standard",
|
||||||
playbackControls: "Avspillingskontroller", previousTrack: "Forrige spor",
|
playbackControls: "Avspillingskontroller", previousTrack: "Forrige spor",
|
||||||
@@ -243,6 +251,7 @@
|
|||||||
"dlna-renderer-unreachable": "DLNA-gjengiveren kan ikke nås.",
|
"dlna-renderer-unreachable": "DLNA-gjengiveren kan ikke nås.",
|
||||||
},
|
},
|
||||||
fi: {
|
fi: {
|
||||||
|
playingTime: "Toistoaika: {duration}",
|
||||||
output: "ULOSTULO", searchPlayers: "Etsi verkkosoittimia", logout: "KIRJAUDU ULOS",
|
output: "ULOSTULO", searchPlayers: "Etsi verkkosoittimia", logout: "KIRJAUDU ULOS",
|
||||||
rendererLocal: "PAIKALLINEN", defaultPlaylist: "Oletus",
|
rendererLocal: "PAIKALLINEN", defaultPlaylist: "Oletus",
|
||||||
playbackControls: "Toiston ohjaimet", previousTrack: "Edellinen kappale",
|
playbackControls: "Toiston ohjaimet", previousTrack: "Edellinen kappale",
|
||||||
@@ -273,6 +282,7 @@
|
|||||||
"dlna-renderer-unreachable": "DLNA-toistimeen ei saada yhteyttä.",
|
"dlna-renderer-unreachable": "DLNA-toistimeen ei saada yhteyttä.",
|
||||||
},
|
},
|
||||||
is: {
|
is: {
|
||||||
|
playingTime: "Spilunartími: {duration}",
|
||||||
output: "ÚTTAK", searchPlayers: "Leita að netspilurum", logout: "SKRÁ ÚT",
|
output: "ÚTTAK", searchPlayers: "Leita að netspilurum", logout: "SKRÁ ÚT",
|
||||||
rendererLocal: "STAÐBUNDIÐ", defaultPlaylist: "Sjálfgefið",
|
rendererLocal: "STAÐBUNDIÐ", defaultPlaylist: "Sjálfgefið",
|
||||||
playbackControls: "Stýringar spilunar", previousTrack: "Fyrra lag",
|
playbackControls: "Stýringar spilunar", previousTrack: "Fyrra lag",
|
||||||
|
|||||||
@@ -33,7 +33,9 @@ The central server is the heart of the application. Its main features are:
|
|||||||
device, discovered UPnP renderers, Sonos groups, or registered agents.}
|
device, discovered UPnP renderers, Sonos groups, or registered agents.}
|
||||||
@item{Playlist tabs with adding, removing, reordering, renaming and
|
@item{Playlist tabs with adding, removing, reordering, renaming and
|
||||||
drag-and-drop support. Tracks from different libraries can share a
|
drag-and-drop support. Tracks from different libraries can share a
|
||||||
playlist.}
|
playlist. The playlist toolbar shows the selected playlist's track count
|
||||||
|
and total known playing time, rounded to minutes and expressed in hours
|
||||||
|
and minutes. Tracks without duration metadata contribute zero to the total.}
|
||||||
@item{Gapless network playback where the renderer or playback agent supports
|
@item{Gapless network playback where the renderer or playback agent supports
|
||||||
preloading the next track, with a server-side fallback.}
|
preloading the next track, with a server-side fallback.}
|
||||||
@item{Transport controls, seeking, volume, repeat mode and live playback
|
@item{Transport controls, seeking, volume, repeat mode and live playback
|
||||||
|
|||||||
Reference in New Issue
Block a user