AzuraCast / AzuraCast

A self-hosted web radio management suite, including turnkey installer tools for the full radio software stack and a modern, easy-to-use web app to manage your stations.
https://www.azuracast.com/
GNU Affero General Public License v3.0
3.1k stars 569 forks source link

Implement a Customizable Embeddable Radio Player Widget #3217

Open BusterNeece opened 4 years ago

BusterNeece commented 4 years ago

We've already abstracted out a large amount of our public radio player's code to a Vue component, which makes the idea of having it be a heavily customizable widget even more doable in a fairly short period of time.

Basically, what we're looking for in the initial implementation of a customizable embeddable widget would be:

rainbow-bamboo commented 9 months ago

I've found a way to make a custom player from within my application.

I've modified the embed to be able to

  1. Emit an event containing its status such as the song title and track progress (so I can render it from my application).
  2. Receive messages that can make it play/pause

1. Getting the Status of the Player

// Add to the embed through Public JS in the branding page of the admin panel

function radioStatus(){
  if (document.getElementsByClassName('radio-control-play-button')[0]) {
  const playState = document.getElementsByClassName('radio-control-play-button')[0].ariaLabel
  const albumArt = document.getElementsByClassName('album_art')[0].src
  const title = document.getElementsByClassName('now-playing-title')[0].innerText
  const artist = document.getElementsByClassName('now-playing-artist')[0].innerText
  const trackProgress = document.getElementsByClassName('progress-bar bg-secondary')[0].style.width
  const volumeLevel = document.querySelector('.radio-control-volume-slider .form-range').value

  return JSON.stringify(
    {    playState: playState,
         albumArt: albumArt,
         title: title,
         artist: artist,
         trackProgress: trackProgress,
         volumeLevel: volumeLevel})
  } else{
    return false
  }

};

function broadcastRadioInfo() {
 parent.postMessage(radioStatus(), "<domain of website/localhost>");
};

const intervalID = setInterval(broadcastRadioInfo, 500);
// this will cause the event to emit every 500ms

From the client I can then read the status of the radio by adding an EventListener

// Your client code

window.addEventListener("message", function(e) {
    var message = e.data;
    var origin = e.origin;
    if (origin === "https://<url-of-radio-station>" && message) {
        const radioStatus= JSON.parse(message);
});

2. Controlling the player from my application

I send messages to it from my application with an instruction object where play is a boolean representing what state you want the player to be in. if you want it to play or if you want it to stop.

document.getElementById("<id of the player iframe>").contentWindow.postMessage(
  JSON.stringify({ play: true, volume: 50 }),
  "https://<url-of-radio-station>"
);

which are in turn handled from the player by adding an event listener.

// Add to the embed through Public JS in the branding page of the admin panel

const SAFE_ORIGINS = ["https://<your-domain>", "http://<localhost-if-developing>"]

window.addEventListener('message', function(e) {
    var message = e.data;
    var origin = e.origin;
    if (SAFE_ORIGINS.includes(origin)) {
        const instructions = JSON.parse(message);
        const playState = document.getElementsByClassName('radio-control-play-button')[0].ariaLabel;
        if (playState == "Play" && instructions.play) {
            document.getElementsByClassName('radio-control-play-button')[0].click();
        } else if (playState == "Stop" && !instructions.play) {
            document.getElementsByClassName('radio-control-play-button')[0].click();
        }

        if (instructions.volume) {
            // This approach for changing the volume does not work because I believe that it changes the input value,
            // but not the state of the Vue component directly.
            const volumeSelector = document.querySelector('.radio-control-volume-slider .form-range');
            volumeSelector.value = instructions.volume;
        }
    }
});

Considerations

References

Accessing cross domain iFrame contents MDN Docs for postMessage