From bf6c05629b894908562fe93fb6f38728e4df6e98 Mon Sep 17 00:00:00 2001 From: Miles Wilson Date: Thu, 15 May 2025 15:47:09 -0400 Subject: Initial commit. --- player.css | 184 +++++++++++++++++++++++++++++ player.js | 147 +++++++++++++++++++++++ readme.md | 2 + spinitron-now-playing.php | 294 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 627 insertions(+) create mode 100644 player.css create mode 100644 player.js create mode 100644 readme.md create mode 100644 spinitron-now-playing.php 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 = ' Pause'; + updateMetadata(); + updateInterval = setInterval(updateMetadata, spinitronConfig.cacheDuration); + } else { + audio.pause(); + playButton.innerHTML = ' Play'; + clearInterval(updateInterval); + } + } + + // Initial metadata check + updateMetadata(); + + // Event listeners + playButton.addEventListener('click', togglePlayback); + + // Handle audio ending + audio.addEventListener('ended', () => { + playButton.innerHTML = ' Play'; + 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 @@ + '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 ''; +} + +function spinitron_cache_duration_render() { + $cache_duration = get_option( 'spinitron_cache_duration', 60 ); + echo ''; +} + +function spinitron_section_callback() { + echo '

Enter your Spinitron API credentials and cache settings below:

'; +} + +function spinitron_options_page_html() { + if ( ! current_user_can( 'manage_options' ) ) return; + ?> +
+

Spinitron Now Playing Settings

+
+ +
+
+ '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(); ?> +
+ + + + + +
+ '; +} + +// 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(); ?> +
+ +
+