diff options
| author | Miles Wilson <miles@pro.mileswilson.net> | 2025-05-15 15:47:09 -0400 |
|---|---|---|
| committer | Miles Wilson <miles@pro.mileswilson.net> | 2025-05-15 15:47:09 -0400 |
| commit | bf6c05629b894908562fe93fb6f38728e4df6e98 (patch) | |
| tree | 972e906854cfa822dc6055093630731027c9f887 | |
| download | spinitron-now-playing-main.tar.gz spinitron-now-playing-main.tar.bz2 spinitron-now-playing-main.zip | |
Diffstat (limited to '')
| -rw-r--r-- | player.css | 184 | ||||
| -rw-r--r-- | player.js | 147 | ||||
| -rw-r--r-- | readme.md | 2 | ||||
| -rw-r--r-- | spinitron-now-playing.php | 294 |
4 files changed, 627 insertions, 0 deletions
diff --git a/player.css b/player.css new file mode 100644 index 0000000..294a914 --- /dev/null +++ b/player.css @@ -0,0 +1,184 @@ +/* Player container */ +.spinitron-player { + position: fixed; + bottom: 0; + left: 0; + right: 0; + background: #1a1a1a; + color: white; + padding: 1rem; + z-index: 10000; + box-shadow: 0 -2px 10px rgba(0,0,0,0.2); + display: flex; + gap: 2rem; + align-items: center; + transition: transform 0.3s ease; +} + +.spinitron-player.hidden { + transform: translateY(100%); +} + +/* Play button */ +.spinitron-play-button { + background: #4CAF50; + border: none; + padding: 0.8rem 1.5rem; + border-radius: 5px; + color: white; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; /* Center content horizontally */ + gap: 0.5rem; + min-width: 120px; + transition: background 0.3s ease; + text-align: center; /* Ensure text is centered */ +} + +.spinitron-play-button:hover { + background: #45a049; +} + +/* Metadata container */ +.spinitron-metadata { + flex-grow: 1; + display: flex; + gap: 2rem; + overflow: hidden; +} + +/* Song and show info */ +.spinitron-song-info, +.spinitron-show-info { + display: flex; + flex-direction: column; + gap: 0.3rem; + min-width: 0; +} + +/* Status labels */ +.spinitron-now-playing { + font-size: 0.9em; + text-transform: uppercase; + letter-spacing: 0.5px; + transition: color 0.3s ease; +} + +/* Title text */ +.spinitron-title { + font-weight: bold; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.spinitron-title a, +#spinitronArtistAlbum a, +#spinitronEpisodeName a, +#spinitronSongTitle, +#spinitronShowTitle { + color: #ccc !important; +} + +/* Artist/album and episode info */ +#spinitronArtistAlbum, +#spinitronEpisodeName { + font-size: 0.9em; + color: #ccc; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.spinitron-title a, +#spinitronArtistAlbum a, +#spinitronEpisodeName a { + color: inherit; + text-decoration: none; +} + +.spinitron-title a:hover, +#spinitronArtistAlbum a:hover, +#spinitronEpisodeName a:hover { + text-decoration: underline; +} + +.listen-now a { + color: #4CAF50 !important; + font-weight: bold; +} + +/* Responsive design */ +@media (max-width: 768px) { + .spinitron-player { + flex-direction: column; + align-items: flex-start; + gap: 1rem; + padding: 1rem; + } + + .spinitron-metadata { + flex-direction: column; + gap: 1rem; + width: 100%; + } + + .spinitron-play-button { + width: 100%; /* Make button full width on mobile */ + justify-content: center; /* Keep content centered */ + } +} + + + + +/* Recently played marquee */ +#spin-recent { + background: #222; + color: #fff; + padding: 10px 0; + overflow: hidden; + white-space: nowrap; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); +} + +#ticker { + display: flex; + animation: ticker-scroll 20s linear infinite; +} + +#ticker p { + margin: 0; + display: flex; + gap: 2rem; +} + +#ticker span { + display: inline-block; + padding-right: 2rem; +} + +#ticker a { + color: #4CAF50; + text-decoration: none; +} + +#ticker a:hover { + text-decoration: underline; +} + +.recent-link { + color: inherit; + text-decoration: none; +} + +.recent-link:hover { + text-decoration: underline; +} + +/* Marquee animation */ +@keyframes ticker-scroll { + 0% { transform: translateX(100%); } + 100% { transform: translateX(-100%); } +} diff --git a/player.js b/player.js new file mode 100644 index 0000000..d26d284 --- /dev/null +++ b/player.js @@ -0,0 +1,147 @@ +document.addEventListener('DOMContentLoaded', function() { + const audio = document.getElementById('spinitronAudioPlayer'); + const playButton = document.getElementById('spinitronPlayButton'); + let updateInterval; + let statusInterval; + let currentSpinId = null; + let currentEndTime = null; + + // Update metadata from the API + function updateMetadata() { + try { + const response = await fetch(spinitronConfig.endpoint); + const data = await response.json(); + + if (data.status === 'ok') { + // Update song metadata links + const songTitle = document.getElementById('spinitronSongTitle'); + songTitle.textContent = data.spin_data.song || '-'; + songTitle.href = `https://spinitron.com/WVAU/spin/${data.spin_data.id}`; + + const artistLink = document.getElementById('spinitronArtist'); + artistLink.textContent = data.spin_data.artist || '-'; + artistLink.href = `https://spinitron.com/WVAU/artist/${encodeURIComponent(data.spin_data.artist)}`; + + const releaseLink = document.getElementById('spinitronRelease'); + releaseLink.textContent = data.spin_data.release || '-'; + releaseLink.href = `https://spinitron.com/WVAU/release/${encodeURIComponent(data.spin_data.release)}`; + + // Update show metadata links + const showTitle = document.getElementById('spinitronShowTitle'); + showTitle.textContent = data.playlist_data.title || '-'; + showTitle.href = `https://spinitron.com/WVAU/playlist/${data.playlist_data.id}`; + + const episodeLink = document.getElementById('spinitronEpisode'); + episodeLink.textContent = data.playlist_data.episode_name || '-'; + episodeLink.href = `https://spinitron.com/WVAU/playlist/${data.playlist_data.id}`; + } + } catch (error) { + console.error('Error fetching metadata:', error); + } + } + + // Update the "NOW PLAYING" / "LAST PLAYED" status labels + function updateStatusLabels() { + if (!currentEndTime) return; + + const now = Date.now(); + const endTime = currentEndTime.getTime(); + const secondsSinceEnd = (now - endTime) / 1000; + const labels = document.querySelectorAll('.spinitron-now-playing'); + + labels.forEach(label => { + if (secondsSinceEnd > 60) { + label.textContent = 'LAST PLAYED (?)'; + label.style.color = '#ff4444'; + } else if (secondsSinceEnd > 30) { + label.textContent = 'LAST PLAYED'; + label.style.color = '#ffa500'; + } else { + label.textContent = 'NOW PLAYING'; + label.style.color = '#888'; + } + }); + } + + // Toggle audio playback + function togglePlayback() { + if (audio.paused) { + audio.play().catch(error => console.error('Audio playback failed:', error)); + playButton.innerHTML = '<span>⏸</span> <span class="play-text">Pause</span>'; + updateMetadata(); + updateInterval = setInterval(updateMetadata, spinitronConfig.cacheDuration); + } else { + audio.pause(); + playButton.innerHTML = '<span>▶</span> <span class="play-text">Play</span>'; + clearInterval(updateInterval); + } + } + + // Initial metadata check + updateMetadata(); + + // Event listeners + playButton.addEventListener('click', togglePlayback); + + // Handle audio ending + audio.addEventListener('ended', () => { + playButton.innerHTML = '<span>▶</span> <span class="play-text">Play</span>'; + clearInterval(updateInterval); + }); + + // Auto-update metadata when tab becomes visible + document.addEventListener('visibilitychange', () => { + if (!document.hidden) { + updateMetadata(); + updateStatusLabels(); + } + }); +}); + + + + +// Recently Played Marquee Updates +async function updateRecentlyPlayed() { + try { + const response = await fetch(spinitronConfig.endpoint); + const data = await response.json(); + + if (data.status === 'ok' && data.spin_data) { + const spin = data.spin_data; + const playlist = data.playlist_data; + const template = document.getElementById('recent-spin-template').innerHTML; + const container = document.getElementById('recent-spin-container'); + + const recentSpinHtml = template + .replace(/{time}/g, new Date(spin.start).toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }).toLowerCase()) + .replace(/{song}/g, spin.song || 'Unknown Song') + .replace(/{artist}/g, spin.artist || 'Unknown Artist') + .replace(/{release}/g, spin.release || 'Unknown Release') + .replace(/{spin_id}/g, spin.id) + .replace(/{artist_id}/g, encodeURIComponent(spin.artist)) + .replace(/{release_id}/g, encodeURIComponent(spin.release)); + + container.innerHTML = recentSpinHtml; + } + } catch (error) { + console.error('Error updating recently played:', error); + } +} + +// Start updating the recently played marquee +if (document.getElementById('spin-recent')) { + updateRecentlyPlayed(); // Initial update + setInterval(updateRecentlyPlayed, spinitronConfig.cacheDuration); // Periodic updates +} + +// Pause on hover +const ticker = document.getElementById('ticker'); +if (ticker) { + ticker.addEventListener('mouseenter', () => { + ticker.style.animationPlayState = 'paused'; + }); + ticker.addEventListener('mouseleave', () => { + ticker.style.animationPlayState = 'running'; + }); +}
\ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..3238384 --- /dev/null +++ b/readme.md @@ -0,0 +1,2 @@ +# Spinitron Now Playing +Wordpress plugin that automatically pulls ***now playing*** data from Spinitron and registers a shortcode. diff --git a/spinitron-now-playing.php b/spinitron-now-playing.php new file mode 100644 index 0000000..53b51ce --- /dev/null +++ b/spinitron-now-playing.php @@ -0,0 +1,294 @@ +<?php +/* +Plugin Name: Spinitron Now Playing +Description: Fetches and caches now playing data from Spinitron API. Provides a REST endpoint at /wp-json/spinitron/v1/now-playing. +Version: 1.0 +Author: Miles Wilson +*/ + +// Prevent direct access +defined( 'ABSPATH' ) || exit; + +// Add admin menu +add_action( 'admin_menu', 'spinitron_add_admin_menu' ); +add_action( 'admin_init', 'spinitron_settings_init' ); + +function spinitron_add_admin_menu() { + add_options_page( + 'Spinitron Now Playing Settings', + 'Spinitron Now Playing', + 'manage_options', + 'spinitron-now-playing', + 'spinitron_options_page_html' + ); +} + +function spinitron_settings_init() { + register_setting( 'spinitron', 'spinitron_api_key', array( + 'sanitize_callback' => 'sanitize_text_field', + ) ); + register_setting( 'spinitron', 'spinitron_cache_duration', array( + 'sanitize_callback' => 'absint', + ) ); + register_setting( 'spinitron', 'spinitron_audio_stream_url', array( + 'sanitize_callback' => 'esc_url_raw', + ) ); + + add_settings_section( + 'spinitron_section', + 'API Settings', + 'spinitron_section_callback', + 'spinitron' + ); + + add_settings_field( + 'spinitron_api_key', + 'API Key', + 'spinitron_api_key_render', + 'spinitron', + 'spinitron_section' + ); + + add_settings_field( + 'spinitron_cache_duration', + 'Cache Duration (seconds)', + 'spinitron_cache_duration_render', + 'spinitron', + 'spinitron_section' + ); + + add_settings_field( + 'spinitron_audio_stream_url', + 'Audio Stream URL', + 'spinitron_audio_stream_url_render', + 'spinitron', + 'spinitron_section' + ); +} + +function spinitron_api_key_render() { + $api_key = get_option( 'spinitron_api_key' ); + echo '<input type="text" name="spinitron_api_key" value="' . esc_attr( $api_key ) . '" class="regular-text">'; +} + +function spinitron_cache_duration_render() { + $cache_duration = get_option( 'spinitron_cache_duration', 60 ); + echo '<input type="number" name="spinitron_cache_duration" value="' . esc_attr( $cache_duration ) . '" min="1">'; +} + +function spinitron_section_callback() { + echo '<p>Enter your Spinitron API credentials and cache settings below:</p>'; +} + +function spinitron_options_page_html() { + if ( ! current_user_can( 'manage_options' ) ) return; + ?> + <div class="wrap"> + <h1>Spinitron Now Playing Settings</h1> + <form action="options.php" method="post"> + <?php + settings_fields( 'spinitron' ); + do_settings_sections( 'spinitron' ); + submit_button( 'Save Settings' ); + ?> + </form> + </div> + <?php +} + +// Register REST API endpoint +add_action( 'rest_api_init', function () { + register_rest_route( 'spinitron/v1', '/now-playing', array( + 'methods' => 'GET', + 'callback' => 'spinitron_now_playing_endpoint', + 'permission_callback' => '__return_true' + ) ); +} ); + +function spinitron_now_playing_endpoint( $request ) { + $api_key = get_option( 'spinitron_api_key' ); + $cache_duration = get_option( 'spinitron_cache_duration', 60 ); + $cache_duration = max( 1, (int) $cache_duration ); + + // Check API key + if ( empty( $api_key ) ) { + return new WP_Error( 'api_key_missing', 'Spinitron API key is not configured.', array( 'status' => 500 ) ); + } + + // Check cache + $cached_data = get_transient( 'spinitron_now_playing_data' ); + if ( $cached_data !== false ) { + return spinitron_create_response( $cached_data ); + } + + // Fetch new data + $spin_url = 'https://spinitron.com/api/spins?count=1&sort=start&direction=desc'; + $args = array( + 'headers' => array( + 'Authorization' => 'Bearer ' . $api_key, + 'Accept' => 'application/json', + 'User-Agent' => 'WordPress Spinitron Now Playing', + ), + 'timeout' => 5, + ); + + // Fetch spin data + $spin_response = wp_remote_get( $spin_url, $args ); + if ( is_wp_error( $spin_response ) ) { + $error_data = array( + 'status' => 'error', + 'error' => 'API request failed: ' . $spin_response->get_error_message(), + 'last_updated' => time(), + ); + set_transient( 'spinitron_now_playing_data', $error_data, $cache_duration ); + return spinitron_create_response( $error_data ); + } + + $spin_body = wp_remote_retrieve_body( $spin_response ); + $spin_data = json_decode( $spin_body, true ); + + if ( empty( $spin_data['items'] ) ) { + $error_data = array( + 'status' => 'error', + 'error' => 'No spins found in response', + 'last_updated' => time(), + ); + set_transient( 'spinitron_now_playing_data', $error_data, $cache_duration ); + return spinitron_create_response( $error_data ); + } + + $spin_item = $spin_data['items'][0]; + + // Fetch playlist data + if ( empty( $spin_item['_links']['playlist']['href'] ) ) { + $error_data = array( + 'status' => 'error', + 'error' => 'Spin data missing playlist link', + 'spin_data' => $spin_item, + 'last_updated' => time(), + ); + set_transient( 'spinitron_now_playing_data', $error_data, $cache_duration ); + return spinitron_create_response( $error_data ); + } + + $playlist_url = $spin_item['_links']['playlist']['href']; + $playlist_response = wp_remote_get( $playlist_url, $args ); + + if ( is_wp_error( $playlist_response ) ) { + $error_data = array( + 'status' => 'error', + 'error' => 'Playlist request failed: ' . $playlist_response->get_error_message(), + 'spin_data' => $spin_item, + 'last_updated' => time(), + ); + set_transient( 'spinitron_now_playing_data', $error_data, $cache_duration ); + return spinitron_create_response( $error_data ); + } + + $playlist_body = wp_remote_retrieve_body( $playlist_response ); + $playlist_data = json_decode( $playlist_body, true ); + + // Successful response + $result = array( + 'status' => 'ok', + 'spin_data' => $spin_item, + 'playlist_data' => $playlist_data, + 'error' => null, + 'last_updated' => time(), + ); + + set_transient( 'spinitron_now_playing_data', $result, $cache_duration ); + return spinitron_create_response( $result ); +} + +function spinitron_create_response( $data ) { + $response = new WP_REST_Response( $data ); + $response->header( 'Access-Control-Allow-Origin', '*' ); + $response->header( 'Cache-Control', 'no-cache, max-age=0' ); + return $response; +} + +// Add shortcode for player +add_shortcode('spinitron_player', 'spinitron_player_shortcode'); + +function spinitron_player_shortcode() { + $audio_url = get_option('spinitron_audio_stream_url'); + + ob_start(); ?> + <div class="spinitron-player"> + <button class="spinitron-play-button" id="spinitronPlayButton"> + <span>▶</span> <span class="play-text">Play</span> + </button> + + <div class="spinitron-metadata" id="spinitronMetadata"> + <div class="spinitron-song-info"> + <div class="spinitron-now-playing">NOW PLAYING</div> + <div class="spinitron-title"> + <a href="https://spinitron.com/WVAU/" target="_blank" id="spinitronSongTitle" style="color: #ccc">-</a> + </div> + <div id="spinitronArtistAlbum"> + by <a href="https://spinitron.com/WVAU/" target="_blank" id="spinitronArtist" style="color: #ccc">-</a> + from <a href="https://spinitron.com/WVAU/" target="_blank" id="spinitronRelease" style="color: #ccc">-</a> + </div> + </div> + + <div class="spinitron-show-info"> + <div class="spinitron-now-playing">CURRENT SHOW</div> + <div class="spinitron-title"> + <a href="https://spinitron.com/WVAU/" target="_blank" id="spinitronShowTitle" style="color: #ccc">-</a> + </div> + <div id="spinitronEpisodeName"> + <a href="https://spinitron.com/WVAU/" target="_blank" id="spinitronEpisode" style="color: #ccc">-</a> + </div> + </div> + </div> + + <audio id="spinitronAudioPlayer" src="<?php echo esc_url($audio_url); ?>"></audio> + </div> + <?php + return ob_get_clean(); +} + +function spinitron_audio_stream_url_render() { + $audio_url = get_option('spinitron_audio_stream_url'); + echo '<input type="url" name="spinitron_audio_stream_url" value="' . esc_url($audio_url) . '" class="regular-text">'; +} + +// Enqueue assets +add_action('wp_enqueue_scripts', 'spinitron_enqueue_assets'); + +function spinitron_enqueue_assets() { + wp_enqueue_style('spinitron-player', plugins_url('player.css', __FILE__)); + wp_enqueue_script('spinitron-player', plugins_url('player.js', __FILE__), array(), false, true); + + wp_localize_script('spinitron-player', 'spinitronConfig', array( + 'endpoint' => rest_url('spinitron/v1/now-playing'), + 'cacheDuration' => get_option('spinitron_cache_duration', 60) * 1000 + )); +} + +// Recently Played Marquee Shortcode +add_shortcode('spinitron_recently_played', 'spinitron_recently_played_shortcode'); + +function spinitron_recently_played_shortcode() { + ob_start(); ?> + <div id="spin-recent"> + <div id="ticker"> + <p> + <span id="recent-spin-template" style="display: none;"> + <a href="https://spinitron.com/WVAU/spin/{spin_id}" target="_blank" class="recent-link"> + {time} <em>“{song}”</em> + </a> + by <a href="https://spinitron.com/WVAU/artist/{artist_id}" target="_blank" class="recent-link"><b>{artist}</b></a> + from <a href="https://spinitron.com/WVAU/release/{release_id}" target="_blank" class="recent-link">{release}</a> + </span> + <span id="recent-spin-container"></span> + <span class="listen-now"> + <em><a href="<?php echo esc_url(get_option('spinitron_audio_stream_url')); ?>" target="_blank">LISTEN NOW!</a></em> + </span> + </p> + </div> + </div> + <?php + return ob_get_clean(); +} |
