// Radio Emulator // Kaotisk Hund 2024 // // A simple co-mechanism to pretend playing a live radio as it would happen for // a radio station that mostly plays prerecoded shows. // // Client side implementation // Let's remind here the structures we are waiting for: // 1. Hash // 2. 0 // 3. audio/ogg file // 4. application/json file // // 1. Hash // Can be an SHA512sum or SHA256sum, we don't do checks, we only ask the hash // accompanied by what do we think it is. // // 2. 0 // When nothing exists on the radio station we are visiting. // // 3. audio/ogg file // An audio file to play. // // 4. application/json file // Could be one of the following: // - list // - show_info // var debugMode = false; var audioElement = document.querySelector('audio'); var sourceElement = document.querySelector('source'); var currentTimeP = document.querySelector('.current-time'); var listStartedP = document.querySelector('.started-on'); var currentShowHash = document.querySelector('.current-show-hash'); var listHash = document.querySelector('.list-hash'); var artistP = document.querySelector('.artist'); var titleP = document.querySelector('.title'); var radioPlayerDiv = document.querySelector('.radio-player'); var youAreHere = document.querySelector('.you-are-here'); var ProgressBar = document.querySelector('progress'); var startsOnP = document.querySelector('.starts-on'); var showDurationP = document.querySelector('.show-duration'); var relativeTime = document.querySelector('.relative-time'); const documentTitle = "Radio Station Emulator"; const separator = " :: "; function setTitleSyncing(){ document.title = "🔃Syncing" + separator + documentTitle; } function setTitleMessage(message){ document.title = message + separator + documentTitle; } var seconds_here = 0; function increaseSeconds() { seconds_here = seconds_here+1; youAreHere.innerText = seconds_here; ProgressBar.value = parseInt(currentTimeP.innerText) + seconds_here; relativeTime.innerText = parseInt(currentTimeP.innerText) + seconds_here; return seconds_here; } function getSecondsHere() { return seconds_here; } setInterval(increaseSeconds, 1000); function FetchJSON( url, callback, params ) { setTitleSyncing(); const request = new XMLHttpRequest(); request.addEventListener("load", ()=>{ var json = JSON.parse(request.response); if(request.status !== 404){ callback(json, params); } else { if ( debugMode ) console.log(`ERROR ${request.status} while loading ${url}`); } }); request.addEventListener("error", ()=>{ if ( debugMode ) console.log("An error occured. While loading: "+url+" for "+callback+"."); }); request.addEventListener("abort", ()=>{ if ( debugMode ) console.log("Request aborted: "+url+" for "+callback+"."); }); request.open("GET", url); request.send(); } function genericCallback(json, params) { if ( debugMode ) console.log('genericCallback'); if ( debugMode ) console.log(json); if ( debugMode ) console.log(params); } var calledLoadShowCallback = 0; function stopHereAndReflect() { return 0; } function loadShowCallback(json, params) { const [ list, now_on_sequence, element, hash_of_list ] = params; if ( debugMode ) console.log('loadShowCallback'); if ( debugMode ) console.log(json); if ( debugMode ) console.log(element); if ( debugMode ) console.log(params); listStartedP.innerText = list.started_on; listHash.innerText = hash_of_list; currentShowHash.innerText = json.hash; artistP.innerText = json.artist; titleP.innerText = json.title; setTitleMessage ( "▶️ " + json.artist + " - " + json.title ); showDurationP.innerText = Math.floor(element.duration/1000); startsOnP.innerText = element.starts_on; ProgressBar.max = Math.floor(element.duration/1000); sourceElement.src = "http://z.kaotisk-hund.com:8010/v0/audio/ogg/" + json.hash + "#t=" + Math.floor((now_on_sequence - element.starts_on)/1000); sourceElement.type = json.mimetype; audioElement.load(); if ( debugMode ) console.log('plays here: '+(now_on_sequence - element.starts_on)/1000); audioElement.addEventListener('canplaythrough', function(){ if ( debugMode ) console.log('CAN PLAY THROUGH'); if ( calledLoadShowCallback < 100 ) { calledLoadShowCallback++; currentTimeP.innerText = ((now_on_sequence - element.starts_on)/1000); audioElement.currentTime = ((now_on_sequence - element.starts_on)/1000); audioElement.play(); } }); audioElement.addEventListener('ended', function(){ location.reload(); }); var synced_at = sync_radio(); if ( debugMode ) console.log("Synced initially at : "+synced_at); if ( synced_at === 0 ) { if ( synced_at > audioElement.currentTime ) { if ( debugMode ) console.log('Good sync!'); } else { setTimeout(sync_radio, 10000); } } } function sync_radio() { var value = currentTimeP.innerText; if ( value !== "" ) { var new_now = parseFloat(value) + getSecondsHere(); if ( debugMode ) console.log("Trying to sync @ "+ value + " + " +getSecondsHere() + " = " + new_now); audioElement.currentTime = new_now; return new_now; } return 0; } function listCallback(json, params) { if ( debugMode ) console.log('listCallback'); var [ now, hash_of_list ] = params; var delta_time = now - json.started_on; var min_times_played = Math.floor( delta_time / json.duration ); var max_times_to_be_played = delta_time / json.duration; var Dt = max_times_to_be_played - min_times_played; var now_on_sequence = Dt * json.duration; if ( debugMode ) console.log(`now_on_sequence: ${now_on_sequence}, Dt: ${Dt}`) previous = { starts_on: 0 }; json.list.forEach((element)=>{ if ( now_on_sequence < element.starts_on && now_on_sequence > previous.starts_on){ } else { now_on_sequence = now_on_sequence - previous.starts_on; if ( debugMode ) console.log(now_on_sequence); previous = element; if ( debugMode ) console.log(element); FetchJSON("http://z.kaotisk-hund.com:8010/v0/application/json/" + element.hash, loadShowCallback, [json, now_on_sequence, element, hash_of_list]); } }); } function hashCallback(json, params) { var [ now ] = params; if ( debugMode ) console.log('hashCallback'); FetchJSON('http://z.kaotisk-hund.com:8010/v0/application/json/' + json.latest_list, listCallback, [now, json.latest_list]); } FetchJSON('http://z.kaotisk-hund.com:8010/v0/list', hashCallback, [ new Date().getTime() ]);