diff --git a/index.html b/index.html
index 4feffff..67405a3 100644
--- a/index.html
+++ b/index.html
@@ -3908,6 +3908,18 @@
+
Reset Local Data
diff --git a/js/analytics.js b/js/analytics.js
index d1d33bc..fce55e0 100644
--- a/js/analytics.js
+++ b/js/analytics.js
@@ -1,11 +1,22 @@
// js/analytics.js - Plausible Analytics custom event tracking
+import { analyticsSettings } from './storage.js';
+
+/**
+ * Check if analytics is enabled
+ * @returns {boolean}
+ */
+function isAnalyticsEnabled() {
+ return analyticsSettings.isEnabled();
+}
+
/**
* Track a custom event with Plausible
* @param {string} eventName - The name of the event
* @param {object} [props] - Optional event properties
*/
export function trackEvent(eventName, props = {}) {
+ if (!isAnalyticsEnabled()) return;
if (window.plausible) {
try {
window.plausible(eventName, { props });
@@ -26,26 +37,40 @@ export function trackPageView(path) {
// Playback Events
export function trackPlayTrack(track) {
trackEvent('Play Track', {
+ track_id: track?.id || 'unknown',
track_title: track?.title || 'Unknown',
+ artist_id: track?.artist?.id || track?.artists?.[0]?.id || 'unknown',
artist: track?.artist?.name || track?.artists?.[0]?.name || 'Unknown',
+ album_id: track?.album?.id || 'unknown',
album: track?.album?.title || 'Unknown',
duration: track?.duration || 0,
quality: track?.audioQuality || track?.quality || 'Unknown',
is_local: track?.isLocal || false,
+ is_explicit: track?.explicit || false,
+ track_number: track?.trackNumber || 0,
+ year: track?.album?.releaseYear || track?.album?.releaseDate || 'unknown',
});
}
export function trackPauseTrack(track) {
trackEvent('Pause Track', {
+ track_id: track?.id || 'unknown',
track_title: track?.title || 'Unknown',
+ artist_id: track?.artist?.id || track?.artists?.[0]?.id || 'unknown',
artist: track?.artist?.name || track?.artists?.[0]?.name || 'Unknown',
+ album_id: track?.album?.id || 'unknown',
+ album: track?.album?.title || 'Unknown',
});
}
export function trackSkipTrack(track, direction) {
trackEvent('Skip Track', {
+ track_id: track?.id || 'unknown',
track_title: track?.title || 'Unknown',
+ artist_id: track?.artist?.id || track?.artists?.[0]?.id || 'unknown',
artist: track?.artist?.name || track?.artists?.[0]?.name || 'Unknown',
+ album_id: track?.album?.id || 'unknown',
+ album: track?.album?.title || 'Unknown',
direction: direction,
});
}
@@ -58,6 +83,19 @@ export function trackToggleRepeat(mode) {
trackEvent('Toggle Repeat', { mode });
}
+export function trackTrackComplete(track, completionPercent) {
+ trackEvent('Track Complete', {
+ track_id: track?.id || 'unknown',
+ track_title: track?.title || 'Unknown',
+ artist_id: track?.artist?.id || track?.artists?.[0]?.id || 'unknown',
+ artist: track?.artist?.name || track?.artists?.[0]?.name || 'Unknown',
+ album_id: track?.album?.id || 'unknown',
+ album: track?.album?.title || 'Unknown',
+ duration: track?.duration || 0,
+ completion_percent: completionPercent || 100,
+ });
+}
+
export function trackSetVolume(level) {
// Only track volume changes at coarse intervals to avoid spam
const roundedLevel = Math.round(level * 10) / 10;
@@ -80,6 +118,16 @@ export function trackSeek(position, duration) {
}
}
+// Track listening progress milestones (10%, 25%, 50%, 75%, 90%, 100%)
+export function trackListeningProgress(track, percent) {
+ trackEvent('Listening Progress', {
+ track_id: track?.id || 'unknown',
+ track_title: track?.title || 'Unknown',
+ artist_id: track?.artist?.id || track?.artists?.[0]?.id || 'unknown',
+ percent: percent,
+ });
+}
+
// Search Events
export function trackSearch(query, resultsCount) {
trackEvent('Search', {
@@ -108,15 +156,22 @@ export function trackSidebarNavigation(item) {
// Library Events
export function trackLikeTrack(track) {
trackEvent('Like Track', {
+ track_id: track?.id || 'unknown',
track_title: track?.title || 'Unknown',
+ artist_id: track?.artist?.id || track?.artists?.[0]?.id || 'unknown',
artist: track?.artist?.name || track?.artists?.[0]?.name || 'Unknown',
+ album_id: track?.album?.id || 'unknown',
+ album: track?.album?.title || 'Unknown',
});
}
export function trackUnlikeTrack(track) {
trackEvent('Unlike Track', {
+ track_id: track?.id || 'unknown',
track_title: track?.title || 'Unknown',
+ artist_id: track?.artist?.id || track?.artists?.[0]?.id || 'unknown',
artist: track?.artist?.name || track?.artists?.[0]?.name || 'Unknown',
+ album_id: track?.album?.id || 'unknown',
});
}
@@ -204,23 +259,29 @@ export function trackDeleteFolder(folderName) {
// Playback Actions
export function trackPlayAlbum(album, shuffle) {
trackEvent('Play Album', {
+ album_id: album?.id || 'unknown',
album_title: album?.title || 'Unknown',
+ artist_id: album?.artist?.id || 'unknown',
artist: album?.artist?.name || 'Unknown',
shuffle: shuffle || false,
track_count: album?.numberOfTracks || album?.tracks?.length || 0,
+ year: album?.releaseYear || album?.releaseDate || 'unknown',
});
}
export function trackPlayPlaylist(playlist, shuffle) {
trackEvent('Play Playlist', {
+ playlist_id: playlist?.id || 'unknown',
playlist_name: playlist?.title || playlist?.name || 'Unknown',
shuffle: shuffle || false,
track_count: playlist?.tracks?.length || 0,
+ is_public: playlist?.isPublic || false,
});
}
export function trackPlayArtistRadio(artist) {
trackEvent('Play Artist Radio', {
+ artist_id: artist?.id || 'unknown',
artist_name: artist?.name || 'Unknown',
});
}
@@ -232,15 +293,20 @@ export function trackShuffleLikedTracks(count) {
// Download Events
export function trackDownloadTrack(track, quality) {
trackEvent('Download Track', {
+ track_id: track?.id || 'unknown',
track_title: track?.title || 'Unknown',
+ artist_id: track?.artist?.id || track?.artists?.[0]?.id || 'unknown',
artist: track?.artist?.name || track?.artists?.[0]?.name || 'Unknown',
+ album_id: track?.album?.id || 'unknown',
quality: quality || 'Unknown',
});
}
export function trackDownloadAlbum(album, quality) {
trackEvent('Download Album', {
+ album_id: album?.id || 'unknown',
album_title: album?.title || 'Unknown',
+ artist_id: album?.artist?.id || 'unknown',
artist: album?.artist?.name || 'Unknown',
track_count: album?.numberOfTracks || album?.tracks?.length || 0,
quality: quality || 'Unknown',
@@ -249,6 +315,7 @@ export function trackDownloadAlbum(album, quality) {
export function trackDownloadPlaylist(playlist, quality) {
trackEvent('Download Playlist', {
+ playlist_id: playlist?.id || 'unknown',
playlist_name: playlist?.title || playlist?.name || 'Unknown',
track_count: playlist?.tracks?.length || 0,
quality: quality || 'Unknown',
@@ -264,6 +331,7 @@ export function trackDownloadLikedTracks(count, quality) {
export function trackDownloadDiscography(artist, selection) {
trackEvent('Download Discography', {
+ artist_id: artist?.id || 'unknown',
artist_name: artist?.name || 'Unknown',
include_albums: selection?.includeAlbums || false,
include_eps: selection?.includeEPs || false,
@@ -274,16 +342,22 @@ export function trackDownloadDiscography(artist, selection) {
// Queue Management
export function trackAddToQueue(track, position) {
trackEvent('Add to Queue', {
+ track_id: track?.id || 'unknown',
track_title: track?.title || 'Unknown',
+ artist_id: track?.artist?.id || track?.artists?.[0]?.id || 'unknown',
artist: track?.artist?.name || track?.artists?.[0]?.name || 'Unknown',
+ album_id: track?.album?.id || 'unknown',
position: position || 'end',
});
}
export function trackPlayNext(track) {
trackEvent('Play Next', {
+ track_id: track?.id || 'unknown',
track_title: track?.title || 'Unknown',
+ artist_id: track?.artist?.id || track?.artists?.[0]?.id || 'unknown',
artist: track?.artist?.name || track?.artists?.[0]?.name || 'Unknown',
+ album_id: track?.album?.id || 'unknown',
});
}
@@ -306,37 +380,46 @@ export function trackContextMenuAction(action, itemType, item) {
export function trackBlockTrack(track) {
trackEvent('Block Track', {
+ track_id: track?.id || 'unknown',
track_title: track?.title || 'Unknown',
+ artist_id: track?.artist?.id || track?.artists?.[0]?.id || 'unknown',
artist: track?.artist?.name || track?.artists?.[0]?.name || 'Unknown',
+ album_id: track?.album?.id || 'unknown',
});
}
export function trackUnblockTrack(track) {
trackEvent('Unblock Track', {
+ track_id: track?.id || 'unknown',
track_title: track?.title || 'Unknown',
});
}
export function trackBlockAlbum(album) {
trackEvent('Block Album', {
+ album_id: album?.id || 'unknown',
album_title: album?.title || 'Unknown',
+ artist_id: album?.artist?.id || 'unknown',
});
}
export function trackUnblockAlbum(album) {
trackEvent('Unblock Album', {
+ album_id: album?.id || 'unknown',
album_title: album?.title || 'Unknown',
});
}
export function trackBlockArtist(artist) {
trackEvent('Block Artist', {
+ artist_id: artist?.id || 'unknown',
artist_name: artist?.name || 'Unknown',
});
}
export function trackUnblockArtist(artist) {
trackEvent('Unblock Artist', {
+ artist_id: artist?.id || 'unknown',
artist_name: artist?.name || 'Unknown',
});
}
@@ -634,6 +717,8 @@ export function trackSessionEnd(duration) {
// Initialize analytics on page load
export function initAnalytics() {
+ if (!isAnalyticsEnabled()) return;
+
// Track initial page view
trackPageView(window.location.pathname);
diff --git a/js/settings.js b/js/settings.js
index 54dcdc5..a2bd5f7 100644
--- a/js/settings.js
+++ b/js/settings.js
@@ -31,6 +31,7 @@ import {
pwaUpdateSettings,
contentBlockingSettings,
musicProviderSettings,
+ analyticsSettings,
} from './storage.js';
import { audioContextManager, EQ_PRESETS } from './audio-context.js';
import { getButterchurnPresets } from './visualizers/butterchurn.js';
@@ -2363,6 +2364,15 @@ export function initializeSettings(scrobbler, player, api, ui) {
});
}
+ // Analytics Toggle
+ const analyticsToggle = document.getElementById('analytics-toggle');
+ if (analyticsToggle) {
+ analyticsToggle.checked = analyticsSettings.isEnabled();
+ analyticsToggle.addEventListener('change', (e) => {
+ analyticsSettings.setEnabled(e.target.checked);
+ });
+ }
+
// Reset Local Data Button
const resetLocalDataBtn = document.getElementById('reset-local-data-btn');
if (resetLocalDataBtn) {
diff --git a/js/storage.js b/js/storage.js
index 1733ada..5e71076 100644
--- a/js/storage.js
+++ b/js/storage.js
@@ -1481,6 +1481,23 @@ export const homePageSettings = {
},
};
+export const analyticsSettings = {
+ ENABLED_KEY: 'analytics-enabled',
+
+ isEnabled() {
+ try {
+ const val = localStorage.getItem(this.ENABLED_KEY);
+ return val === null ? true : val === 'true';
+ } catch {
+ return true;
+ }
+ },
+
+ setEnabled(enabled) {
+ localStorage.setItem(this.ENABLED_KEY, enabled ? 'true' : 'false');
+ },
+};
+
export const sidebarSectionSettings = {
SHOW_HOME_KEY: 'sidebar-show-home',
SHOW_LIBRARY_KEY: 'sidebar-show-library',