Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
1ac096ca52
|
|||
575bf35e7f
|
38
content.js
38
content.js
@ -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,7 +52,10 @@ async function getNowPlayingData() {
|
||||
const timestampInput = document.querySelector('amp-lcd.lcd.lcd__music')?.shadowRoot
|
||||
?.querySelector('input#playback-progress[aria-valuenow][aria-valuemax]');
|
||||
|
||||
const defaultArtworkBase64 = await imageUrlToBase64(defaultArtwork);
|
||||
if (!artworkBase64Cache.defaultArtworkBase64 && placeholderArtwork) {
|
||||
artworkBase64Cache.defaultArtworkBase64 = await imageUrlToBase64(placeholderArtwork);
|
||||
}
|
||||
const defaultArtworkBase64 = artworkBase64Cache.defaultArtworkBase64;
|
||||
|
||||
if (!media) {
|
||||
pauseStartTime = null;
|
||||
@ -94,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,
|
||||
@ -177,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.");
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "Apple Music Now Playing",
|
||||
"version": "1.4.1",
|
||||
"version": "1.4.3",
|
||||
"description": "Easily display your currently playing Apple Music track on your website with this extension.",
|
||||
"permissions": [
|
||||
"storage",
|
||||
|
85
options.html
85
options.html
@ -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>
|
||||
|
88
options.js
88
options.js
@ -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)';
|
||||
}
|
Reference in New Issue
Block a user