mirror of
https://github.com/nuttylmao/nutty.gg.git
synced 2026-09-19 04:00:59 -04:00
Big refactor
This commit is contained in:
+17
-34
@@ -413,42 +413,25 @@ async function LoadHTMLTemplate(name) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate an accent color given an image
|
function ConvertMillisecondsToHoursMinutesSecondsSoItLooksBetterAndNotCringe(time) {
|
||||||
// async function GetAccentPalette(imageUrl, count = 20) {
|
if (isNaN(time) || time <= 0) return "0:00";
|
||||||
// if (typeof ColorThief === 'undefined') {
|
|
||||||
// await new Promise((resolve, reject) => {
|
|
||||||
// const script = document.createElement('script');
|
|
||||||
// script.src = "https://cdnjs.cloudflare.com/ajax/libs/color-thief/2.3.2/color-thief.min.js";
|
|
||||||
// script.onload = resolve;
|
|
||||||
// script.onerror = reject;
|
|
||||||
// document.head.appendChild(script);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return new Promise((resolve) => {
|
const totalSeconds = Math.floor(time / 1000);
|
||||||
// const img = new Image();
|
const hours = Math.floor(totalSeconds / 3600);
|
||||||
// img.crossOrigin = "Anonymous";
|
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||||
// img.src = imageUrl;
|
const seconds = totalSeconds % 60;
|
||||||
|
|
||||||
// img.onload = () => {
|
|
||||||
// const colorThief = new ColorThief();
|
|
||||||
// // Clamp count between 2 and 20 to ensure algorithm stability
|
|
||||||
// const safeCount = Math.max(2, Math.min(20, count));
|
|
||||||
// const palette = colorThief.getPalette(img, safeCount);
|
|
||||||
|
|
||||||
// // Convert RGB array to Hex
|
// Format seconds with a leading zero
|
||||||
// const hexPalette = palette.map(([r, g, b]) => {
|
const paddedSeconds = ('0' + seconds).slice(-2);
|
||||||
// return "#" + ((1 << 24) + (r << 16) + (g << 8) + b)
|
|
||||||
// .toString(16)
|
|
||||||
// .slice(1);
|
|
||||||
// });
|
|
||||||
|
|
||||||
// resolve(hexPalette);
|
|
||||||
// };
|
|
||||||
|
|
||||||
// img.onerror = () => resolve(["#ffffff"]);
|
if (hours > 0) {
|
||||||
// });
|
// Format minutes with a leading zero if hours are present
|
||||||
// }
|
const paddedMinutes = ('0' + minutes).slice(-2);
|
||||||
|
return `${hours}:${paddedMinutes}:${paddedSeconds}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${minutes}:${paddedSeconds}`;
|
||||||
|
}
|
||||||
|
|
||||||
async function GetAccentPalette(imageUrl) {
|
async function GetAccentPalette(imageUrl) {
|
||||||
// 1. Dynamic Loader: Load Vibrant.js
|
// 1. Dynamic Loader: Load Vibrant.js
|
||||||
@@ -487,7 +470,7 @@ async function GetAccentPalette(imageUrl) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generic popup
|
// Generic popup
|
||||||
function showPopup(iconSrc, title, subtitle, attribute, background, button) {
|
function SplashscreenPopup(iconSrc, title, subtitle, attribute, background, button) {
|
||||||
// 1. Check if a popup is already on screen
|
// 1. Check if a popup is already on screen
|
||||||
const existingOverlay = document.getElementById('global-common-overlay');
|
const existingOverlay = document.getElementById('global-common-overlay');
|
||||||
|
|
||||||
|
|||||||
+91
-19
@@ -5,6 +5,12 @@
|
|||||||
const queryString = window.location.search;
|
const queryString = window.location.search;
|
||||||
const urlParams = new URLSearchParams(queryString);
|
const urlParams = new URLSearchParams(queryString);
|
||||||
|
|
||||||
|
////////////////
|
||||||
|
// CONSTANTS //
|
||||||
|
////////////////
|
||||||
|
|
||||||
|
const REQUIRED_VERSION = '0.0.1';
|
||||||
|
const SMTC_BRIDGE_DOWNLOAD_URL = 'https://github.com/nuttylmao/smtc-bridge/releases';
|
||||||
|
|
||||||
///////////////////
|
///////////////////
|
||||||
// PAGE ELEMENTS //
|
// PAGE ELEMENTS //
|
||||||
@@ -37,13 +43,11 @@ const hideAnimation = urlParams.get('hideAnimation') || 'slide-out-bottom';
|
|||||||
// GLOBAL VARS //
|
// GLOBAL VARS //
|
||||||
/////////////////
|
/////////////////
|
||||||
|
|
||||||
const REQUIRED_VERSION = '0.0.1';
|
|
||||||
const SMTC_BRIDGE_DOWNLOAD_URL = 'https://github.com/nuttylmao/smtc-bridge/releases';
|
|
||||||
let smtcBridgePopup = null;
|
let smtcBridgePopup = null;
|
||||||
let versionCheckPopup = null;
|
let versionCheckPopup = null;
|
||||||
let skipVersionCheck = false;
|
let skipVersionCheck = false;
|
||||||
let CurrentPlaybackStatus;
|
let CurrentPlaybackStatus;
|
||||||
let CurrentSong;
|
let CurrentSongKey;
|
||||||
let hideTimeout = null;
|
let hideTimeout = null;
|
||||||
|
|
||||||
////////////////
|
////////////////
|
||||||
@@ -178,7 +182,7 @@ window.addEventListener('DOMContentLoaded', () => {
|
|||||||
////////////
|
////////////
|
||||||
|
|
||||||
function ShowWaitingForSMTCBridgePopup() {
|
function ShowWaitingForSMTCBridgePopup() {
|
||||||
const newPopup = showPopup(
|
const newPopup = SplashscreenPopup(
|
||||||
'/.common/resources/smtc-bridge-icon.png',
|
'/.common/resources/smtc-bridge-icon.png',
|
||||||
'Waiting for SMTC Bridge',
|
'Waiting for SMTC Bridge',
|
||||||
'Please launch SMTC Bridge',
|
'Please launch SMTC Bridge',
|
||||||
@@ -199,7 +203,7 @@ function ShowWaitingForSMTCBridgePopup() {
|
|||||||
|
|
||||||
|
|
||||||
function ShowSMTCBridgeUpdateRequiredPopup(installedVersion) {
|
function ShowSMTCBridgeUpdateRequiredPopup(installedVersion) {
|
||||||
const newPopup = showPopup(
|
const newPopup = SplashscreenPopup(
|
||||||
'/.common/resources/smtc-bridge-icon.png',
|
'/.common/resources/smtc-bridge-icon.png',
|
||||||
'Update Required',
|
'Update Required',
|
||||||
'Your version of SMTC Bridge is out of date.',
|
'Your version of SMTC Bridge is out of date.',
|
||||||
@@ -219,7 +223,7 @@ function ShowSMTCBridgeUpdateRequiredPopup(installedVersion) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ShowSMTCBridgeUpdateAvailablePopup(installedVersion) {
|
function ShowSMTCBridgeUpdateAvailablePopup(installedVersion) {
|
||||||
const newPopup = showPopup(
|
const newPopup = SplashscreenPopup(
|
||||||
'/.common/resources/smtc-bridge-icon.png',
|
'/.common/resources/smtc-bridge-icon.png',
|
||||||
'Update Available',
|
'Update Available',
|
||||||
'A new version of SMTC Bridge is available.',
|
'A new version of SMTC Bridge is available.',
|
||||||
@@ -286,22 +290,90 @@ function CheckSMTCBridgeVersion(installedVersion) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function ConvertMillisecondsToMinutesSoThatItLooksBetterOnTheOverlay(time) {
|
// Each theme must implement the following
|
||||||
if (isNaN(time) || time <= 0) return "0:00";
|
async function UpdatePlayerState(data) {
|
||||||
|
// Check if the user has provided a target application in the settings
|
||||||
|
const isFiltering = targetApplication && targetApplication.trim() !== "";
|
||||||
|
|
||||||
const totalSeconds = Math.floor(time / 1000);
|
// Search for the session that matches the target application
|
||||||
const hours = Math.floor(totalSeconds / 3600);
|
const sessionToFind = isFiltering ? targetApplication : data.current_session_id;
|
||||||
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
let targetSession = data.sessions.find(s => {
|
||||||
const seconds = totalSeconds % 60;
|
// 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;
|
||||||
|
});
|
||||||
|
|
||||||
// Format seconds with a leading zero
|
// If a target session was found, update the state of the widget
|
||||||
const paddedSeconds = ('0' + seconds).slice(-2);
|
if (targetSession) {
|
||||||
|
// Extract the relevant properties from the session
|
||||||
|
const playbackInfo = targetSession.playback_info;
|
||||||
|
const mediaProps = targetSession.media_properties;
|
||||||
|
const timelineProps = targetSession.timeline_properties;
|
||||||
|
|
||||||
if (hours > 0) {
|
// Calcualte an accent color
|
||||||
// Format minutes with a leading zero if hours are present
|
const accentColorPalette = await GetAccentPalette(mediaProps.Thumbnail);
|
||||||
const paddedMinutes = ('0' + minutes).slice(-2);
|
|
||||||
return `${hours}:${paddedMinutes}:${paddedSeconds}`;
|
// 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 !== CurrentSongKey) {
|
||||||
|
ChangeTrack(mediaProps, accentColorPalette); // Now trigger your cross-fade logic here!
|
||||||
|
CurrentSongKey = 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;
|
||||||
|
|
||||||
|
SetProgressInfo(timelineProps, currentPositionMs, accentColorPalette);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SetVisibility(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function SetVisibility(visible) {
|
||||||
|
// Always clear any pending hide timers whenever we change visibility
|
||||||
|
if (hideTimeout) {
|
||||||
|
clearTimeout(hideTimeout);
|
||||||
|
hideTimeout = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${minutes}:${paddedSeconds}`;
|
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`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -42,116 +42,7 @@ if (!showProgressBar)
|
|||||||
// CORE FUNCTIONS //
|
// CORE FUNCTIONS //
|
||||||
////////////////////
|
////////////////////
|
||||||
|
|
||||||
// Each theme must implement the following
|
async function ChangeTrack(mediaProps, accentColorPalette) {
|
||||||
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, accentColorPalette.LightVibrant); // 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;
|
|
||||||
|
|
||||||
// Update the label using your naming convention
|
|
||||||
currentTimeLabel.innerText =
|
|
||||||
ConvertMillisecondsToMinutesSoThatItLooksBetterOnTheOverlay(currentPositionMs);
|
|
||||||
|
|
||||||
durationLabel.innerText =
|
|
||||||
ConvertMillisecondsToMinutesSoThatItLooksBetterOnTheOverlay(timelineProps.EndTime);
|
|
||||||
|
|
||||||
// Set progressbar
|
|
||||||
// Ensure we don't divide by zero or exceed 100%
|
|
||||||
const durationMs = timelineProps.EndTime;
|
|
||||||
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', accentColorPalette.LightVibrant);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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, tintColor) {
|
|
||||||
// Fade in the overlay (shows the new text)
|
// Fade in the overlay (shows the new text)
|
||||||
trackLabel.style.opacity = "0";
|
trackLabel.style.opacity = "0";
|
||||||
artistLabel.style.opacity = "0";
|
artistLabel.style.opacity = "0";
|
||||||
@@ -172,7 +63,7 @@ async function ChangeTrack(mediaProps, tintColor) {
|
|||||||
|
|
||||||
// Apply a tint: Use 30% opacity of the accent color + a dark overlay for contrast
|
// Apply a tint: Use 30% opacity of the accent color + a dark overlay for contrast
|
||||||
// 'rgba(0,0,0,0.6)' ensures the text stays readable
|
// 'rgba(0,0,0,0.6)' ensures the text stays readable
|
||||||
// backgroundLayer.style.backgroundColor = tintColor + "80"; // 80 is 50% opacity in hex
|
// backgroundLayer.style.backgroundColor = accentColorPalette.DarkMuted + "80"; // 80 is 50% opacity in hex
|
||||||
|
|
||||||
trackLabel.style.opacity = "";
|
trackLabel.style.opacity = "";
|
||||||
artistLabel.style.opacity = "";
|
artistLabel.style.opacity = "";
|
||||||
@@ -186,13 +77,30 @@ async function ChangeTrack(mediaProps, tintColor) {
|
|||||||
|
|
||||||
// Apply a tint: Use 30% opacity of the accent color + a dark overlay for contrast
|
// Apply a tint: Use 30% opacity of the accent color + a dark overlay for contrast
|
||||||
// 'rgba(0,0,0,0.6)' ensures the text stays readable
|
// 'rgba(0,0,0,0.6)' ensures the text stays readable
|
||||||
// backgroundTransitionLayer.style.backgroundColor = tintColor + "80"; // 80 is 50% opacity in hex
|
// backgroundTransitionLayer.style.backgroundColor = accentColorPalette.DarkMuted + "80"; // 80 is 50% opacity in hex
|
||||||
|
|
||||||
SetVisibility(true); // Show the overlay for a few seconds if autoHide is enabled
|
SetVisibility(true); // Show the overlay for a few seconds if autoHide is enabled
|
||||||
}, 250);
|
}, 250);
|
||||||
}, 250);
|
}, 250);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function SetProgressInfo(timelineProps, currentPositionMs, accentColorPalette) {
|
||||||
|
// Update the label using your naming convention
|
||||||
|
currentTimeLabel.innerText =
|
||||||
|
ConvertMillisecondsToHoursMinutesSecondsSoItLooksBetterAndNotCringe(currentPositionMs);
|
||||||
|
|
||||||
|
durationLabel.innerText =
|
||||||
|
ConvertMillisecondsToHoursMinutesSecondsSoItLooksBetterAndNotCringe(timelineProps.EndTime);
|
||||||
|
|
||||||
|
// Set progressbar
|
||||||
|
// Ensure we don't divide by zero or exceed 100%
|
||||||
|
const durationMs = timelineProps.EndTime;
|
||||||
|
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', accentColorPalette.LightVibrant);
|
||||||
|
}
|
||||||
|
|
||||||
function SetAlbumArtSize() {
|
function SetAlbumArtSize() {
|
||||||
// const height = songInfoContainer.offsetHeight;
|
// const height = songInfoContainer.offsetHeight;
|
||||||
albumArtContainer.style.height = `100%`;
|
albumArtContainer.style.height = `100%`;
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
width: 500px;
|
width: 500px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
/* This forces both containers to be the same height */
|
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|||||||
@@ -42,116 +42,7 @@ if (!showProgressBar)
|
|||||||
// CORE FUNCTIONS //
|
// CORE FUNCTIONS //
|
||||||
////////////////////
|
////////////////////
|
||||||
|
|
||||||
// Each theme must implement the following
|
async function ChangeTrack(mediaProps, accentColorPalette) {
|
||||||
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, accentColorPalette.LightVibrant); // 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;
|
|
||||||
|
|
||||||
// Update the label using your naming convention
|
|
||||||
currentTimeLabel.innerText =
|
|
||||||
ConvertMillisecondsToMinutesSoThatItLooksBetterOnTheOverlay(currentPositionMs);
|
|
||||||
|
|
||||||
durationLabel.innerText =
|
|
||||||
ConvertMillisecondsToMinutesSoThatItLooksBetterOnTheOverlay(timelineProps.EndTime);
|
|
||||||
|
|
||||||
// Set progressbar
|
|
||||||
// Ensure we don't divide by zero or exceed 100%
|
|
||||||
const durationMs = timelineProps.EndTime;
|
|
||||||
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', accentColorPalette.LightVibrant);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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, tintColor) {
|
|
||||||
// Fade in the overlay (shows the new text)
|
// Fade in the overlay (shows the new text)
|
||||||
trackLabel.style.opacity = "0";
|
trackLabel.style.opacity = "0";
|
||||||
artistLabel.style.opacity = "0";
|
artistLabel.style.opacity = "0";
|
||||||
@@ -172,7 +63,7 @@ async function ChangeTrack(mediaProps, tintColor) {
|
|||||||
|
|
||||||
// Apply a tint: Use 30% opacity of the accent color + a dark overlay for contrast
|
// Apply a tint: Use 30% opacity of the accent color + a dark overlay for contrast
|
||||||
// 'rgba(0,0,0,0.6)' ensures the text stays readable
|
// 'rgba(0,0,0,0.6)' ensures the text stays readable
|
||||||
// backgroundLayer.style.backgroundColor = tintColor + "80"; // 80 is 50% opacity in hex
|
backgroundLayer.style.backgroundColor = accentColorPalette.DarkMuted + "80"; // 80 is 50% opacity in hex
|
||||||
|
|
||||||
trackLabel.style.opacity = "";
|
trackLabel.style.opacity = "";
|
||||||
artistLabel.style.opacity = "";
|
artistLabel.style.opacity = "";
|
||||||
@@ -186,13 +77,30 @@ async function ChangeTrack(mediaProps, tintColor) {
|
|||||||
|
|
||||||
// Apply a tint: Use 30% opacity of the accent color + a dark overlay for contrast
|
// Apply a tint: Use 30% opacity of the accent color + a dark overlay for contrast
|
||||||
// 'rgba(0,0,0,0.6)' ensures the text stays readable
|
// 'rgba(0,0,0,0.6)' ensures the text stays readable
|
||||||
// backgroundTransitionLayer.style.backgroundColor = tintColor + "80"; // 80 is 50% opacity in hex
|
backgroundTransitionLayer.style.backgroundColor = aaccentColorPalette.DarkMuted + "80"; // 80 is 50% opacity in hex
|
||||||
|
|
||||||
SetVisibility(true); // Show the overlay for a few seconds if autoHide is enabled
|
SetVisibility(true); // Show the overlay for a few seconds if autoHide is enabled
|
||||||
}, 250);
|
}, 250);
|
||||||
}, 250);
|
}, 250);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function SetProgressInfo(timelineProps, currentPositionMs, accentColorPalette) {
|
||||||
|
// Update the label using your naming convention
|
||||||
|
currentTimeLabel.innerText =
|
||||||
|
ConvertMillisecondsToHoursMinutesSecondsSoItLooksBetterAndNotCringe(currentPositionMs);
|
||||||
|
|
||||||
|
durationLabel.innerText =
|
||||||
|
ConvertMillisecondsToHoursMinutesSecondsSoItLooksBetterAndNotCringe(timelineProps.EndTime);
|
||||||
|
|
||||||
|
// Set progressbar
|
||||||
|
// Ensure we don't divide by zero or exceed 100%
|
||||||
|
const durationMs = timelineProps.EndTime;
|
||||||
|
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', accentColorPalette.LightVibrant);
|
||||||
|
}
|
||||||
|
|
||||||
function SetAlbumArtSize() {
|
function SetAlbumArtSize() {
|
||||||
const height = songInfoContainer.offsetHeight;
|
const height = songInfoContainer.offsetHeight;
|
||||||
albumArtContainer.style.height = `${height}px`;
|
albumArtContainer.style.height = `${height}px`;
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
width: 500px;
|
width: 500px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
/* This forces both containers to be the same height */
|
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|||||||
@@ -40,115 +40,6 @@ else
|
|||||||
// CORE FUNCTIONS //
|
// 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, accentColorPalette); // 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;
|
|
||||||
let progressPercent = durationMs > 0 ? (currentPositionMs / durationMs) * 100 : 0;
|
|
||||||
progressPercent = Math.min(100, Math.max(0, progressPercent));
|
|
||||||
|
|
||||||
// Calculate how much needs to be hidden (the right-side offset)
|
|
||||||
const clipRight = 100 - progressPercent;
|
|
||||||
|
|
||||||
// Update the clip-path
|
|
||||||
progressBar.style.clipPath = `inset(0 ${clipRight}% 0 0)`;
|
|
||||||
|
|
||||||
// const clipRight = progressPercent;
|
|
||||||
// progressBar.style.maskImage = `linear-gradient(to right, black calc(${clipRight}% - 4em), transparent ${clipRight}%)`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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, accentColorPalette) {
|
async function ChangeTrack(mediaProps, accentColorPalette) {
|
||||||
// Fade in the overlay (shows the new text)
|
// Fade in the overlay (shows the new text)
|
||||||
songLabel.style.opacity = "0";
|
songLabel.style.opacity = "0";
|
||||||
@@ -210,4 +101,21 @@ async function ChangeTrack(mediaProps, accentColorPalette) {
|
|||||||
SetVisibility(true); // Show the overlay for a few seconds if autoHide is enabled
|
SetVisibility(true); // Show the overlay for a few seconds if autoHide is enabled
|
||||||
}, 250);
|
}, 250);
|
||||||
}, 250);
|
}, 250);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SetProgressInfo(timelineProps, currentPositionMs, accentColorPalette) {
|
||||||
|
// Set progressbar
|
||||||
|
// Ensure we don't divide by zero or exceed 100%
|
||||||
|
const durationMs = timelineProps.EndTime;
|
||||||
|
let progressPercent = durationMs > 0 ? (currentPositionMs / durationMs) * 100 : 0;
|
||||||
|
progressPercent = Math.min(100, Math.max(0, progressPercent));
|
||||||
|
|
||||||
|
// Calculate how much needs to be hidden (the right-side offset)
|
||||||
|
const clipRight = 100 - progressPercent;
|
||||||
|
|
||||||
|
// Update the clip-path
|
||||||
|
progressBar.style.clipPath = `inset(0 ${clipRight}% 0 0)`;
|
||||||
|
|
||||||
|
// const clipRight = progressPercent;
|
||||||
|
// progressBar.style.maskImage = `linear-gradient(to right, black calc(${clipRight}% - 4em), transparent ${clipRight}%)`
|
||||||
}
|
}
|
||||||
@@ -42,111 +42,6 @@ else
|
|||||||
// CORE FUNCTIONS //
|
// CORE FUNCTIONS //
|
||||||
////////////////////
|
////////////////////
|
||||||
|
|
||||||
// Each theme must implement the following
|
|
||||||
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;
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
// // Update the label using your naming convention
|
|
||||||
// currentTimeLabel.innerText =
|
|
||||||
// ConvertMillisecondsToMinutesSoThatItLooksBetterOnTheOverlay(currentPositionMs);
|
|
||||||
|
|
||||||
// durationLabel.innerText =
|
|
||||||
// ConvertMillisecondsToMinutesSoThatItLooksBetterOnTheOverlay(timelineProps.EndTime);
|
|
||||||
|
|
||||||
// // Set progressbar
|
|
||||||
// // Ensure we don't divide by zero or exceed 100%
|
|
||||||
// const durationMs = timelineProps.EndTime;
|
|
||||||
// let progressPercent = durationMs > 0 ? (currentPositionMs / durationMs) * 100 : 0;
|
|
||||||
// progressPercent = Math.min(100, Math.max(0, progressPercent));
|
|
||||||
// progressBarFill.style.width = `${progressPercent}%`;
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
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) {
|
async function ChangeTrack(mediaProps) {
|
||||||
// Fade in the overlay (shows the new text)
|
// Fade in the overlay (shows the new text)
|
||||||
trackLabel.style.opacity = "0";
|
trackLabel.style.opacity = "0";
|
||||||
@@ -189,6 +84,9 @@ async function ChangeTrack(mediaProps) {
|
|||||||
}, 250);
|
}, 250);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function SetProgressInfo(timelineProps, currentPositionMs, accentColorPalette) {
|
||||||
|
}
|
||||||
|
|
||||||
function SetAlbumArtSize() {
|
function SetAlbumArtSize() {
|
||||||
const height = songInfoContainer.offsetHeight;
|
const height = songInfoContainer.offsetHeight;
|
||||||
albumArtContainer.style.height = `${1.5 * height}px`;
|
albumArtContainer.style.height = `${1.5 * height}px`;
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
width: 500px;
|
width: 500px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
/* This forces both containers to be the same height */
|
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
gap: 1em;
|
gap: 1em;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|||||||
@@ -54,121 +54,6 @@ switch (themeVariant) {
|
|||||||
// CORE FUNCTIONS //
|
// 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, accentColorPalette); // 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;
|
|
||||||
|
|
||||||
// Update the label using your naming convention
|
|
||||||
currentTimeLabel.innerText =
|
|
||||||
ConvertMillisecondsToMinutesSoThatItLooksBetterOnTheOverlay(currentPositionMs);
|
|
||||||
|
|
||||||
durationLabel.innerText =
|
|
||||||
ConvertMillisecondsToMinutesSoThatItLooksBetterOnTheOverlay(timelineProps.EndTime);
|
|
||||||
|
|
||||||
// Set progressbar
|
|
||||||
// Ensure we don't divide by zero or exceed 100%
|
|
||||||
const durationMs = timelineProps.EndTime;
|
|
||||||
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', accentColorPalette.LightVibrant);
|
|
||||||
switch (themeVariant) {
|
|
||||||
case "matte":
|
|
||||||
progressBarFill.style.setProperty('--accent-color', accentColorPalette.DarkVibrant);
|
|
||||||
break;
|
|
||||||
case "matte-dark":
|
|
||||||
default:
|
|
||||||
progressBarFill.style.setProperty('--accent-color', accentColorPalette.LightVibrant);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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, accentColorPalette) {
|
async function ChangeTrack(mediaProps, accentColorPalette) {
|
||||||
// Fade in the overlay (shows the new text)
|
// Fade in the overlay (shows the new text)
|
||||||
trackLabel.style.opacity = "0";
|
trackLabel.style.opacity = "0";
|
||||||
@@ -228,12 +113,28 @@ async function ChangeTrack(mediaProps, accentColorPalette) {
|
|||||||
}, 250);
|
}, 250);
|
||||||
}
|
}
|
||||||
|
|
||||||
// function SetAlbumArtSize() {
|
function SetProgressInfo(timelineProps, currentPositionMs, accentColorPalette) {
|
||||||
// const height = songInfoContainer.offsetHeight;
|
// Update the label using your naming convention
|
||||||
// albumArtContainer.style.height = `${height}px`;
|
currentTimeLabel.innerText =
|
||||||
// albumArtContainer.style.width = `${height}px`;
|
ConvertMillisecondsToHoursMinutesSecondsSoItLooksBetterAndNotCringe(currentPositionMs);
|
||||||
// }
|
|
||||||
|
|
||||||
// requestAnimationFrame(() => {
|
durationLabel.innerText =
|
||||||
// SetAlbumArtSize();
|
ConvertMillisecondsToHoursMinutesSecondsSoItLooksBetterAndNotCringe(timelineProps.EndTime);
|
||||||
// });
|
|
||||||
|
// Set progressbar
|
||||||
|
// Ensure we don't divide by zero or exceed 100%
|
||||||
|
const durationMs = timelineProps.EndTime;
|
||||||
|
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', accentColorPalette.LightVibrant);
|
||||||
|
switch (themeVariant) {
|
||||||
|
case "matte":
|
||||||
|
progressBarFill.style.setProperty('--accent-color', accentColorPalette.DarkVibrant);
|
||||||
|
break;
|
||||||
|
case "matte-dark":
|
||||||
|
default:
|
||||||
|
progressBarFill.style.setProperty('--accent-color', accentColorPalette.LightVibrant);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,6 @@
|
|||||||
width: 500px;
|
width: 500px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
/* This forces both containers to be the same height */
|
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|||||||
@@ -38,112 +38,6 @@ if (!showProgressBar)
|
|||||||
// CORE FUNCTIONS //
|
// 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) {
|
async function ChangeTrack(mediaProps) {
|
||||||
// // Fade in the overlay (shows the new text)
|
// // Fade in the overlay (shows the new text)
|
||||||
albumArtLayer.style.opacity = "0";
|
albumArtLayer.style.opacity = "0";
|
||||||
@@ -164,4 +58,18 @@ async function ChangeTrack(mediaProps) {
|
|||||||
SetVisibility(true); // Show the overlay for a few seconds if autoHide is enabled
|
SetVisibility(true); // Show the overlay for a few seconds if autoHide is enabled
|
||||||
}, 250);
|
}, 250);
|
||||||
}, 250);
|
}, 250);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SetProgressInfo(timelineProps, currentPositionMs, accentColorPalette) {
|
||||||
|
// 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;
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,6 @@
|
|||||||
width: 500px;
|
width: 500px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
/* This forces both containers to be the same height */
|
|
||||||
gap: 1em;
|
gap: 1em;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
Reference in New Issue
Block a user