Made it so you can configure everything

You can configure the following settings:
- Placeholder artwork URL
- Paused timeout (in seconds, minutes, or hours)
- Metadata fetch interval (in seconds, minutes, or hours)

I also added helping notes to the title of the inputs to make it clearer what each setting does.

Also meow meow meow meow meow meow meow meow meow meow meow meow meow meow
This commit is contained in:
2025-06-27 12:00:56 -07:00
parent 575bf35e7f
commit 1ac096ca52
4 changed files with 180 additions and 32 deletions

View File

@ -4,15 +4,23 @@ let artworkEndpoint = null;
let token = null;
let intervalId = null;
let lastSentArtworkKey = null;
let placeholderArtwork = null;
let pausedTimeout = 300000;
let metadataFetchInterval = 1000;
// log credits to console
if (!window._nowPlayingExtensionLogged) {
console.log('Apple Music Now Playing Extension made by Sophia Atkinson with help from PreMiD contributors');
window._nowPlayingExtensionLogged = true;
}
const defaultArtwork = "https://placehold.co/150/1E1F22/FFF?text=Nothing%20playing";
const pausedTimeout = 300000; // 5 minutes
const metadataFetchInterval = 1000; // 1 second
// Cache for artwork base64 by URL
const artworkBase64Cache = {
lastArtworkUrl: null,
lastArtworkBase64: null
lastArtworkBase64: null,
defaultArtworkBase64: null
};
async function imageUrlToBase64(url) {
@ -44,8 +52,8 @@ async function getNowPlayingData() {
const timestampInput = document.querySelector('amp-lcd.lcd.lcd__music')?.shadowRoot
?.querySelector('input#playback-progress[aria-valuenow][aria-valuemax]');
if (!artworkBase64Cache.defaultArtworkBase64) {
artworkBase64Cache.defaultArtworkBase64 = await imageUrlToBase64(defaultArtwork);
if (!artworkBase64Cache.defaultArtworkBase64 && placeholderArtwork) {
artworkBase64Cache.defaultArtworkBase64 = await imageUrlToBase64(placeholderArtwork);
}
const defaultArtworkBase64 = artworkBase64Cache.defaultArtworkBase64;
@ -97,7 +105,7 @@ async function getNowPlayingData() {
const currentTime = timestampInput ? Number(timestampInput.getAttribute('aria-valuenow')) : media.currentTime || 0;
const duration = timestampInput ? Number(timestampInput.getAttribute('aria-valuemax')) : media.duration || 0;
const nowUnix = Math.floor(Date.now() / metadataFetchInterval);
const nowUnix = Math.floor(Date.now() / 1000);
return {
details: title,
@ -180,19 +188,18 @@ function startNowPlayingLoop() {
intervalId = setInterval(() => safeCall(sendNowPlaying), metadataFetchInterval);
}
}
if (!window._nowPlayingExtensionLogged) {
console.log('Apple Music Now Playing Extension made by Sophia Atkinson with help from PreMiD contributors');
window._nowPlayingExtensionLogged = true;
}
if (chrome?.storage?.sync) {
chrome.storage.sync.get(['endpoint', 'artworkEndpoint', 'token'], result => {
chrome.storage.sync.get(['endpoint', 'artworkEndpoint', 'token', 'placeholderArtwork', 'pausedTimeout', 'metadataFetchInterval'], result => {
if (chrome.runtime.lastError) {
console.error("Failed to load settings:", chrome.runtime.lastError.message);
return;
}
({ endpoint, artworkEndpoint, token } = result);
endpoint = result.endpoint;
artworkEndpoint = result.artworkEndpoint;
token = result.token;
placeholderArtwork = result.placeholderArtwork || 'https://placehold.co/150/1E1F22/FFF?text=Nothing%20playing';
pausedTimeout = Number(result.pausedTimeout) || pausedTimeout;
metadataFetchInterval = Number(result.metadataFetchInterval) || metadataFetchInterval;
if (!endpoint || !token) {
console.error("No endpoint/token configured.");

View File

@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "Apple Music Now Playing",
"version": "1.4.2",
"version": "1.4.3",
"description": "Easily display your currently playing Apple Music track on your website with this extension.",
"permissions": [
"storage",

View File

@ -14,6 +14,8 @@
--button-bg: var(--accent);
--button-hover: #d67b8a;
--shadow: rgba(245, 169, 184, 0.3);
--details-bg: #18181a;
--details-border: #2a2a2a;
}
body {
margin: 0;
@ -83,18 +85,93 @@
background-color: var(--button-hover);
outline: none;
}
details {
margin-top: 2rem;
background: var(--details-bg);
border: 1.5px solid var(--details-border);
border-radius: 10px;
padding: 0.5rem 1rem;
max-width: 100%;
}
details summary {
cursor: pointer;
font-weight: 700;
color: var(--accent);
font-size: 1.05rem;
outline: none;
padding: 0.5rem 0;
user-select: none;
transition: color 0.2s;
}
details summary:hover,
details summary:focus {
color: #fff;
}
details > div {
margin-top: 1rem;
padding-bottom: 0.5rem;
border-top: 1px solid var(--details-border);
padding-top: 1rem;
overflow-x: hidden;
}
details label {
margin-top: 0;
font-size: 0.98rem;
font-weight: 500;
color: #ccc;
}
details input[type="text"] {
margin-top: 0.3rem;
font-size: 0.98rem;
background: #232326;
border-color: #333;
width: 100%;
box-sizing: border-box;
}
.help {
cursor: help;
color: var(--accent);
transition: color 0.2s ease;
}
label[for="endpoint"]:before,
label[for="artworkEndpoint"]:before,
label[for="token"]:before {
content: "* ";
color: red;
font-weight: bold;
}
#version {
margin-top: 1.5rem;
font-size: 0.9rem;
color: #aaa;
text-align: center;
}
</style>
</head>
<body>
<div class="container">
<h1><span title="Apple Music Now Playing">AmNP</span> Settings (Beta)</h1>
<h1><span class="help" title="Apple Music Now Playing">AmNP</span> <span class="help" title="Why are you hovering over this, this is the settings page nothing special about it">Settings</span> <span class="help" title="Beta means that this extention is experimental and may not work as expected">(Beta)</span></h1>
<label for="endpoint">Now playing Endpoint:</label>
<input type="text" id="endpoint" placeholder="https://zorpzeep.blorp/wp-json/nowplaying/v1/update" autocomplete="off" spellcheck="false"/>
<input title="For wp-amnp it uses the /wp-json/nowplaying/v1/update" type="text" id="endpoint" placeholder="https://zorpzeep.blorp/wp-json/nowplaying/v1/update" autocomplete="off" spellcheck="false" required/>
<label for="artworkEndpoint">Artwork Endpoint:</label>
<input type="text" id="artworkEndpoint" placeholder="https://zorpzeep.blorp/wp-json/nowplaying/v1/update/art" autocomplete="off" spellcheck="false"/>
<input title="For wp-amnp it uses the /wp-json/nowplaying/v1/update/artwork" type="text" id="artworkEndpoint" placeholder="https://zorpzeep.blorp/wp-json/nowplaying/v1/update/artwork" autocomplete="off" spellcheck="false" required/>
<label for="token">Bearer Token:</label>
<input type="text" id="token" placeholder="73757065725f7365637572655f746f6b656e" autocomplete="off" spellcheck="false"/>
<input title="For wp-amnp it can be found in Settings -> Now Playing API" type="text" id="token" placeholder="73757065725f7365637572655f746f6b656e" autocomplete="off" spellcheck="false" required/>
<details>
<summary>Extra Configurations</summary>
<div>
<label for="placeholderArtwork">Placeholder Artwork URL:</label>
<input title="You can type a url that host a placeholder image, any format supported, all are encoded to base64" type="text" id="placeholderArtwork" placeholder="https://placehold.co/150/1E1F22/FFF?text=Nothing%20playing" autocomplete="off" spellcheck="false"/>
<label for="pausedTimeout">Paused Timeout:</label>
<input title="You can type 5m, 5 minutes, 300 seconds, 1h, 1 hour, etc. Note that they are converted to ms" type="text" id="pausedTimeout" placeholder="5m" autocomplete="off" spellcheck="false"/>
<label for="metadataFetchInterval">Update Interval:</label>
<input title="You can type 5m, 5 minutes, 300 seconds, 1h, 1 hour, etc. Note that they are converted to ms" type="text" id="metadataFetchInterval" placeholder="1s" autocomplete="off" spellcheck="false"/>
</div>
</details>
<button id="save">Save</button>
<footer id="version">You are running version: (unavailable)</footer>
</div>
<script src="options.js"></script>
</body>

View File

@ -1,17 +1,81 @@
/* global chrome */
document.addEventListener('DOMContentLoaded', () => {
chrome.storage.sync.get(['endpoint', 'token', 'artworkEndpoint'], (data) => {
document.getElementById('endpoint').value = data.endpoint || '';
document.getElementById('token').value = data.token || '';
document.getElementById('artworkEndpoint').value = data.artworkEndpoint || '';
});
const keys = [
'endpoint',
'token',
'artworkEndpoint',
'placeholderArtwork',
'pausedTimeout',
'metadataFetchInterval'
];
function parseTimeToMs(input) {
if (!input) return 0;
const str = input.trim().toLowerCase();
const match = str.match(/^(\d+(\.\d+)?)(\s*(ms|milliseconds?|s|sec|seconds?|m|min|minutes?|h|hr|hrs|hour|hours?))?$/);
if (!match) return Number(str) || 0;
const value = parseFloat(match[1]);
const unit = match[4] || '';
switch (unit) {
case 'ms':
case 'millisecond':
case 'milliseconds':
return value;
case 's':
case 'sec':
case 'second':
case 'seconds':
return value * 1000;
case 'm':
case 'min':
case 'minute':
case 'minutes':
return value * 60 * 1000;
case 'h':
case 'hr':
case 'hrs':
case 'hour':
case 'hours':
return value * 60 * 60 * 1000;
default:
return value;
}
}
document.getElementById('save').addEventListener('click', () => {
const endpoint = document.getElementById('endpoint').value;
const token = document.getElementById('token').value;
const artworkEndpoint = document.getElementById('artworkEndpoint').value;
chrome.storage.sync.set({ endpoint, token, artworkEndpoint }, () => {
alert('Settings saved.');
});
chrome.storage.sync.get(keys, (data) => {
for (const key of keys) {
const elem = document.getElementById(key);
if (elem) {
elem.value = data[key] != null ? String(data[key]) : '';
} else {
console.warn(`Element with id "${key}" not found in DOM`);
}
}
const saveBtn = document.getElementById('save');
if (saveBtn) {
saveBtn.addEventListener('click', () => {
const valuesToSave = {};
for (const key of keys) {
const elem = document.getElementById(key);
let value = elem ? elem.value.trim() : '';
// Parse time fields to ms
if (key === 'pausedTimeout' || key === 'metadataFetchInterval') {
value = parseTimeToMs(value);
}
valuesToSave[key] = value;
}
chrome.storage.sync.set(valuesToSave, () => {
alert('Settings saved.');
});
});
} else {
console.error('Save button not found in DOM');
}
});
});
if (typeof chrome !== "undefined" && chrome.runtime && chrome.runtime.getManifest) {
document.getElementById('version').textContent = 'You are running Version: ' + chrome.runtime.getManifest().version;
} else {
document.getElementById('version').textContent = 'You are running Version: (unavailable)';
}