Gapless Playback
diff --git a/js/player.js b/js/player.js
index 72ad1e8..4412a82 100644
--- a/js/player.js
+++ b/js/player.js
@@ -1,6 +1,6 @@
//js/player.js
import { MediaPlayer } from 'dashjs';
-import { REPEAT_MODE, formatTime, getTrackArtists, getTrackTitle, getTrackArtistsHTML } from './utils.js';
+import { REPEAT_MODE, formatTime, getTrackArtists, getTrackTitle, getTrackArtistsHTML, createQualityBadgeHTML } from './utils.js';
import { queueManager, replayGainSettings } from './storage.js';
export class Player {
@@ -119,7 +119,10 @@ export class Player {
const artistEl = document.querySelector('.now-playing-bar .artist');
if (coverEl) coverEl.src = this.api.getCoverUrl(track.album?.cover);
- if (titleEl) titleEl.textContent = trackTitle;
+ if (titleEl) {
+ const qualityBadge = createQualityBadgeHTML(track);
+ titleEl.innerHTML = `${trackTitle} ${qualityBadge}`;
+ }
if (artistEl) artistEl.innerHTML = trackArtistsHTML + yearDisplay;
const mixBtn = document.getElementById('now-playing-mix-btn');
@@ -232,7 +235,8 @@ export class Player {
}
}
- async playTrackFromQueue(startTime = 0) {
+ async playTrack(track, options = {}) {
+ const { startTime = 0 } = options;
const currentQueue = this.shuffleActive ? this.shuffledQueue : this.queue;
if (this.currentQueueIndex < 0 || this.currentQueueIndex >= currentQueue.length) {
return;
@@ -240,7 +244,6 @@ export class Player {
this.saveQueueState();
- const track = currentQueue[this.currentQueueIndex];
this.currentTrack = track;
const trackTitle = getTrackTitle(track);
@@ -256,7 +259,8 @@ export class Player {
}
document.querySelector('.now-playing-bar .cover').src = this.api.getCoverUrl(track.album?.cover);
- document.querySelector('.now-playing-bar .title').textContent = trackTitle;
+ const qualityBadge = createQualityBadgeHTML(track);
+ document.querySelector('.now-playing-bar .title').innerHTML = `${trackTitle} ${qualityBadge}`;
document.querySelector('.now-playing-bar .artist').innerHTML = trackArtistsHTML + yearDisplay;
const mixBtn = document.getElementById('now-playing-mix-btn');
diff --git a/js/settings.js b/js/settings.js
index 4650278..4acd6cb 100644
--- a/js/settings.js
+++ b/js/settings.js
@@ -11,6 +11,7 @@ import {
replayGainSettings,
smoothScrollingSettings,
downloadQualitySettings,
+ qualityBadgeSettings,
} from './storage.js';
import { db } from './db.js';
import { authManager } from './accounts/auth.js';
@@ -272,6 +273,18 @@ export function initializeSettings(scrobbler, player, api, ui) {
});
}
+ // Quality Badge Settings
+ const showQualityBadgesToggle = document.getElementById('show-quality-badges-toggle');
+ if (showQualityBadgesToggle) {
+ showQualityBadgesToggle.checked = qualityBadgeSettings.isEnabled();
+ showQualityBadgesToggle.addEventListener('change', (e) => {
+ qualityBadgeSettings.setEnabled(e.target.checked);
+ // Re-render to reflect changes
+ ui.renderLibraryPage();
+ if (window.renderQueueFunction) window.renderQueueFunction();
+ });
+ }
+
// ReplayGain Settings
const replayGainMode = document.getElementById('replay-gain-mode');
if (replayGainMode) {
diff --git a/js/storage.js b/js/storage.js
index f620707..ebdefdf 100644
--- a/js/storage.js
+++ b/js/storage.js
@@ -552,6 +552,23 @@ export const smoothScrollingSettings = {
},
};
+export const qualityBadgeSettings = {
+ STORAGE_KEY: 'show-quality-badges',
+
+ isEnabled() {
+ try {
+ const val = localStorage.getItem(this.STORAGE_KEY);
+ return val === null ? true : val === 'true';
+ } catch {
+ return true;
+ }
+ },
+
+ setEnabled(enabled) {
+ localStorage.setItem(this.STORAGE_KEY, enabled ? 'true' : 'false');
+ },
+};
+
export const queueManager = {
STORAGE_KEY: 'monochrome-queue',
diff --git a/js/ui.js b/js/ui.js
index 5ca00a7..bde3bff 100644
--- a/js/ui.js
+++ b/js/ui.js
@@ -10,6 +10,7 @@ import {
hasExplicitContent,
getTrackArtists,
getTrackTitle,
+ createQualityBadgeHTML,
calculateTotalDuration,
formatDuration,
escapeHtml,
@@ -176,6 +177,7 @@ export class UIRenderer {
const trackNumberHTML = `
${showCover ? trackImageHTML : displayIndex}
`;
const explicitBadge = hasExplicitContent(track) ? this.createExplicitBadge() : '';
+ const qualityBadge = createQualityBadgeHTML(track);
const trackArtists = getTrackArtists(track);
const trackTitle = getTrackTitle(track);
const isCurrentTrack = this.player?.currentTrack?.id === track.id;
@@ -233,6 +235,7 @@ export class UIRenderer {
${escapeHtml(trackTitle)}
${explicitBadge}
+ ${qualityBadge}
${escapeHtml(trackArtists)}${yearDisplay}
@@ -401,6 +404,7 @@ export class UIRenderer {
createAlbumCardHTML(album) {
const explicitBadge = hasExplicitContent(album) ? this.createExplicitBadge() : '';
+ const qualityBadge = createQualityBadgeHTML(album);
let yearDisplay = '';
if (album.releaseDate) {
const date = new Date(album.releaseDate);
@@ -417,7 +421,7 @@ export class UIRenderer {
type: 'album',
id: album.id,
href: `#album/${album.id}`,
- title: `${escapeHtml(album.title)} ${explicitBadge}`,
+ title: `${escapeHtml(album.title)} ${explicitBadge} ${qualityBadge}`,
subtitle: `${escapeHtml(album.artist?.name ?? '')} • ${yearDisplay}${typeLabel}`,
imageHTML: `
`,
actionButtonsHTML: `
@@ -629,7 +633,8 @@ export class UIRenderer {
const coverUrl = this.api.getCoverUrl(track.album?.cover, '1280');
image.src = coverUrl;
- title.textContent = track.title;
+ const qualityBadge = createQualityBadgeHTML(track);
+ title.innerHTML = `${escapeHtml(track.title)} ${qualityBadge}`;
artist.textContent = getTrackArtists(track);
if (nextTrack) {
diff --git a/js/utils.js b/js/utils.js
index 34192cc..6cc7b70 100644
--- a/js/utils.js
+++ b/js/utils.js
@@ -1,4 +1,5 @@
//js/utils.js
+import { qualityBadgeSettings } from './storage.js';
export const QUALITY = 'LOSSLESS';
@@ -123,10 +124,19 @@ export const normalizeQualityToken = (value) => {
return quality;
}
}
-
return null;
};
+export const createQualityBadgeHTML = (track) => {
+ if (!qualityBadgeSettings.isEnabled()) return '';
+
+ const quality = deriveTrackQuality(track);
+ if (quality === 'HI_RES_LOSSLESS') {
+ return '
';
+ }
+ return '';
+};
+
export const deriveQualityFromTags = (rawTags) => {
if (!Array.isArray(rawTags)) return null;
diff --git a/styles.css b/styles.css
index 189181e..6add99e 100644
--- a/styles.css
+++ b/styles.css
@@ -872,6 +872,18 @@ body.has-page-background .track-item:hover {
line-height: 1;
}
+.quality-hires {
+ background-color: var(--highlight);
+ color: var(--primary-foreground);
+ font-size: 0.6rem;
+ font-weight: 700;
+ padding: 0.15rem 0.3rem;
+ border-radius: 3px;
+ margin-left: 0.5rem;
+ vertical-align: middle;
+ line-height: 1;
+}
+
.track-list {
display: flex;
flex-direction: column;