4 Commits
1.3.4 ... main

Author SHA1 Message Date
a9ff5434ed Add README 2025-06-23 15:15:42 -07:00
676fed9f2c change placeholder text, updated settings page
So now it just pulls a url from placeholder.co and converts it to base64, why? because it lessens client requests and makes it easier to handle in the frontend.

also added notes to the settings page to explain the new functionality, with the artwork api
2025-06-23 14:06:39 -07:00
d66f112971 Updated to handle the separate post requests for artwork, and other now playing data, hope it works lol 2025-06-21 22:58:53 -07:00
1e75266545 fix issue with .amnp#widget :3 2025-06-21 16:24:46 -07:00
3 changed files with 340 additions and 118 deletions

86
README.MD Normal file
View File

@ -0,0 +1,86 @@
# AmNP For Wordpress
---
## Features
* REST API endpoint for now playing data & separate endpoint for artwork updates
* Bearer authentication
* Admin panel for key management
* Shortcode: `[now-playing-widget]`
* Sidebar/Footer widget support
* Graceful fallback for NoScript peeps
---
## REST API Endpoints
| Endpoint | Method | Description |
| --------------------------------------- | ------ | ---------------------------- |
| `/wp-json/nowplaying/v1/update` | `POST` | Updates now playing metadata |
| `/wp-json/nowplaying/v1/update/artwork` | `POST` | Updates album artwork |
All requests must include an `Authorization` header in the format:
```
Authorization: Bearer 73757065725f7365637572655f746f6b656e
```
---
## Admin Setup
After installing the plugin:
1. Go to **Settings -> Now Playing API**
2. Set or generate a secure 64-character API key
3. Use the displayed endpoint URLs for POST requests from the [AmNP Extention](https://git.oldgate.org/amnp/ext-amnp)
---
## Usage
### Shortcode
Add the widget to any post or page using:
```
[now-playing-widget]
```
### Widget
Go to **Appearance → Widgets**, then drag the **Now Playing Widget** into your sidebar or footer.
### Example of Widget
![Widget showing from my website playing: Misician by Porter Robinson ](https://share.aatkinson.org/nirO5/wuQAWePU81.png/raw)
---
## Output
The widget displays:
* Album artwork (and when you hover over it, it shows the album name!)
* Song title
* Artist name
* Playback status
* Progress bar and time
If JavaScript is disabled, a static fallback will display the last known song and artwork.
---
## File Output
Two JSON files are saved to the plugin directory:
* `nowplaying.json` -> song metadata
* `nowplaying-art.json` -> album artwork (base64 or URL)
---
## License
This project is licensed under the [MIT License](https://opensource.org/licenses/MIT).

View File

@ -1,4 +1,4 @@
.amnp #widget {
.amnp#widget {
background: #2a2a2a;
padding: 20px;
border-radius: 8px;

View File

@ -2,121 +2,203 @@
/*
Plugin Name: Apple Music Now Playing API
Description: Accepts JSON now-playing updates via REST, lets user set a secure API key on the plugin page, a shortcode widget, that can be added to Footer
Version: 1.3.4
Version: 1.4.1
Author: Sophia Atkinson
*/
defined('ABSPATH') || exit;
defined("ABSPATH") || exit();
// === Register REST API endpoint ===
add_action('rest_api_init', function () {
register_rest_route('nowplaying/v1', '/update', [
'methods' => 'POST',
'callback' => 'nowplaying_api_handle_post',
'permission_callback' => '__return_true',
// === Register REST API endpoints ===
add_action("rest_api_init", function () {
register_rest_route("nowplaying/v1", "/update", [
"methods" => "POST",
"callback" => "nowplaying_api_handle_post",
"permission_callback" => "__return_true",
]);
register_rest_route("nowplaying/v1", "/update/artwork", [
"methods" => "POST",
"callback" => "nowplaying_api_handle_art_post",
"permission_callback" => "__return_true",
]);
});
function nowplaying_api_handle_post(WP_REST_Request $request) {
$stored_key = get_option('nowplaying_api_key');
$auth_header = $request->get_header('authorization');
function nowplaying_api_handle_post(WP_REST_Request $request)
{
$stored_key = get_option("nowplaying_api_key");
$auth_header = $request->get_header("authorization");
if (!$auth_header || $auth_header !== "Bearer $stored_key") {
return new WP_REST_Response('Forbidden: Invalid API key.', 403);
$expected_prefix = "bearer ";
$auth_header_normalized = strtolower(trim($auth_header ?? ""));
if (
!$auth_header ||
stripos($auth_header_normalized, $expected_prefix) !== 0 ||
substr(trim($auth_header), strlen($expected_prefix)) !== $stored_key
) {
return new WP_REST_Response("Forbidden: Invalid API key.", 403);
}
$data = json_decode($request->get_body(), true);
if ($data === null) {
return new WP_REST_Response('Invalid JSON.', 400);
return new WP_REST_Response("Invalid JSON.", 400);
}
$file_path = plugin_dir_path(__FILE__) . 'nowplaying.json';
$file_path = plugin_dir_path(__FILE__) . "nowplaying.json";
$written = file_put_contents(
$file_path,
json_encode($data, JSON_PRETTY_PRINT)
);
try {
$written = file_put_contents(
$file_path,
json_encode($data, JSON_PRETTY_PRINT)
);
if ($written === false) {
throw new Exception();
}
} catch (Exception $e) {
return new WP_REST_Response("Failed to write to nowplaying.json.", 500);
}
return new WP_REST_Response("Now Playing updated.", 200);
}
function nowplaying_api_handle_art_post(WP_REST_Request $request)
{
$stored_key = get_option("nowplaying_api_key");
$auth_header = $request->get_header("authorization");
if (!$auth_header || $auth_header !== "Bearer $stored_key") {
return new WP_REST_Response("Forbidden: Invalid API key.", 403);
}
$data = json_decode($request->get_body(), true);
if ($data === null) {
return new WP_REST_Response("Invalid JSON.", 400);
}
if (!isset($data["image"]) || !is_string($data["image"])) {
return new WP_REST_Response(
'Missing or invalid "image" property.',
400
);
}
$file_path = plugin_dir_path(__FILE__) . "nowplaying-art.json";
try {
$written = file_put_contents($file_path, json_encode($data, JSON_PRETTY_PRINT));
if (!$written) throw new Exception();
$written = file_put_contents(
$file_path,
json_encode($data, JSON_PRETTY_PRINT)
);
if ($written === false) {
throw new Exception();
}
} catch (Exception $e) {
return new WP_REST_Response('Failed to write to nowplaying.json.', 500);
return new WP_REST_Response(
"Failed to write to nowplaying-art.json.",
500
);
}
return new WP_REST_Response('Now Playing updated.', 200);
return new WP_REST_Response("Artwork updated.", 200);
}
// === Admin Settings Page ===
add_action('admin_menu', function () {
add_action("admin_menu", function () {
add_options_page(
'Now Playing API Settings',
'Now Playing API',
'manage_options',
'nowplaying-api',
'nowplaying_api_settings_page'
"Now Playing API Settings",
"Now Playing API",
"manage_options",
"nowplaying-api",
"nowplaying_api_settings_page"
);
});
function nowplaying_api_settings_page() {
if (!current_user_can('manage_options')) return;
function nowplaying_api_settings_page()
{
if (!current_user_can("manage_options")) {
return;
}
if (isset($_POST['nowplaying_api_key']) && check_admin_referer('nowplaying_save_key', 'nowplaying_nonce')) {
$new_key = sanitize_text_field($_POST['nowplaying_api_key']);
if (
isset($_POST["nowplaying_api_key"]) &&
check_admin_referer("nowplaying_save_key", "nowplaying_nonce")
) {
$new_key = sanitize_text_field($_POST["nowplaying_api_key"]);
if (strlen($new_key) === 64) {
update_option('nowplaying_api_key', $new_key);
update_option("nowplaying_api_key", $new_key);
echo '<div class="updated"><p>API key updated.</p></div>';
} else {
echo '<div class="error"><p>API key must be exactly 64 characters.</p></div>';
}
}
if (isset($_POST['generate_key']) && check_admin_referer('nowplaying_save_key', 'nowplaying_nonce')) {
if (
isset($_POST["generate_key"]) &&
check_admin_referer("nowplaying_save_key", "nowplaying_nonce")
) {
$generated_key = bin2hex(random_bytes(32));
update_option('nowplaying_api_key', $generated_key);
update_option("nowplaying_api_key", $generated_key);
echo '<div class="updated"><p>New API key generated.</p></div>';
}
$current_key = get_option('nowplaying_api_key');
$current_key = get_option("nowplaying_api_key");
if (!$current_key) {
$current_key = bin2hex(random_bytes(32));
update_option('nowplaying_api_key', $current_key);
update_option("nowplaying_api_key", $current_key);
}
$url = esc_url(site_url('/wp-json/nowplaying/v1/update'));
$url = esc_url(site_url("/wp-json/nowplaying/v1/update"));
$artwork_url = esc_url(site_url("/wp-json/nowplaying/v1/update/artwork"));
?>
<div class="wrap">
<h1>Apple Music Now Playing API Settings</h1>
<p>This is the key you will use for the web extension to communicate with this server. The POST address is:</p>
<code><?php echo $url; ?></code>
<p>This is the key you will use for the web extension to communicate with this server. The POST addresses are:</p>
Now playing Endpoint: <code><?php echo $url; ?></code>
<br />
Artwork Endpoint: <code><?php echo $artwork_url; ?></code>
<form method="post">
<?php wp_nonce_field('nowplaying_save_key', 'nowplaying_nonce'); ?>
<?php wp_nonce_field("nowplaying_save_key", "nowplaying_nonce"); ?>
<table class="form-table">
<tr>
<th><label for="nowplaying_api_key">Current API Key</label></th>
<td>
<input type="text" name="nowplaying_api_key" value="<?php echo esc_attr($current_key); ?>" class="regular-text" maxlength="64" />
<input type="text" name="nowplaying_api_key" value="<?php echo esc_attr(
$current_key
); ?>" class="regular-text" maxlength="64" />
<p class="description">Keep this key secret. <br> Must be 64 characters.</p>
</td>
</tr>
</table>
<?php submit_button('Save API Key'); ?>
<?php submit_button("Save API Key"); ?>
</form>
<form method="post" style="margin-top: 20px;">
<?php wp_nonce_field('nowplaying_save_key', 'nowplaying_nonce'); ?>
<?php submit_button('Generate New API Key', 'secondary', 'generate_key'); ?>
<?php wp_nonce_field("nowplaying_save_key", "nowplaying_nonce"); ?>
<?php submit_button(
"Generate New API Key",
"secondary",
"generate_key"
); ?>
</form>
</div>
<?php
}
// === Shortcode [now-playing-widget] ===
add_shortcode('now-playing-widget', function () {
$plugin_url = plugin_dir_url(__FILE__) . 'nowplaying.json';
add_shortcode("now-playing-widget", function () {
$plugin_url = plugin_dir_url(__FILE__) . "nowplaying.json";
$art_url = plugin_dir_url(__FILE__) . "nowplaying-art.json";
ob_start();
?>
<?php $css_url = plugin_dir_url(__FILE__) . 'assets/css/amnp-style.css'; ?>
<?php $placeholder_image = 'data:image/svg+xml;base64,' . base64_encode(file_get_contents('https://placehold.co/150/1E1F22/FFF?text=Nothing%20playing')); ?>
<?php $css_url = plugin_dir_url(__FILE__) . "assets/css/amnp-style.css"; ?>
<link rel="stylesheet" type="text/css" href="<?php echo esc_url($css_url); ?>">
<div class="amnp hasjs" id="widget">
<img title="" id="cover" src="data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22150%22%20height%3D%22150%22%20viewBox%3D%220%200%20150%20150%22%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22%231E1F22%22%2F%3E%3Cpath%20fill%3D%22%23FFF%22%20d%3D%22M31.66%2073.75h1.61q.59%200%201.03-.15t.73-.42q.29-.28.44-.68.14-.39.14-.88%200-.46-.14-.84-.15-.38-.43-.64-.29-.26-.73-.4t-1.04-.14h-1.61zm-2.15-5.82h3.76q1.16%200%202.01.27.84.28%201.4.76.55.49.82%201.17.26.68.26%201.49%200%20.84-.28%201.54t-.84%201.2q-.56.51-1.4.79t-1.97.28h-1.61v4.07h-2.15zm9.61-.32h1.98V79.5h-1.98zm8.29%209.77V76q-.85.04-1.44.14-.58.11-.93.28-.36.17-.51.39-.15.23-.15.49%200%20.52.31.74.31.23.8.23.61%200%201.05-.22.45-.22.87-.67m-4.17-4.31-.36-.63q1.42-1.3%203.41-1.3.72%200%201.29.24.57.23.96.65t.6%201.01q.2.58.2%201.28v5.18h-.9q-.28%200-.43-.08-.15-.09-.24-.34l-.17-.6q-.32.28-.61.5-.3.21-.62.35-.32.15-.68.22-.37.08-.81.08-.52%200-.96-.14t-.76-.42-.49-.7q-.18-.41-.18-.97%200-.31.11-.62.1-.3.34-.58.23-.28.61-.53.37-.25.92-.43.55-.19%201.28-.3.73-.12%201.66-.14v-.48q0-.83-.35-1.22-.35-.4-1.02-.4-.48%200-.79.11-.32.11-.56.25t-.43.26q-.2.11-.44.11-.21%200-.35-.11-.15-.11-.23-.25m13.83-1.78h1.58l-4.47%2010.47q-.09.2-.23.31t-.43.11h-1.47l1.54-3.3-3.32-7.59H52q.24%200%20.37.11.13.12.19.26l1.75%204.26q.09.21.15.43.06.21.11.44.07-.23.15-.45.07-.22.16-.43l1.64-4.25q.07-.16.22-.26.14-.11.33-.11m2.63%200h1.98v8.21H59.7zm2.26-2.39q0%20.26-.11.48-.1.22-.27.39-.18.17-.41.27t-.49.1-.49-.1-.39-.27q-.17-.17-.27-.39t-.1-.48.1-.5q.1-.23.27-.4.16-.16.39-.26t.49-.1.49.1.41.26q.17.17.27.4.11.24.11.5m3.45%202.75.14.65q.25-.25.52-.46.28-.21.59-.36.3-.15.66-.23.35-.09.76-.09.68%200%201.2.23t.86.64q.35.41.53.99.18.57.18%201.26v5.22h-1.97v-5.22q0-.76-.35-1.17-.34-.41-1.05-.41-.51%200-.96.23-.44.23-.84.63v5.94H63.7v-8.21h1.21q.38%200%20.5.36m10.31%203.55q.37%200%20.64-.1.28-.1.46-.28t.27-.42q.09-.25.09-.55%200-.61-.36-.96-.37-.36-1.1-.36-.72%200-1.09.36-.36.35-.36.96%200%20.29.09.54t.27.43.46.28q.27.1.63.1m2.24%204.67q0-.24-.15-.39-.14-.16-.39-.24t-.58-.12-.7-.06l-.77-.04q-.4-.02-.77-.06-.33.18-.54.43-.2.25-.2.57%200%20.22.11.41t.34.32q.24.14.61.21.38.08.92.08.56%200%20.96-.08.4-.09.66-.24.26-.14.38-.35.12-.2.12-.44m-.4-8.3h2.36v.74q0%20.35-.42.43l-.74.14q.17.42.17.92%200%20.61-.24%201.1-.25.5-.68.84t-1.02.53-1.27.19q-.24%200-.46-.02-.22-.03-.44-.07-.38.23-.38.52%200%20.25.22.37.23.11.61.16.37.05.85.06t.99.05q.5.04.98.14t.86.32q.37.21.6.59.23.37.23.95%200%20.55-.27%201.06t-.77.91q-.51.4-1.25.64-.74.25-1.69.25-.92%200-1.61-.18t-1.15-.48q-.45-.3-.68-.69-.22-.4-.22-.82%200-.58.35-.96.35-.39.95-.62-.32-.17-.52-.45-.19-.28-.19-.74%200-.18.07-.38.07-.19.2-.39.13-.19.33-.36t.47-.31q-.62-.33-.98-.89-.35-.56-.35-1.32%200-.6.24-1.1.25-.49.68-.84.44-.34%201.03-.53.6-.18%201.3-.18.53%200%201%20.11.46.1.84.31m15.67-3.64h1.89V79.5h-1.1q-.26%200-.43-.08-.17-.09-.33-.29l-6.04-7.71q.05.53.05.98v7.1h-1.9V67.93h1.13q.14%200%20.24.01.1.02.17.05.08.04.15.11.07.06.16.18l6.06%207.74-.04-.55q-.01-.27-.01-.51zm7.79%203.23q.91%200%201.66.3t1.28.84.82%201.33q.29.78.29%201.75%200%20.98-.29%201.76t-.82%201.34q-.53.55-1.28.84-.75.3-1.66.3-.92%200-1.67-.3-.75-.29-1.29-.84-.53-.56-.82-1.34t-.29-1.76q0-.97.29-1.75.29-.79.82-1.33.54-.54%201.29-.84t1.67-.3m0%206.94q1.02%200%201.52-.69.49-.69.49-2.01%200-1.33-.49-2.03-.5-.69-1.52-.69-1.04%200-1.54.7t-.5%202.02.5%202.01%201.54.69m10.66-6.81h1.97v8.21h-1.21q-.39%200-.49-.36l-.14-.66q-.5.52-1.11.83-.61.32-1.43.32-.67%200-1.19-.23t-.87-.64q-.35-.42-.53-.99t-.18-1.26v-5.22h1.98v5.22q0%20.75.34%201.16.35.41%201.05.41.51%200%20.96-.22.45-.23.85-.63zm6.66%208.34q-1.07%200-1.64-.61-.58-.6-.58-1.66v-4.59h-.84q-.16%200-.27-.1-.11-.11-.11-.31v-.79l1.32-.21.42-2.24q.04-.16.15-.25t.29-.09h1.02v2.58h2.19v1.41h-2.19v4.45q0%20.38.19.6t.51.22q.19%200%20.31-.05.13-.04.22-.09l.16-.09q.07-.05.15-.05t.14.05q.06.04.12.13l.59.96q-.43.36-.99.54-.56.19-1.16.19%22%2F%3E%3C%2Fsvg%3E" alt="Album cover" />
<img title="" id="cover" src="<?php echo $placeholder_image; ?>" alt="Album cover" />
<div title="" id="title">Loading...</div>
<div title="" id="artist"></div>
<div id="status"></div>
@ -124,17 +206,19 @@ add_shortcode('now-playing-widget', function () {
<div id="time-info"><span id="current-time">0:00</span><span id="total-time">0:00</span></div>
</div>
<?php
$json_path = plugin_dir_path(__FILE__) . 'nowplaying.json';
$nowplaying_data = @json_decode(file_get_contents($json_path), true) ?? [];
$np_details = esc_html($nowplaying_data['details'] ?? 'Not playing');
$np_artist = esc_html($nowplaying_data['state'] ?? '');
$np_status = isset($nowplaying_data['paused']) && $nowplaying_data['paused'] ? 'Paused' : 'Playing';
$np_cover = esc_attr($nowplaying_data['artworkBase64'] ?? '');
$np_album = esc_attr($nowplaying_data['album'] ?? '');
$json_path = plugin_dir_path(__FILE__) . "nowplaying.json";
$art_path = plugin_dir_path(__FILE__) . "nowplaying-art.json";
$nowplaying_data = @json_decode(file_get_contents($json_path), true) ?? [];
$art_data = @json_decode(file_get_contents($art_path), true) ?? [];
$np_details = esc_html($nowplaying_data["details"] ?? "Not playing");
$np_artist = esc_html($nowplaying_data["artist"] ?? "");
$np_status = isset($nowplaying_data["paused"]) && $nowplaying_data["paused"] ? "Paused" : "Playing";
$np_cover = esc_attr($art_data["image"] ?? ($nowplaying_data["artworkBase64"] ?? ""));
$np_album = esc_attr($nowplaying_data["album"] ?? "");
?>
<noscript>
<div class="amnp" id="widget">
<img src="<?php echo $np_cover ?: '...' ?>" alt="Album cover" title="<?php echo $np_album; ?>" />
<img src="<?php echo $np_cover ?: $placeholder_image; ?>" alt="Album cover" title="<?php echo $np_album; ?>" />
<div title="<?php echo $np_details; ?>"><?php echo $np_details; ?></div>
<div title="<?php echo $np_artist; ?>"><?php echo $np_artist; ?></div>
<div><?php echo $np_status; ?></div>
@ -142,84 +226,136 @@ add_shortcode('now-playing-widget', function () {
</noscript>
<script>
let lastData={}, intervalId=null;
function formatTime(s){if(!s||isNaN(s))return"0:00";let m=Math.floor(s/60),sec=Math.floor(s%60);return m+":"+(sec<10?"0":"")+sec}
function deepEqual(a,b){return JSON.stringify(a)===JSON.stringify(b)}
async function fetchNowPlaying(){
if(document.hidden)return;
try{
const res=await fetch('<?php echo esc_url($plugin_url); ?>?'+Date.now(),{cache:'no-store'});
if(!res.ok)throw new Error('Network error');
const data=await res.json();
if(deepEqual(data,lastData))return;
lastData=data;
const {details="Not playing", state="", paused, artworkBase64='', currentTime=0, duration=0, album=""} = data;
document.getElementById('title').textContent = details;
document.getElementById('title').title = details;
document.getElementById('artist').textContent = state;
document.getElementById('artist').title = state;
if (artworkBase64) document.getElementById('cover').src = artworkBase64;
document.getElementById('cover').alt = details + " cover";
document.getElementById('cover').title = album;
document.getElementById('status').textContent = paused ? "Paused" : "Playing";
document.getElementById('progress-bar').style.width = (duration > 0 ? (currentTime / duration) * 100 : 0) + '%';
document.getElementById('current-time').textContent = formatTime(currentTime);
document.getElementById('total-time').textContent = formatTime(duration);
}catch(err){console.error("Fetch failed:",err)}
let lastNowPlaying = null;
let lastSongKey = null;
let lastArtImage = null;
let intervalId = null;
function formatTime(s){if(!s||isNaN(s)){return "0:00"}let m=Math.floor(s/60),sec=Math.floor(s%60);return m+":"+(sec<10?"0":"")+sec}
async function fetchJson(url) {
try {
const res = await fetch(url + '?' + Date.now(), { cache: 'no-store' });
if (!res.ok) throw new Error(`Fetch failed: ${res.status}`);
return await res.json();
} catch (e) {
console.error('Fetch error:', e);
return null;
}
function startFetching(){if(intervalId===null){fetchNowPlaying();intervalId=setInterval(fetchNowPlaying,1000)}}
function stopFetching(){if(intervalId!==null){clearInterval(intervalId);intervalId=null}}
const widget=document.getElementById('widget');
let widgetVisible=false;
const observer=new IntersectionObserver((entries)=>{entries.forEach(entry=>{widgetVisible=entry.isIntersecting;updateFetchingState()})},{threshold:0.1});
observer.observe(widget);
document.addEventListener('visibilitychange',updateFetchingState);
function updateFetchingState(){if(!document.hidden&&widgetVisible){startFetching()}else{stopFetching()}}
updateFetchingState();
</script>
<?php
return ob_get_clean();
});
}
async function updateWidget() {
if (document.hidden) return;
const nowPlaying = await fetchJson('<?php echo esc_url($plugin_url); ?>');
if (!nowPlaying) return;
const currentSongKey = (nowPlaying.details || '') + '|' + (nowPlaying.album || '');
if (JSON.stringify(nowPlaying) !== JSON.stringify(lastNowPlaying)) {
lastNowPlaying = nowPlaying;
// === Register Customizer Widget Support with Title Option ===
class Now_Playing_Widget extends WP_Widget {
public function __construct() {
document.getElementById('title').textContent = nowPlaying.details || 'Not playing';
document.getElementById('title').title = nowPlaying.details || '';
document.getElementById('artist').textContent = nowPlaying.state || '';
document.getElementById('artist').title = nowPlaying.state || '';
document.getElementById('status').textContent = nowPlaying.paused ? 'Paused' : 'Playing';
const currentTime = typeof nowPlaying.currentTime === 'number' ? nowPlaying.currentTime : 0;
const duration = typeof nowPlaying.duration === 'number' ? nowPlaying.duration : 0;
document.getElementById('progress-bar').style.width =
(duration > 0 ? (currentTime / duration) * 100 : 0) + '%';
document.getElementById('current-time').textContent = formatTime(nowPlaying.currentTime);
document.getElementById('total-time').textContent = formatTime(nowPlaying.duration);
}
if (currentSongKey !== lastSongKey) {
lastSongKey = currentSongKey;
const artData = await fetchJson('<?php echo esc_url($art_url); ?>');
let image = null;
if (artData?.image) {
image = artData.image;
} else if (nowPlaying.artworkBase64) {
image = nowPlaying.artworkBase64;
}
if (image && image !== lastArtImage) {
lastArtImage = image;
const cover = document.getElementById('cover');
cover.src = image;
cover.alt = nowPlaying.details || 'Album cover';
cover.title = nowPlaying.album || '';
}
}
}
function startLoop() {
if (intervalId === null) {
updateWidget();
intervalId = setInterval(updateWidget, 1000);
}
}
function stopLoop(){if (intervalId !== null) {clearInterval(intervalId);intervalId = null;}}
const widget = document.getElementById('widget');
let isVisible = false;
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {isVisible = entry.isIntersecting;updateFetchState();});
}, { threshold: 0.1 });
function updateFetchState() {
if (!document.hidden && isVisible) {
startLoop();
} else {
stopLoop();
}
}
observer.observe(widget);
document.addEventListener('visibilitychange', updateFetchState);
updateFetchState();
</script>
<?php return ob_get_clean();
}); // === Register Customizer Widget Support with Title Option ===
class Now_Playing_Widget extends WP_Widget
{
public function __construct()
{
parent::__construct(
'now_playing_widget',
__('Now Playing Widget', 'nowplaying'),
['description' => __('Displays the current Apple Music song from your API.', 'nowplaying')]
"now_playing_widget",
__("Now Playing Widget", "nowplaying"),
[
"description" => __(
"Displays the current Apple Music song from your API.",
"nowplaying"
),
]
);
}
public function widget($args, $instance) {
$title = apply_filters('widget_title', $instance['title'] ?? '');
echo $args['before_widget'];
public function widget($args, $instance)
{
$title = apply_filters("widget_title", $instance["title"] ?? "");
echo $args["before_widget"];
if (!empty($title)) {
echo $args['before_title'] . esc_html($title) . $args['after_title'];
echo $args["before_title"] .
esc_html($title) .
$args["after_title"];
}
echo do_shortcode('[now-playing-widget]');
echo $args['after_widget'];
echo do_shortcode("[now-playing-widget]");
echo $args["after_widget"];
}
public function form($instance) {
$title = esc_attr($instance['title'] ?? '');
?>
public function form($instance)
{
$title = esc_attr($instance["title"] ?? ""); ?>
<p>
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php _e('Title:'); ?></label>
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>"
name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text"
<label for="<?php echo esc_attr(
$this->get_field_id("title")
); ?>"><?php _e("Title:"); ?></label>
<input class="widefat" id="<?php echo esc_attr(
$this->get_field_id("title")
); ?>"
name="<?php echo esc_attr(
$this->get_field_name("title")
); ?>" type="text"
value="<?php echo $title; ?>" />
</p>
<?php
}
public function update($new_instance, $old_instance) {
public function update($new_instance, $old_instance)
{
$instance = [];
$instance['title'] = sanitize_text_field($new_instance['title'] ?? '');
$instance["title"] = sanitize_text_field($new_instance["title"] ?? "");
return $instance;
}
}
add_action('widgets_init', function () {
register_widget('Now_Playing_Widget');
add_action("widgets_init", function () {
register_widget("Now_Playing_Widget");
});