Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
1ac096ca52
|
37
content.js
37
content.js
@ -4,15 +4,23 @@ let artworkEndpoint = null;
|
|||||||
let token = null;
|
let token = null;
|
||||||
let intervalId = null;
|
let intervalId = null;
|
||||||
let lastSentArtworkKey = 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
|
// Cache for artwork base64 by URL
|
||||||
const artworkBase64Cache = {
|
const artworkBase64Cache = {
|
||||||
lastArtworkUrl: null,
|
lastArtworkUrl: null,
|
||||||
lastArtworkBase64: null
|
lastArtworkBase64: null,
|
||||||
|
defaultArtworkBase64: null
|
||||||
};
|
};
|
||||||
|
|
||||||
async function imageUrlToBase64(url) {
|
async function imageUrlToBase64(url) {
|
||||||
@ -44,8 +52,8 @@ async function getNowPlayingData() {
|
|||||||
const timestampInput = document.querySelector('amp-lcd.lcd.lcd__music')?.shadowRoot
|
const timestampInput = document.querySelector('amp-lcd.lcd.lcd__music')?.shadowRoot
|
||||||
?.querySelector('input#playback-progress[aria-valuenow][aria-valuemax]');
|
?.querySelector('input#playback-progress[aria-valuenow][aria-valuemax]');
|
||||||
|
|
||||||
if (!artworkBase64Cache.defaultArtworkBase64) {
|
if (!artworkBase64Cache.defaultArtworkBase64 && placeholderArtwork) {
|
||||||
artworkBase64Cache.defaultArtworkBase64 = await imageUrlToBase64(defaultArtwork);
|
artworkBase64Cache.defaultArtworkBase64 = await imageUrlToBase64(placeholderArtwork);
|
||||||
}
|
}
|
||||||
const defaultArtworkBase64 = artworkBase64Cache.defaultArtworkBase64;
|
const defaultArtworkBase64 = artworkBase64Cache.defaultArtworkBase64;
|
||||||
|
|
||||||
@ -97,7 +105,7 @@ async function getNowPlayingData() {
|
|||||||
|
|
||||||
const currentTime = timestampInput ? Number(timestampInput.getAttribute('aria-valuenow')) : media.currentTime || 0;
|
const currentTime = timestampInput ? Number(timestampInput.getAttribute('aria-valuenow')) : media.currentTime || 0;
|
||||||
const duration = timestampInput ? Number(timestampInput.getAttribute('aria-valuemax')) : media.duration || 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 {
|
return {
|
||||||
details: title,
|
details: title,
|
||||||
@ -180,19 +188,18 @@ function startNowPlayingLoop() {
|
|||||||
intervalId = setInterval(() => safeCall(sendNowPlaying), metadataFetchInterval);
|
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) {
|
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) {
|
if (chrome.runtime.lastError) {
|
||||||
console.error("Failed to load settings:", chrome.runtime.lastError.message);
|
console.error("Failed to load settings:", chrome.runtime.lastError.message);
|
||||||
return;
|
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) {
|
if (!endpoint || !token) {
|
||||||
console.error("No endpoint/token configured.");
|
console.error("No endpoint/token configured.");
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "Apple Music Now Playing",
|
"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.",
|
"description": "Easily display your currently playing Apple Music track on your website with this extension.",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"storage",
|
"storage",
|
||||||
|
85
options.html
85
options.html
@ -14,6 +14,8 @@
|
|||||||
--button-bg: var(--accent);
|
--button-bg: var(--accent);
|
||||||
--button-hover: #d67b8a;
|
--button-hover: #d67b8a;
|
||||||
--shadow: rgba(245, 169, 184, 0.3);
|
--shadow: rgba(245, 169, 184, 0.3);
|
||||||
|
--details-bg: #18181a;
|
||||||
|
--details-border: #2a2a2a;
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -83,18 +85,93 @@
|
|||||||
background-color: var(--button-hover);
|
background-color: var(--button-hover);
|
||||||
outline: none;
|
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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<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>
|
<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>
|
<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>
|
<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>
|
<button id="save">Save</button>
|
||||||
|
|
||||||
|
<footer id="version">You are running version: (unavailable)</footer>
|
||||||
</div>
|
</div>
|
||||||
<script src="options.js"></script>
|
<script src="options.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
88
options.js
88
options.js
@ -1,17 +1,81 @@
|
|||||||
/* global chrome */
|
/* global chrome */
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
chrome.storage.sync.get(['endpoint', 'token', 'artworkEndpoint'], (data) => {
|
const keys = [
|
||||||
document.getElementById('endpoint').value = data.endpoint || '';
|
'endpoint',
|
||||||
document.getElementById('token').value = data.token || '';
|
'token',
|
||||||
document.getElementById('artworkEndpoint').value = data.artworkEndpoint || '';
|
'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', () => {
|
chrome.storage.sync.get(keys, (data) => {
|
||||||
const endpoint = document.getElementById('endpoint').value;
|
for (const key of keys) {
|
||||||
const token = document.getElementById('token').value;
|
const elem = document.getElementById(key);
|
||||||
const artworkEndpoint = document.getElementById('artworkEndpoint').value;
|
if (elem) {
|
||||||
chrome.storage.sync.set({ endpoint, token, artworkEndpoint }, () => {
|
elem.value = data[key] != null ? String(data[key]) : '';
|
||||||
alert('Settings saved.');
|
} 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