Browse Source

Player: current-playlist label + off-air button/gif state

Current playlist/set name from the stream metadata: /api/status now returns the
Icecast mount `genre` (set in the broadcast, e.g. "June 2016"), shown as a small
"♫ <genre>" label under the now-playing title. Hidden off-air and when blank.

Off-air state when the audio stream stops: previously only the tower/eq gifs
swapped to their off art while the play button sat on "playing" over dead audio.
Now the button also resets to paused (stopping the stalled audio) and dims to a
greyed, not-allowed look; it re-enables when the stream returns. The click
interceptor is refined to allow a *pause* while offline -- it only blocks
attempts to *play* into a stream that isn't there -- which is what lets the reset
stop playback without popping the connection dialog.

Verified across online -> stopped -> back-on-air transitions: button paused +
dimmed and gifs off when stopped, both restored when live.
windhamdavid 3 weeks ago
parent
commit
2d7d511051
4 changed files with 42 additions and 1 deletions
  1. 4 0
      app.js
  2. 19 0
      src/css/main.css
  3. 1 0
      src/index.html
  4. 18 1
      src/js/radio.js

+ 4 - 0
app.js

@@ -292,6 +292,10 @@ function normalizeIcecastStatus(payload, mount) {
   return {
     online: true,
     title: source.title ?? source.yp_currently_playing ?? null,
+    // Icecast's mount `genre` field, repurposed as the current playlist/set name
+    // (e.g. "June 2016"). It's set in the broadcast metadata, so it changes when
+    // the source is reconfigured, not per-track.
+    genre: source.genre ?? null,
     listeners: source.listeners ?? 0,
     peakListeners: source.listener_peak ?? 0,
     bitrate,

+ 19 - 0
src/css/main.css

@@ -207,6 +207,18 @@ body {
   background-color: #dadada;
 }
 
+/* Current playlist/set name (from the stream's genre metadata), under the
+   now-playing title in the player. */
+.current-playlist {
+  margin-top: 4px;
+  font-size: 12px;
+  font-style: italic;
+  color: #777;
+  text-align: center;
+}
+.current-playlist::before { content: "\266B\00a0"; } /* ♫ + nbsp */
+.current-playlist[hidden] { display: none; }
+
 /* Player tab icons in the page's slate instead of Bootstrap's link blue. These
    sit on the white player panel, so a dark colour reads fine.
    Bootstrap's own `.nav-tabs > li.active > a` is more specific, so the active
@@ -381,6 +393,13 @@ meter {
 	-webkit-transition: all .1s linear;
 	transition: all .1s linear;
 }
+/* Off air: the stream stopped, so dim the play button and mark it inactive.
+   opacity + grayscale dims it regardless of how the play/pause glyph is drawn. */
+#amplitude-play-pause.off-air {
+  opacity: 0.4;
+  filter: grayscale(1);
+  cursor: not-allowed;
+}
 .amplitude-paused {  
   color:rgba(88, 185, 87, .7);
   display: block;

+ 1 - 0
src/index.html

@@ -77,6 +77,7 @@
                </div>
                <div class="well current-track">
                   <h6 style="margin:0;"><img id="eq" src="" title="eq" width="12px" style="float:left;margin:0 10px 0 0;"> <span id="amplitude-now-playing-name"><span id="track">* Off Air *</span></span></h6>
+                  <div id="playlist" class="current-playlist" hidden></div>
                </div>
             </div>
             <!-- END Audio Player -->

+ 18 - 1
src/js/radio.js

@@ -219,6 +219,13 @@ var interval = null;
 // as off air quietly in the background.
 function showOffAirArt() {
   $('#track').text('* Off Air *');
+  $('#playlist').prop('hidden', true).text('');
+  // If we were playing when the stream dropped, pause it so the button doesn't
+  // sit on "playing" over dead audio (the interceptor allows this pause), and
+  // dim it to show there's nothing to play into.
+  var btn = document.getElementById('amplitude-play-pause');
+  if (btn && btn.classList.contains('amplitude-playing')) btn.click();
+  $('#amplitude-play-pause').addClass('off-air');
   $('#radio').attr('src', get_radio_none()).fadeIn(300);
   $('#eq').attr('src', get_radio_eq_none()).fadeIn(300);
 }
@@ -255,9 +262,15 @@ function radioTitle() {
       // Back on air: close the dialog if someone's sitting in front of it.
       $('#connection-error').modal('hide');
       $('#track').text(status.title || 'Unknown track');
+      // Genre from the stream metadata, shown as the current playlist/set name.
+      // Toggle the `hidden` prop (the CSS keys off [hidden]) so an empty genre
+      // doesn't leave a stray music-note bullet.
+      if (status.genre) $('#playlist').text(status.genre).prop('hidden', false);
+      else $('#playlist').prop('hidden', true).text('');
       $('#listeners').text(status.listeners);
       $('#peak-listeners').text(status.peakListeners);
       $('#bitrate').text(status.bitrate === null ? '--' : status.bitrate);
+      $('#amplitude-play-pause').removeClass('off-air'); // back on air: re-enable
       $('#radio').attr('src', get_radio_tower()).fadeIn(300);
       $('#eq').attr('src', get_radio_eq()).fadeIn(300);
 
@@ -291,8 +304,12 @@ $(document).ready(function() {
   // doomed playback attempt against a dead mount and nothing tells the user.
   document.addEventListener('click', function (e) {
     if (!e.target || !e.target.closest) return;
-    if (!e.target.closest('#amplitude-play-pause')) return;
+    var btn = e.target.closest('#amplitude-play-pause');
+    if (!btn) return;
     if (streamOnline) return;   // let Amplitude have it
+    // Offline: pausing a now-dead stream is fine (and is how the off-air reset
+    // stops it) — only block attempts to *play* into a stream that isn't there.
+    if (btn.classList.contains('amplitude-playing')) return;
     e.stopPropagation();
     e.preventDefault();
     showConnectionError();