adding playing time for a playlist

This commit is contained in:
2026-09-08 10:18:36 +02:00
parent 365a7b8317
commit 4b9e6c5651
4 changed files with 40 additions and 3 deletions
+4 -1
View File
@@ -984,11 +984,14 @@
'name (browser-entry-name 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)
(hasheq 'index index
'id (playlist-tab-id 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)
(let ((id (upnp-device-udn device)))
+23 -1
View File
@@ -94,6 +94,27 @@ function formatTime(value) {
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.
function setStatus(message) {
elements.status.textContent = message || "";
@@ -504,7 +525,8 @@ function renderPlaylist(nextState) {
elements.playlistEmpty.hidden = nextState.tracks.length > 0;
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;
}
+10
View File
@@ -3,6 +3,7 @@
const translations = {
en: {
playingTime: "{duration} playing time",
output: "OUTPUT", searchPlayers: "Search for network players", logout: "LOG OUT",
rendererLocal: "LOCAL", defaultPlaylist: "Default",
playbackControls: "Playback controls", previousTrack: "Previous track",
@@ -33,6 +34,7 @@
"dlna-renderer-unreachable": "The DLNA renderer is unreachable.",
},
nl: {
playingTime: "{duration} speeltijd",
output: "UITVOER", searchPlayers: "Netwerkspelers zoeken", logout: "UITLOGGEN",
rendererLocal: "LOKAAL", defaultPlaylist: "Standaard",
playbackControls: "Afspeelbediening", previousTrack: "Vorige track",
@@ -63,6 +65,7 @@
"dlna-renderer-unreachable": "De DLNA-renderer is niet bereikbaar.",
},
de: {
playingTime: "{duration} Spielzeit",
output: "AUSGABE", searchPlayers: "Netzwerkplayer suchen", logout: "ABMELDEN",
rendererLocal: "LOKAL", defaultPlaylist: "Standard",
playbackControls: "Wiedergabesteuerung", previousTrack: "Vorheriger Titel",
@@ -93,6 +96,7 @@
"dlna-renderer-unreachable": "Der DLNA-Renderer ist nicht erreichbar.",
},
fr: {
playingTime: "{duration} de lecture",
output: "SORTIE", searchPlayers: "Rechercher les lecteurs réseau", logout: "DÉCONNEXION",
rendererLocal: "LOCAL", defaultPlaylist: "Par défaut",
playbackControls: "Commandes de lecture", previousTrack: "Piste précédente",
@@ -123,6 +127,7 @@
"dlna-renderer-unreachable": "Le lecteur DLNA est inaccessible.",
},
es: {
playingTime: "{duration} de reproducción",
output: "SALIDA", searchPlayers: "Buscar reproductores de red", logout: "CERRAR SESIÓN",
rendererLocal: "LOCAL", defaultPlaylist: "Predeterminada",
playbackControls: "Controles de reproducción", previousTrack: "Pista anterior",
@@ -153,6 +158,7 @@
"dlna-renderer-unreachable": "No se puede acceder al renderizador DLNA.",
},
it: {
playingTime: "{duration} di riproduzione",
output: "USCITA", searchPlayers: "Cerca lettori di rete", logout: "ESCI",
rendererLocal: "LOCALE", defaultPlaylist: "Predefinita",
playbackControls: "Controlli di riproduzione", previousTrack: "Traccia precedente",
@@ -183,6 +189,7 @@
"dlna-renderer-unreachable": "Il renderer DLNA non è raggiungibile.",
},
sv: {
playingTime: "{duration} speltid",
output: "UTGÅNG", searchPlayers: "Sök efter nätverksspelare", logout: "LOGGA UT",
rendererLocal: "LOKAL", defaultPlaylist: "Standard",
playbackControls: "Uppspelningskontroller", previousTrack: "Föregående spår",
@@ -213,6 +220,7 @@
"dlna-renderer-unreachable": "DLNA-renderaren kan inte nås.",
},
no: {
playingTime: "{duration} spilletid",
output: "UTGANG", searchPlayers: "Søk etter nettverksspillere", logout: "LOGG UT",
rendererLocal: "LOKAL", defaultPlaylist: "Standard",
playbackControls: "Avspillingskontroller", previousTrack: "Forrige spor",
@@ -243,6 +251,7 @@
"dlna-renderer-unreachable": "DLNA-gjengiveren kan ikke nås.",
},
fi: {
playingTime: "Toistoaika: {duration}",
output: "ULOSTULO", searchPlayers: "Etsi verkkosoittimia", logout: "KIRJAUDU ULOS",
rendererLocal: "PAIKALLINEN", defaultPlaylist: "Oletus",
playbackControls: "Toiston ohjaimet", previousTrack: "Edellinen kappale",
@@ -273,6 +282,7 @@
"dlna-renderer-unreachable": "DLNA-toistimeen ei saada yhteyttä.",
},
is: {
playingTime: "Spilunartími: {duration}",
output: "ÚTTAK", searchPlayers: "Leita að netspilurum", logout: "SKRÁ ÚT",
rendererLocal: "STAÐBUNDIÐ", defaultPlaylist: "Sjálfgefið",
playbackControls: "Stýringar spilunar", previousTrack: "Fyrra lag",
+3 -1
View File
@@ -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.}
@item{Playlist tabs with adding, removing, reordering, renaming and
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
preloading the next track, with a server-side fallback.}
@item{Transport controls, seeking, volume, repeat mode and live playback