diff --git a/now-playing/themes/simple/script.js b/now-playing/themes/simple/script.js index f2f0787..a318e97 100644 --- a/now-playing/themes/simple/script.js +++ b/now-playing/themes/simple/script.js @@ -112,7 +112,6 @@ function UpdatePlayerState(data) { // let progressPercent = durationMs > 0 ? (currentPositionMs / durationMs) * 100 : 0; // progressPercent = Math.min(100, Math.max(0, progressPercent)); // progressBarFill.style.width = `${progressPercent}%`; - // progressBarFill.style.setProperty('--accent-color', mediaProps.AccentColor); // } } else @@ -162,7 +161,6 @@ async function ChangeTrack(mediaProps) { // Extract the image source string (use fallback if Windows has no art) const newArtUrl = mediaProps.Thumbnail; - // const accent = mediaProps.AccentColor || "#ffffff"; // Set the image // backgroundLayer.style.backgroundImage = `url('${newArtUrl}')`; diff --git a/now-playing/themes/vinyl/images/vinyl.af b/now-playing/themes/vinyl/images/vinyl.af new file mode 100644 index 0000000..e5c8f12 Binary files /dev/null and b/now-playing/themes/vinyl/images/vinyl.af differ diff --git a/now-playing/themes/vinyl/images/vinyl.png b/now-playing/themes/vinyl/images/vinyl.png new file mode 100644 index 0000000..68f85ee Binary files /dev/null and b/now-playing/themes/vinyl/images/vinyl.png differ diff --git a/now-playing/themes/vinyl/index.html b/now-playing/themes/vinyl/index.html new file mode 100644 index 0000000..2b39992 --- /dev/null +++ b/now-playing/themes/vinyl/index.html @@ -0,0 +1,12 @@ +
+ + + +
+ +
+
+
+
+
+
\ No newline at end of file diff --git a/now-playing/themes/vinyl/script.js b/now-playing/themes/vinyl/script.js new file mode 100644 index 0000000..7a438f9 --- /dev/null +++ b/now-playing/themes/vinyl/script.js @@ -0,0 +1,167 @@ +/////////////////// +// PAGE ELEMENTS // +/////////////////// + +const mainWrapper = document.getElementById('main-wrapper'); +const albumArtContainer = document.getElementById('album-art-container'); +// const songInfoContainer = document.getElementById('song-info-container'); +const albumArtLayer = document.getElementById('album-art-layer'); +const albumArtTransition = document.getElementById('album-art-transition-layer'); +const progressCircle = document.getElementById('progress-pie-circle'); + +/////////////// +// CONSTANTS // +/////////////// + +const circumference = 157.1; + + + + +//////////////// +// PAGE SETUP // +//////////////// + +// Set container width +if (maxWidth > 0) + mainWrapper.style.width = `${maxWidth}px`; +else + mainWrapper.style.width = `100%`; + +// Set progress bar visibility +if (!showProgressBar) + progressCircle.style.display = 'none'; + + + +//////////////////// +// CORE FUNCTIONS // +//////////////////// + +// Each theme must implement the following +async function UpdatePlayerState(data) { + // Check if the user has provided a target application in the settings + const isFiltering = targetApplication && targetApplication.trim() !== ""; + + // Search for the session that matches the target application + const sessionToFind = isFiltering ? targetApplication : data.current_session_id; + let targetSession = data.sessions.find(s => { + // If filtering, check if the source_app_id matches the user's string + if (isFiltering) { + return s.source_app_id.toLowerCase() === targetApplication.toLowerCase(); + } + // Otherwise, match the system's current active session ID + return s.source_app_id === sessionToFind; + }); + + // If a target session was found, update the state of the widget + if (targetSession) { + // Extract the relevant properties from the session + const playbackInfo = targetSession.playback_info; + const mediaProps = targetSession.media_properties; + const timelineProps = targetSession.timeline_properties; + + // Calcualte an accent color + const accentColorPalette = await GetAccentPalette(mediaProps.Thumbnail); + + // 1. Check if playback status has changed and update visibility accordingly + if (playbackInfo.PlaybackStatus !== CurrentPlaybackStatus) { + if (playbackInfo.PlaybackStatus === PlaybackStatus.PLAYING) + SetVisibility(true); + else + SetVisibility(false); + CurrentPlaybackStatus = playbackInfo.PlaybackStatus; + } + + // 2. Check if the track name/artist have changed - this is our indicator that the next track has loaded + // Only proceed if the player state is actively playing audio + if (CurrentPlaybackStatus == PlaybackStatus.PLAYING) + { + const newTrackKey = `${mediaProps.Title}|${mediaProps.Artist}`; + if (newTrackKey !== CurrentSong) { + ChangeTrack(mediaProps); // Now trigger your cross-fade logic here! + CurrentSong = newTrackKey; // Update the tracker with the string key + } + } + + // 3. Update the progress info + if (timelineProps) + { + // Parse the Windows timestamp into a JavaScript time object + const lastUpdateAnchor = Date.parse(timelineProps.LastUpdatedTime.replace(' ', 'T')); + + // Calculate the drift (i.e. how many milliseconds have passed since Windows last updated) + const driftMs = Date.now() - lastUpdateAnchor; + + // Add that drift to the reported Position + // Only add drift if the status is PLAYING + const isPlaying = (targetSession.playback_info.PlaybackStatus === PlaybackStatus.PLAYING); + const currentPositionMs = isPlaying && timelineProps.EndTime > 0 ? timelineProps.Position + driftMs : timelineProps.Position; + + // Set progressbar + // Ensure we don't divide by zero or exceed 100% + const durationMs = timelineProps.EndTime; + progressPercent = durationMs > 0 ? (currentPositionMs / durationMs) * 100 : 0; + progressPercent = Math.min(100, Math.max(0, progressPercent)); + document.documentElement.style.setProperty('--accent-color', `${accentColorPalette.LightVibrant}`); + + // Calculate the offset. + // 0% progress = 157.1 offset. 100% progress = 0 offset. + const offset = circumference - (progressPercent / 100) * circumference; + progressCircle.style.strokeDashoffset = offset; + } + } + else + { + SetVisibility(false); + } +} + + + +////////////////////// +// HELPER FUNCTIONS // +////////////////////// + +function SetVisibility(visible) { + // Always clear any pending hide timers whenever we change visibility + if (hideTimeout) { + clearTimeout(hideTimeout); + hideTimeout = null; + } + + if (visible) { + mainWrapper.style.animation = `${showAnimation} 0.5s ease-out forwards`; + + // Only set a new timer if autoHide is enabled + if (autoHide) { + hideTimeout = setTimeout(() => { + SetVisibility(false); + }, displayDuration * 1000); + } + } else { + mainWrapper.style.animation = `${hideAnimation} 0.5s ease-out forwards`; + } +} + +async function ChangeTrack(mediaProps) { + // // Fade in the overlay (shows the new text) + albumArtLayer.style.opacity = "0"; + + // Wait for fade (0.5s), then swap the real text and hide overlay + setTimeout(() => { + + // Extract the image source string (use fallback if Windows has no art) + const newArtUrl = mediaProps.Thumbnail; + + // Set the image + albumArtLayer.style.backgroundImage = `url('${newArtUrl}')`; + albumArtLayer.style.opacity = ""; + + setTimeout(() => { + // Set the image + albumArtTransition.style.backgroundImage = `url('${newArtUrl}')`; + SetVisibility(true); // Show the overlay for a few seconds if autoHide is enabled + }, 250); + }, 250); +} \ No newline at end of file diff --git a/now-playing/themes/vinyl/style.css b/now-playing/themes/vinyl/style.css new file mode 100644 index 0000000..e9bd003 --- /dev/null +++ b/now-playing/themes/vinyl/style.css @@ -0,0 +1,98 @@ + + +#main-wrapper { + width: 500px; + display: flex; + flex-direction: row; + /* This forces both containers to be the same height */ + gap: 1em; + opacity: 0; + align-items: center; + justify-content: center; +} + +#vinyl-container { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + filter: drop-shadow(0px 10px 15px rgba(0, 0, 0, 0.5)); + animation: spin 10s linear infinite; +} + +#vinyl-background { + position: relative; + height: 100%; + width: 100%; +} + +#album-art-container { + position: absolute; + width: 35%; + height: 35%; + border-radius: 50%; + overflow: hidden; + + -webkit-mask-image: radial-gradient(circle, transparent 3%, black 3.5%); + mask-image: radial-gradient(circle, transparent 3%, black 3.5%); +} + +#album-art-layer, +#album-art-transition-layer { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-size: cover; + background-position: center; + transition: opacity 0.5s ease; + display: var(--show-album-art); +} + +#album-art-layer { + z-index: 2; + transition: all 0.5s ease; +} + +#album-art-transition-layer { + z-index: 1; +} + +#progress-ring-container { + position: absolute; + width: 100%; + height: 100%; + border-radius: 20%; + overflow: hidden; /* This masks the 'pie slice' */ + z-index: 0; +} + +#progress-svg { + position: absolute; + width: 100%; + height: 100%; + z-index: 0; + /* Rotates the start point to 12 o'clock */ + transform: rotate(-90deg); + -webkit-mask-image: radial-gradient(circle, transparent 3%, black 3.5%); + mask-image: radial-gradient(circle, transparent 3%, black 3.5%); +} + +#progress-pie-circle { + fill: transparent; + stroke: var(--accent-color); /* Falls back to red if var fails */ + stroke-width: 50; /* This thickness makes it a solid pie */ + + /* 157.1 is the exact circumference of a circle with r=25 (2 * PI * 25) */ + stroke-dasharray: 157.1; + stroke-dashoffset: 157.1; /* Starts empty */ + + /* This creates the smooth, non-stuttering animation */ + transition: all 1s linear; +} + +@keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} \ No newline at end of file