add radio playlist edit in UI
This commit is contained in:
+8
-8
@@ -6,8 +6,8 @@ log.setLevel("debug");
|
||||
var cp = require("child_process");
|
||||
var fs = require("fs");
|
||||
var { getMeta } = require("webradio-metadata");
|
||||
var Dl = require("../adblockradio-dl/dl.js");
|
||||
var config = require("./config.js");
|
||||
var { StreamDl } = require("../adblockradio-dl/dl.js");
|
||||
//var config = require("./config.js");
|
||||
|
||||
class Db {
|
||||
constructor(options) {
|
||||
@@ -15,8 +15,8 @@ class Db {
|
||||
this.name = options.name;
|
||||
this.path = options.path;
|
||||
this.ext = options.ext;
|
||||
this.audioCache = new AudioCache({ bitrate: options.bitrate, cacheLen: config.user.cacheLen });
|
||||
this.metaCache = new MetaCache({ cacheLen: config.user.cacheLen });
|
||||
this.audioCache = new AudioCache({ bitrate: options.bitrate, cacheLen: options.cacheLen });
|
||||
this.metaCache = new MetaCache({ cacheLen: options.cacheLen });
|
||||
}
|
||||
|
||||
dirDate(now) {
|
||||
@@ -208,9 +208,9 @@ class MetaCache extends Writable {
|
||||
} else {
|
||||
var samePayload = true;
|
||||
for (var key in meta.payload) {
|
||||
if (meta.payload[key] !== this.meta[meta.type][this.meta[meta.type].length-1].payload[key]) {
|
||||
if ("" + meta.payload[key] && "" + meta.payload[key] !== "" + this.meta[meta.type][this.meta[meta.type].length-1].payload[key]) {
|
||||
samePayload = false;
|
||||
//log.debug("MetaCache: _write: different payload key=" + key + " new=" + meta.payload[key] + " vs old=" + this.meta[meta.type][this.meta[meta.type].length-1].payload[key]);
|
||||
log.debug("MetaCache: _write: different payload key=" + key + " new=" + meta.payload[key] + " vs old=" + this.meta[meta.type][this.meta[meta.type].length-1].payload[key]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -249,7 +249,7 @@ class MetaCache extends Writable {
|
||||
}
|
||||
|
||||
module.exports = function(radio, options) {
|
||||
var newDl = new Dl({ country: radio.country, name: radio.name, segDuration: options.segDuration });
|
||||
var newDl = new StreamDl({ country: radio.country, name: radio.name, segDuration: options.segDuration });
|
||||
var dbs = null;
|
||||
newDl.on("error", function(err) {
|
||||
log.error("dl err=" + err);
|
||||
@@ -258,7 +258,7 @@ module.exports = function(radio, options) {
|
||||
//metadataCallback(metadata);
|
||||
radio.url = metadata.url;
|
||||
radio.favicon = metadata.favicon;
|
||||
var db = new Db({ country: radio.country, name: radio.name, ext: metadata.ext, bitrate: metadata.bitrate, path: __dirname });
|
||||
var db = new Db({ country: radio.country, name: radio.name, ext: metadata.ext, bitrate: metadata.bitrate, cacheLen: options.cacheLen, path: __dirname });
|
||||
log.debug("DlFactory: " + radio.country + "_" + radio.name + " metadata=" + JSON.stringify(metadata));
|
||||
|
||||
newDl.on("data", function(dataObj) {
|
||||
|
||||
+83
-36
@@ -5,6 +5,7 @@ import './App.css';
|
||||
import Radio from './Radio.js';
|
||||
import Metadata from './Metadata.js';
|
||||
import DelayCanvas from './DelayCanvas.js';
|
||||
import Playlist from './Playlist.js';
|
||||
|
||||
import { load, refreshMetadata, HOST } from './load.js';
|
||||
import { play, stop } from './audio.js';
|
||||
@@ -15,6 +16,7 @@ import classNames from 'classnames';
|
||||
|
||||
import iconPlay from "./img/start_1279169.svg";
|
||||
import iconStop from "./img/stop_1279170.svg";
|
||||
import iconList from "./img/list_241386.svg";
|
||||
//import iconNext from "./img/next_607554.svg";
|
||||
|
||||
class App extends Component {
|
||||
@@ -25,57 +27,67 @@ class App extends Component {
|
||||
config: [],
|
||||
playingRadio: null,
|
||||
playingDate: null,
|
||||
clockDiff: 0
|
||||
clockDiff: 0,
|
||||
playlistEditMode: false
|
||||
}
|
||||
this.play = this.play.bind(this);
|
||||
this.seekBackward = this.seekBackward.bind(this);
|
||||
this.seekForward = this.seekForward.bind(this);
|
||||
this.switchPlaylistEditMode = this.switchPlaylistEditMode.bind(this);
|
||||
this.refreshMetadataContainer = this.refreshMetadataContainer.bind(this);
|
||||
this.refreshConfig = this.refreshConfig.bind(this);
|
||||
this.insertRadio = this.insertRadio.bind(this);
|
||||
this.removeRadio = this.removeRadio.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let self = this;
|
||||
this.refreshConfig();
|
||||
this.timerID = setInterval(() => this.tick(), 300);
|
||||
}
|
||||
|
||||
var refreshMetadataContainer = function() {
|
||||
var stateChange = {};
|
||||
var f = function(iradio, callback) {
|
||||
if (iradio >= self.state.config.radios.length) return callback();
|
||||
var radio = self.state.config.radios[iradio].country + "_" + self.state.config.radios[iradio].name;
|
||||
refreshMetadata(radio, function(metadata) {
|
||||
for (var type in metadata) {
|
||||
if (type === "now") {
|
||||
stateChange.clockDiff = +new Date() - metadata.now;
|
||||
} else {
|
||||
metadata[type][metadata[type].length-1].validTo = null;
|
||||
stateChange[radio + "|" + type] = metadata[type].reverse();
|
||||
}
|
||||
componentWillUnmount() {
|
||||
clearInterval(this.timerID);
|
||||
clearInterval(this.metadataTimerID);
|
||||
}
|
||||
|
||||
refreshMetadataContainer() {
|
||||
var self = this;
|
||||
var stateChange = {};
|
||||
var f = function(iradio, callback) {
|
||||
if (iradio >= self.state.config.radios.length) return callback();
|
||||
var radio = self.state.config.radios[iradio].country + "_" + self.state.config.radios[iradio].name;
|
||||
refreshMetadata(radio, function(metadata) {
|
||||
for (var type in metadata) {
|
||||
if (type === "now") {
|
||||
stateChange.clockDiff = +new Date() - metadata.now;
|
||||
} else {
|
||||
metadata[type][metadata[type].length-1].validTo = null;
|
||||
stateChange[radio + "|" + type] = metadata[type].reverse();
|
||||
}
|
||||
f(iradio+1, callback);
|
||||
});
|
||||
}
|
||||
f(0, function() {
|
||||
self.setState(stateChange);
|
||||
}
|
||||
f(iradio+1, callback);
|
||||
});
|
||||
}
|
||||
f(0, function() {
|
||||
self.setState(stateChange);
|
||||
});
|
||||
}
|
||||
|
||||
refreshConfig() {
|
||||
var self = this;
|
||||
load("/config?t=" + Math.round(Math.random()*1000000), function(res) {
|
||||
try {
|
||||
var config = JSON.parse(res);
|
||||
console.log(config);
|
||||
self.setState({ config: config }, function() {
|
||||
self.metadataTimerID = setInterval(refreshMetadataContainer, 2000);
|
||||
refreshMetadataContainer();
|
||||
self.metadataTimerID = setInterval(self.refreshMetadataContainer, 2000);
|
||||
self.refreshMetadataContainer();
|
||||
});
|
||||
} catch(e) {
|
||||
console.log("problem parsing JSON from server: " + e.message);
|
||||
}
|
||||
self.setState({ configLoaded: true });
|
||||
});
|
||||
this.timerID = setInterval(function() { self.tick() }, 300);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearInterval(this.timerID);
|
||||
clearInterval(this.metadataTimerID);
|
||||
}
|
||||
|
||||
tick() {
|
||||
@@ -87,7 +99,16 @@ class App extends Component {
|
||||
//var delay = +new Date() - this.state.clockDiff - (date ? new Date(date) : new Date();
|
||||
//var secondsDelay = Math.round(delay/1000);
|
||||
if (!radio) radio = this.state.playingRadio;
|
||||
if (!delay) delay = 0;
|
||||
if (!delay) {
|
||||
var startAvailable = new Date();
|
||||
for (var i=0; i<this.state[radio + "|metadata"].length; i++) {
|
||||
if (this.state[radio + "|metadata"][i].validFrom < startAvailable) {
|
||||
startAvailable = this.state[radio + "|metadata"][i].validFrom;
|
||||
}
|
||||
}
|
||||
delay = Math.min(Math.round(+this.state.date - startAvailable), this.state.config.user.cacheLen*1000*2/3) - 8000;
|
||||
}
|
||||
|
||||
console.log("Play: radio=" + radio + " delay=" + delay);
|
||||
this.setState({
|
||||
playingRadio: radio,
|
||||
@@ -96,7 +117,7 @@ class App extends Component {
|
||||
playingLive: delay === 0
|
||||
});
|
||||
|
||||
play(HOST + "/listen/" + radio + "/" + Math.round(delay/1000));
|
||||
play(HOST + "/listen/" + radio + "/" + Math.round(delay/1000) + "?t=" + Math.round(Math.random()*1000000));
|
||||
document.title = radio.split("_")[1] + " - Adblock Radio";
|
||||
} else {
|
||||
this.setState({
|
||||
@@ -127,6 +148,24 @@ class App extends Component {
|
||||
this.play(this.state.playingRadio, Math.max(this.state.playingDelay - 30000,0));
|
||||
}
|
||||
|
||||
switchPlaylistEditMode() {
|
||||
this.setState({ playlistEditMode: !this.state.playlistEditMode });
|
||||
}
|
||||
|
||||
insertRadio(country, name) {
|
||||
var self = this;
|
||||
load("/config/radios/insert/" + country + "/" + name + "?t=" + Math.round(Math.random()*1000000), function(res) {
|
||||
self.refreshConfig()
|
||||
});
|
||||
}
|
||||
|
||||
removeRadio(country, name) {
|
||||
var self = this;
|
||||
load("/config/radios/remove/" + country + "/" + name + "?t=" + Math.round(Math.random()*1000000), function(res) {
|
||||
self.refreshConfig()
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
let config = this.state.config;
|
||||
var self = this;
|
||||
@@ -178,6 +217,7 @@ class App extends Component {
|
||||
|
||||
var buttons = (
|
||||
<StatusButtonsContainer>
|
||||
<PlaybackButton className={classNames({ inactive: !self.state.playlistEditMode })} src={iconList} alt="Edit playlist" onClick={self.switchPlaylistEditMode} />
|
||||
<PlaybackButton className={classNames({ flip: true, inactive: !self.state.playingRadio })} src={iconPlay} alt="Backward 30s" onClick={self.seekBackward} />
|
||||
<PlaybackButton className={classNames({ inactive: !self.state.playingRadio })} src={iconStop} alt="Stop" onClick={() => self.play(null, null)} />
|
||||
<PlaybackButton className={classNames({ inactive: !self.state.playingRadio || self.state.playingLive })} src={iconPlay} alt="Forward 30s" onClick={self.seekForward} />
|
||||
@@ -189,32 +229,39 @@ class App extends Component {
|
||||
return (
|
||||
<AppParent>
|
||||
<AppView>
|
||||
<RadioList className={classNames({ withPreview: !self.state.playingRadio })}>
|
||||
<RadioList className={classNames({ withPreview: !self.state.playingRadio && !self.state.playlistEditMode })}>
|
||||
{config.radios.map(function(radio, i) {
|
||||
var playing = self.state.playingRadio === radio.country + "_" + radio.name;
|
||||
return (
|
||||
<Radio metadata={radio}
|
||||
playCallback={self.play}
|
||||
playing={playing}
|
||||
showMetadata={!self.state.playingRadio}
|
||||
showMetadata={!self.state.playingRadio && !self.state.playlistEditMode}
|
||||
liveMetadata={self.state[radio.country + "_" + radio.name + "|metadata"]}
|
||||
key={"radio" + i} />
|
||||
)
|
||||
})}
|
||||
</RadioList>
|
||||
{self.state.playingRadio &&
|
||||
{self.state.playingRadio && !self.state.playlistEditMode &&
|
||||
<Metadata playingRadio={self.state.playingRadio}
|
||||
playingDelay={self.state.playingDelay}
|
||||
metaList={self.state[self.state.playingRadio + "|metadata"]}
|
||||
date={+self.state.date - self.state.clockDiff}
|
||||
date={new Date(+self.state.date - self.state.clockDiff)}
|
||||
playCallback={self.play} />
|
||||
}
|
||||
{self.state.playlistEditMode &&
|
||||
<Playlist config={self.state.config}
|
||||
insertRadio={self.insertRadio}
|
||||
removeRadio={self.removeRadio} />
|
||||
}
|
||||
</AppView>
|
||||
<Controls>
|
||||
{status}
|
||||
{buttons}
|
||||
<DelayCanvas playing={!!self.state.playingRadio}
|
||||
playingDelay={self.state.playingDelay}
|
||||
<DelayCanvas playingDelay={self.state.playingDelay}
|
||||
metaList={self.state[self.state.playingRadio + "|metadata"]}
|
||||
date={new Date(+self.state.date - self.state.clockDiff)}
|
||||
inactive={!self.state.playingRadio || !self.state[self.state.playingRadio + "|metadata"]}
|
||||
cacheLen={self.state.config.user.cacheLen}
|
||||
playCallback={self.play} />
|
||||
{/*<PlayerStatus settings={this.props.settings} bsw={this.props.bsw} condensed={this.props.condensed} playbackAction={this.togglePlayer} />*/}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
//import styled from "styled-components";
|
||||
import classNames from 'classnames';
|
||||
//import classNames from 'classnames';
|
||||
|
||||
|
||||
class DelayCanvas extends Component {
|
||||
@@ -36,6 +36,7 @@ class DelayCanvas extends Component {
|
||||
}
|
||||
|
||||
getCursorPosition(event) {
|
||||
if (this.props.inactive) return;
|
||||
var rect = this.refs.canvas.getBoundingClientRect();
|
||||
var x = event.clientX - rect.left;
|
||||
|
||||
@@ -49,14 +50,38 @@ class DelayCanvas extends Component {
|
||||
this.play(newDelay);
|
||||
}
|
||||
|
||||
getAvailableCache() {
|
||||
var startAvailable = new Date();
|
||||
for (var i=0; i<this.props.metaList.length; i++) {
|
||||
if (this.props.metaList[i].validFrom < startAvailable) {
|
||||
startAvailable = this.props.metaList[i].validFrom;
|
||||
}
|
||||
}
|
||||
return (+this.props.date - startAvailable) / 1000;
|
||||
}
|
||||
|
||||
updateCanvas() {
|
||||
const ctx = this.refs.canvas.getContext("2d");
|
||||
let height = ctx.canvas.height; //parseFloat(this.props.style.height);
|
||||
let width = ctx.canvas.width; //parseFloat(this.props.style.width);
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
ctx.fillStyle = "rgba(0,0,128,1)";
|
||||
//console.log("canvas height=" + height + " px width=" + width + "px playingDelay=" + this.props.playingDelay + " cacheLen=" + this.props.cacheLen);
|
||||
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
if (this.props.inactive) {
|
||||
ctx.fillStyle = "rgba(128,128,128,1)"; // grey
|
||||
return ctx.fillRect(0, 0, width, height);
|
||||
}
|
||||
|
||||
ctx.fillStyle = "rgba(0,0,128,1)"; // blue
|
||||
ctx.fillRect(0, 0, Math.round(width*(1-this.props.playingDelay/1000/this.props.cacheLen)), height);
|
||||
|
||||
// fill unavailable audio in grey
|
||||
var startCache = this.getAvailableCache();
|
||||
if (startCache < this.props.cacheLen) {
|
||||
ctx.fillStyle = "rgba(128,128,128,1)"; // grey
|
||||
ctx.fillRect(0, 0, Math.round(width*(1-startCache/this.props.cacheLen)), height);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -67,8 +92,9 @@ class DelayCanvas extends Component {
|
||||
}
|
||||
|
||||
DelayCanvas.propTypes = {
|
||||
playing: PropTypes.bool.isRequired,
|
||||
playingDelay: PropTypes.number,
|
||||
metaList: PropTypes.array,
|
||||
date: PropTypes.object.isRequired,
|
||||
cacheLen: PropTypes.number.isRequired,
|
||||
playCallback: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@@ -36,7 +36,10 @@ class Metadata extends Component {
|
||||
this.props.metaList.map(function(item, i) {
|
||||
if (!item.payload || (self.props.maxItems && i >= self.props.maxItems)) return null
|
||||
//var playerTime = +self.props.playingDate;
|
||||
var playing = +item.validFrom - 1000 <= (self.props.date-self.props.playingDelay) && (!item.validTo || (self.props.date-self.props.playingDelay) < +item.validTo - 1000);
|
||||
var playing = false;
|
||||
if (self.props.date) {
|
||||
playing = +item.validFrom - 1000 <= (self.props.date-self.props.playingDelay) && (!item.validTo || (self.props.date-self.props.playingDelay) < +item.validTo - 1000);
|
||||
}
|
||||
//console.log("playing=" + playing + " start=" + item.start + " date=" + playerTime + " stop=" + item.end);
|
||||
return (
|
||||
<MetadataItem className={classNames({ playing: playing, compact: previewMode })} key={"item" + i} onClick={function() { self.play(item.validFrom); }}>
|
||||
@@ -60,6 +63,7 @@ Metadata.propTypes = {
|
||||
playingRadio: PropTypes.string.isRequired,
|
||||
playCallback: PropTypes.func.isRequired,
|
||||
metaList: PropTypes.array,
|
||||
date: PropTypes.object,
|
||||
maxItems: PropTypes.number
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
// Copyright (c) 2018 Alexandre Storelli
|
||||
|
||||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import styled from "styled-components";
|
||||
import { load } from './load.js';
|
||||
import classNames from 'classnames';
|
||||
import defaultCover from "./img/default_radio_logo.svg";
|
||||
|
||||
class Playlist extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.insert = this.insert.bind(this);
|
||||
this.remove = this.remove.bind(this);
|
||||
this.state = {
|
||||
radiosLoaded: false,
|
||||
radios: []
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let self = this;
|
||||
|
||||
load("/config/radios/available?t=" + Math.round(Math.random()*1000000), function(res) {
|
||||
try {
|
||||
var radios = JSON.parse(res);
|
||||
self.setState({ radios: radios });
|
||||
} catch(e) {
|
||||
console.log("problem parsing JSON from server: " + e.message);
|
||||
}
|
||||
self.setState({ radiosLoaded: true });
|
||||
});
|
||||
}
|
||||
|
||||
insert(country, name) {
|
||||
this.props.insertRadio(country, name);
|
||||
}
|
||||
|
||||
remove(country, name) {
|
||||
this.props.removeRadio(country, name);
|
||||
}
|
||||
|
||||
render() {
|
||||
var self = this;
|
||||
if (!this.state.radiosLoaded) {
|
||||
return (
|
||||
<p>Loading...</p>
|
||||
);
|
||||
}
|
||||
|
||||
var current = this.props.config.radios;
|
||||
var available = this.state.radios;
|
||||
var playlistFull = this.props.config.radios.length >= this.props.config.user.maxRadios;
|
||||
|
||||
return (
|
||||
<PlaylistContainer>
|
||||
<PlaylistSectionTitle>Your current favorites : click to remove</PlaylistSectionTitle>
|
||||
{current.map(function(radio, i) {
|
||||
return (
|
||||
<PlaylistItem className={classNames({ active: true })} key={"item" + i} onClick={function() { self.remove(radio.country, radio.name); }}>
|
||||
<PlaylistItemText>
|
||||
{radio.name}
|
||||
</PlaylistItemText>
|
||||
<PlaylistItemLogo src={radio.favicon || defaultCover} alt="logo" />
|
||||
</PlaylistItem>
|
||||
)
|
||||
})}
|
||||
{!playlistFull ?
|
||||
<PlaylistSectionTitle>More radios : click to add</PlaylistSectionTitle>
|
||||
:
|
||||
<PlaylistSectionTitle>More radios : make room in your favorites, then click to add</PlaylistSectionTitle>
|
||||
}
|
||||
{available.map(function(radio, i) {
|
||||
return (
|
||||
<PlaylistItem className={classNames({ active: !playlistFull })} key={"item" + i} onClick={function() { if (!playlistFull) self.insert(radio.country, radio.name); }}>
|
||||
<PlaylistItemText>
|
||||
{radio.name}
|
||||
</PlaylistItemText>
|
||||
<PlaylistItemLogo src={radio.favicon || defaultCover} alt="logo" />
|
||||
</PlaylistItem>
|
||||
)
|
||||
})}
|
||||
</PlaylistContainer>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Playlist.propTypes = {
|
||||
config: PropTypes.object.isRequired,
|
||||
insertRadio: PropTypes.func.isRequired,
|
||||
removeRadio: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
const PlaylistContainer = styled.div`
|
||||
flex-grow: 1;
|
||||
overflow-y: scroll;
|
||||
`;
|
||||
|
||||
const PlaylistSectionTitle = styled.h3`
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const PlaylistItem = styled.div`
|
||||
border: 2px solid #eee;
|
||||
border-radius: 10px;
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
flex-shrink: 0;
|
||||
background: #eee;
|
||||
display: flex;
|
||||
cursor: not-allowed;
|
||||
|
||||
&.active {
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
const PlaylistItemText = styled.p`
|
||||
flex-grow: 1;
|
||||
align-self: center;
|
||||
`
|
||||
|
||||
const PlaylistItemLogo = styled.img`
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
align-self: center;
|
||||
margin-left: 10px;
|
||||
`;
|
||||
|
||||
export default Playlist;
|
||||
|
||||
/*&.playing {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}*/
|
||||
@@ -0,0 +1 @@
|
||||
<svg width='200' height='200' fill="#000000" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve"><path fill="#000000" d="M50,5C25.147,5,5,25.147,5,50s20.147,45,45,45s45-20.147,45-45S74.853,5,50,5z M30.312,68.281 c-2.33,0-4.219-1.889-4.219-4.219s1.889-4.219,4.219-4.219s4.219,1.889,4.219,4.219S32.642,68.281,30.312,68.281z M30.312,54.219 c-2.33,0-4.219-1.889-4.219-4.219s1.889-4.219,4.219-4.219S34.531,47.67,34.531,50S32.642,54.219,30.312,54.219z M30.312,40.156 c-2.33,0-4.219-1.889-4.219-4.219s1.889-4.219,4.219-4.219s4.219,1.889,4.219,4.219S32.642,40.156,30.312,40.156z M69.688,68.281 H41.562c-2.33,0-4.219-1.889-4.219-4.219s1.889-4.219,4.219-4.219h28.125c2.33,0,4.219,1.889,4.219,4.219 S72.017,68.281,69.688,68.281z M69.688,54.219H41.562c-2.33,0-4.219-1.889-4.219-4.219s1.889-4.219,4.219-4.219h28.125 c2.33,0,4.219,1.889,4.219,4.219S72.017,54.219,69.688,54.219z M69.688,40.156H41.562c-2.33,0-4.219-1.889-4.219-4.219 s1.889-4.219,4.219-4.219h28.125c2.33,0,4.219,1.889,4.219,4.219S72.017,40.156,69.688,40.156z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
+2
-2
@@ -1,5 +1,5 @@
|
||||
const HOST = "http://localhost:9820";
|
||||
//const HOST = "https://bufferapi.s00.adblockradio.com"
|
||||
//const HOST = "http://localhost:9820";
|
||||
const HOST = "https://bufferapi.s00.adblockradio.com"
|
||||
|
||||
exports.load = function(path, callback) {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
"use strict";
|
||||
|
||||
var log = require("loglevel");
|
||||
var fs = require("fs");
|
||||
var { getRadioMetadata } = require("../adblockradio-dl/dl.js");
|
||||
var { getAvailable } = require("webradio-metadata");
|
||||
|
||||
|
||||
// list of listened radios:
|
||||
var config = new Object();
|
||||
@@ -12,4 +17,105 @@ try {
|
||||
return log.error("cannot load config. err=" + e);
|
||||
}
|
||||
|
||||
module.exports = config;
|
||||
exports.config = config;
|
||||
|
||||
var isRadioInConfig = function(country, name) {
|
||||
var isAlreadyThere = false;
|
||||
for (var i=0; i<config.radios.length; i++) {
|
||||
if (config.radios[i].country == country && config.radios[i].name == name) {
|
||||
isAlreadyThere = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return isAlreadyThere;
|
||||
}
|
||||
|
||||
var insertRadio = function(country, name, callback) {
|
||||
if (isRadioInConfig()) return callback("Radio already in the list");
|
||||
if (config.radios.length >= config.user.maxRadios) return callback("Playlist is already full");
|
||||
|
||||
// now check that the radio is known to our API.
|
||||
getRadioMetadata(country, name, function(err, radio) {
|
||||
if (!radio) {
|
||||
return callback("Radio is not recognized");
|
||||
} else {
|
||||
config.radios.push({
|
||||
"country": country,
|
||||
"name": name,
|
||||
"content": {
|
||||
"ads": true,
|
||||
"speech": true,
|
||||
"music": true
|
||||
},
|
||||
"enable": true
|
||||
});
|
||||
saveRadios();
|
||||
return callback(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var removeRadio = function(country, name, callback) {
|
||||
|
||||
for (var i=0; i<config.radios.length; i++) {
|
||||
if (config.radios[i].country == country && config.radios[i].name == name) {
|
||||
config.radios.splice(i, 1);
|
||||
saveRadios();
|
||||
return callback(null);
|
||||
}
|
||||
}
|
||||
return callback("Radio not in the list");
|
||||
}
|
||||
|
||||
exports.insertRadio = insertRadio;
|
||||
exports.removeRadio = removeRadio;
|
||||
|
||||
var getRadios = function() {
|
||||
var radios = [];
|
||||
for (var i=0; i<config.radios.length; i++) { // control on what data is exposed via the api
|
||||
var radio = config.radios[i];
|
||||
if (!radio.enable) continue;
|
||||
radios.push({
|
||||
country: radio.country,
|
||||
name: radio.name,
|
||||
enable: radio.enable,
|
||||
content: radio.content,
|
||||
url: radio.enabled,
|
||||
favicon: radio.favicon
|
||||
});
|
||||
}
|
||||
return radios;
|
||||
}
|
||||
|
||||
exports.getRadios = getRadios;
|
||||
|
||||
var saveRadios = function() {
|
||||
var exportedRadios = getRadios();
|
||||
fs.writeFile("config/radios.json", JSON.stringify(exportedRadios), function(err) {
|
||||
if (err) {
|
||||
log.error("saveRadios: could not save radio config. err=" + err);
|
||||
} else {
|
||||
log.debug("saveRadios: config saved");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var getAvailableInactive = function() {
|
||||
var available = getAvailable();
|
||||
// remove radios that are currently in playlist
|
||||
for (let i=available.length-1; i>=0; i--) {
|
||||
let itemInPlaylist = false;
|
||||
for (let j=0; j<config.radios.length; j++) {
|
||||
if (available[i].country === config.radios[j].country && available[i].name === config.radios[j].name) {
|
||||
itemInPlaylist = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (itemInPlaylist) {
|
||||
available.splice(i, 1);
|
||||
}
|
||||
}
|
||||
return available;
|
||||
}
|
||||
|
||||
exports.getAvailableInactive = getAvailableInactive;
|
||||
|
||||
-234
@@ -1,234 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
var config = require("./config.js");
|
||||
var log = require("loglevel");
|
||||
|
||||
var getDateFromPath = function(path) {
|
||||
var spl = path.split("/");
|
||||
return spl[spl.length-1];
|
||||
}
|
||||
|
||||
var getRadio = function(country, name) {
|
||||
if (name) { // both parameters used
|
||||
for (var j=0; j<config.radios.length; j++) {
|
||||
if (config.radios[j].country == country && config.radios[j].name == name) {
|
||||
return config.radios[j];
|
||||
}
|
||||
}
|
||||
} else { // only first parameter used
|
||||
for (var j=0; j<config.radios.length; j++) {
|
||||
if (config.radios[j].country + "_" + config.radios[j].name == country) {
|
||||
return config.radios[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
exports.getRadio = getRadio;
|
||||
|
||||
var isLive = function(radio, file) {
|
||||
return getRadio(radio).liveStatus.currentPrefix == file;
|
||||
}
|
||||
|
||||
/*var listenRequestDate = null;
|
||||
exports.listenHandler = function(response, radio, after, state, callback) {
|
||||
var before = new Date(+new Date(after)+6*SEG_DURATION*1000).toISOString();
|
||||
log.debug("listenHandler: after=" + after + " before=" + before + " bufferSeg=" + state.nSegmentsInitialBuffer);
|
||||
findDataFiles({ radios: [ radio ], after: after, before: before, path: __dirname }, function(classes) {
|
||||
var list = {};
|
||||
for (var classItem in classes) {
|
||||
Object.assign(list, classes[classItem]);
|
||||
}
|
||||
var files = Object.keys(list);
|
||||
files.sort();
|
||||
|
||||
if (files.length == 0) return callback();
|
||||
|
||||
var timeoutMonitor = null;
|
||||
|
||||
async.forEachOfSeries(files, function(file, index, filesCallback) {
|
||||
log.debug("listen: read=" + file + state.ext);
|
||||
|
||||
var rs;
|
||||
var livePlay = isLive(radio, file);
|
||||
if (livePlay) {
|
||||
rs = getRadio(radio).liveStatus.liveReadStream;
|
||||
} else {
|
||||
rs = fs.createReadStream(file + state.ext);
|
||||
}
|
||||
|
||||
var dataRead = 0;
|
||||
rs.on("data", function(data) {
|
||||
response.write(data);
|
||||
dataRead += data.length;
|
||||
});
|
||||
rs.on("error", function(err) {
|
||||
log.error("listen: readFile err=" + err + " livePlay=" + livePlay);
|
||||
});
|
||||
rs.on("end", function() {
|
||||
state.lastSentDate = new Date();
|
||||
var willWaitDrain = !response.write("");
|
||||
log.debug("listen: sent file " + (index+1) + "/" + files.length + " bytes=" + dataRead + " waitDrain=" + willWaitDrain + " live=" + livePlay);
|
||||
if (livePlay || !willWaitDrain) { //willWaitDrain) { // detect congestion of stream
|
||||
bufManager();
|
||||
} else {
|
||||
log.debug("listenHandler: will wait for drain event");
|
||||
response.once("drain", function() {
|
||||
clearTimeout(timeoutMonitor);
|
||||
bufManager();
|
||||
});
|
||||
timeoutMonitor = setTimeout(function() {
|
||||
response.removeListener("drain", bufManager);
|
||||
response.destroy();
|
||||
filesCallback({ message: "drain event not emitted, connection timeout" });
|
||||
}, SEG_DURATION*1500);
|
||||
}
|
||||
});
|
||||
var bufManager = function() {
|
||||
before = new Date(+new Date(getDateFromPath(file))+1).toISOString();
|
||||
if (state.requestDate !== listenRequestDate) {
|
||||
filesCallback({ message: "request canceled because another one has been initiated" });
|
||||
} else if (livePlay) {
|
||||
filesCallback();
|
||||
} else if (state.nSegmentsInitialBuffer > 0) {
|
||||
state.nSegmentsInitialBuffer--;
|
||||
filesCallback();
|
||||
} else {
|
||||
var delay = +SEG_DURATION*1000 - (new Date()-state.lastSentDate);
|
||||
log.debug("listenHandler: send more data in " + delay + " ms");
|
||||
setTimeout(filesCallback, delay);
|
||||
}
|
||||
}
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
log.error("listen: err=" + err.message);
|
||||
callback();
|
||||
} else {
|
||||
listenHandler(response, radio, before, state, callback);
|
||||
}
|
||||
});
|
||||
});
|
||||
}*/
|
||||
|
||||
//const STREAM_INITIAL_BUFFER = 8; // send N seconds at connection.
|
||||
//const STREAM_GRANULARITY = 1; // send N seconds of data every N seconds.
|
||||
var listenRequestDate = null;
|
||||
exports.listenHandler = function(response, radio, delay, state, callback) {
|
||||
var initialBuffer = getRadio(radio).liveStatus.audioCache.readLast(+delay+config.user.streamInitialBuffer,config.user.streamInitialBuffer);
|
||||
log.info("listen: send initial buffer of " + initialBuffer.length + " bytes");
|
||||
response.write(initialBuffer);
|
||||
|
||||
var listenTimer = setInterval(function() {
|
||||
if (state.newRequest) {
|
||||
listenRequestDate = state.requestDate;
|
||||
state.newRequest = false;
|
||||
} else if (listenRequestDate !== state.requestDate) {
|
||||
log.warn("request canceled because another one has been initiated");
|
||||
clearInterval(listenTimer);
|
||||
return callback();
|
||||
}
|
||||
var willWaitDrain = !response.write("");
|
||||
if (!willWaitDrain) { // detect congestion of stream
|
||||
sendMore();
|
||||
} else {
|
||||
log.debug("listenHandler: will wait for drain event");
|
||||
|
||||
var drainCallback = function() {
|
||||
clearTimeout(timeoutMonitor);
|
||||
sendMore();
|
||||
}
|
||||
response.once("drain", drainCallback);
|
||||
var timeoutMonitor = setTimeout(function() {
|
||||
response.removeListener("drain", drainCallback);
|
||||
clearInterval(listenTimer);
|
||||
log.error("listenHandler: drain event not emitted, connection timeout");
|
||||
callback();
|
||||
}, config.user.streamGranularity*1500);
|
||||
}
|
||||
}, 1000*config.user.streamGranularity);
|
||||
var sendMore = function() {
|
||||
response.write(getRadio(radio).liveStatus.audioCache.readAmountAfterCursor(config.user.streamGranularity));
|
||||
}
|
||||
}
|
||||
|
||||
/*exports.metadataHandler = function(response, radio, after) {
|
||||
findDataFiles({ radios: [ radio ], after: after, path: __dirname }, function(classes) {
|
||||
//log.debug(classes); // very verbose
|
||||
var list = {};
|
||||
for (var classItem in classes) {
|
||||
Object.assign(list, classes[classItem]);
|
||||
}
|
||||
var files = Object.keys(list);
|
||||
files.sort();
|
||||
//response.json(list);
|
||||
var isSameSegment = function(curSegment, newData) {
|
||||
if (curSegment.class !== newData.class) {
|
||||
//log.debug("isSameSegment: curEnd=" + curSegment.end + " diff class cur=" + curSegment.class + " vs new=" + newData.class);
|
||||
return false;
|
||||
} else if (!curSegment.title || !newData.title) {
|
||||
//log.debug("isSameSegment: curEnd=" + curSegment.end + " no title");
|
||||
return true;
|
||||
} else if (curSegment.title.artist && newData.title.artist && curSegment.title.artist !== newData.title.artist) {
|
||||
//log.debug("isSameSegment: curEnd=" + curSegment.end + " diff artist cur=" + curSegment.title.artist + " vs new=" + newData.title.artist);
|
||||
return false;
|
||||
} else if (curSegment.title.title && newData.title.title && curSegment.title.title !== newData.title.title) {
|
||||
//log.debug("isSameSegment: curEnd=" + curSegment.end + " diff title cur=" + curSegment.title.title + " vs new=" + newData.title.title);
|
||||
return false;
|
||||
} else if (curSegment.title.cover && newData.title.cover && curSegment.title.cover !== newData.title.cover) {
|
||||
//log.debug("isSameSegment: curEnd=" + curSegment.end + " diff cover");
|
||||
return false;
|
||||
} else {
|
||||
//log.debug("isSameSegment: same segment");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
var newSegment = function(index) {
|
||||
return { class: list[files[index]].class, start: getDateFromPath(files[index]), end: getDateFromPath(files[index]), title: null }
|
||||
}
|
||||
|
||||
var result = [];
|
||||
if (files.length == 0) return response.json(result);
|
||||
var curSegment = newSegment(0);
|
||||
async.forEachOfSeries(files, function(file, index, filesCallback) {
|
||||
//log.debug("metadata: read=" + file + ".json");
|
||||
|
||||
curSegment.end = getDateFromPath(file);
|
||||
|
||||
if (isLive(radio, file)) {
|
||||
//log.info("listenHandler: currentAudioFile");
|
||||
var pData = { class: list[file].class, title: getRadio(radio).liveStatus.metadata };
|
||||
if (!isSameSegment(curSegment, pData)) {
|
||||
result.push(curSegment);
|
||||
curSegment = newSegment(index);
|
||||
}
|
||||
curSegment.title = pData.title;
|
||||
filesCallback();
|
||||
} else {
|
||||
fs.readFile(file + ".json", function(err, data) {
|
||||
if (err) log.error("metadata: readFile err=" + err);
|
||||
try {
|
||||
var pData = JSON.parse(data);
|
||||
} catch(e) {
|
||||
log.error("metadata: json parse error file=" + file + ".json");
|
||||
return filesCallback();
|
||||
}
|
||||
pData.class = list[file].class;
|
||||
if (!isSameSegment(curSegment, pData)) {
|
||||
result.push(curSegment);
|
||||
curSegment = newSegment(index);
|
||||
}
|
||||
curSegment.title = pData.title;
|
||||
filesCallback();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}, function(err) {
|
||||
if (err) log.error("metadata: err=" + err.message);
|
||||
result.push(curSegment);
|
||||
response.set({ 'Access-Control-Allow-Origin': '*' });
|
||||
response.json(result);
|
||||
});
|
||||
});
|
||||
}*/
|
||||
@@ -11,23 +11,49 @@ var findDataFiles = require("./findDataFiles.js");
|
||||
var async = require("async");
|
||||
var DlFactory = require("./DlFactory.js");
|
||||
|
||||
const DL = true;
|
||||
const FETCH_METADATA = true;
|
||||
const SEG_DURATION = 10; // in seconds
|
||||
const LISTEN_BUFFER = 30; // in seconds
|
||||
|
||||
var config = require("./config.js");
|
||||
var { config, getRadios, insertRadio, removeRadio, getAvailableInactive } = require("./config.js");
|
||||
|
||||
if (DL) {
|
||||
var dl = [];
|
||||
var dl = [];
|
||||
var updateDlList = function() {
|
||||
// add missing sockets
|
||||
for (var i=0; i<config.radios.length; i++) {
|
||||
config.radios[i].liveStatus = {};
|
||||
if (config.radios[i].enable) {
|
||||
dl.push(DlFactory(config.radios[i], { fetchMetadata: FETCH_METADATA, segDuration: SEG_DURATION }));
|
||||
var alreadyThere = false;
|
||||
for (var j=0; j<dl.length; j++) {
|
||||
if (dl[j].country == config.radios[i].country && dl[j].name == config.radios[i].name) {
|
||||
alreadyThere = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!alreadyThere && config.radios[i].enable) {
|
||||
config.radios[i].liveStatus = {};
|
||||
log.info("updateDlList: start " + config.radios[i].country + "_" + config.radios[i].name);
|
||||
dl.push(DlFactory(config.radios[i], { fetchMetadata: FETCH_METADATA, segDuration: SEG_DURATION, cacheLen: config.user.cacheLen }));
|
||||
}
|
||||
}
|
||||
|
||||
// remove obsolete ones.
|
||||
for (var j=dl.length-1; j>=0; j--) {
|
||||
var shouldBeThere = false;
|
||||
for (var i=0; i<config.radios.length; i++) {
|
||||
if (dl[j].country == config.radios[i].country && dl[j].name == config.radios[i].name) {
|
||||
shouldBeThere = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!shouldBeThere) {
|
||||
log.info("updateDlList: stop " + dl[j].country + "_" + dl[j].name);
|
||||
dl[j].stopDl();
|
||||
dl.splice(j, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateDlList();
|
||||
|
||||
var http = require('http');
|
||||
var express = require('express');
|
||||
var app = express();
|
||||
@@ -36,24 +62,62 @@ server.listen(9820, "localhost");
|
||||
|
||||
app.get('/config', function(request, response) {
|
||||
response.set({ 'Access-Control-Allow-Origin': '*' });
|
||||
var result = { radios: [], user: config.user };
|
||||
for (var i=0; i<config.radios.length; i++) { // control on what data is exposed via the api
|
||||
var radio = config.radios[i];
|
||||
if (!radio.enable) continue;
|
||||
result.radios.push({
|
||||
country: radio.country,
|
||||
name: radio.name,
|
||||
enable: radio.enable,
|
||||
content: radio.content,
|
||||
url: radio.enabled,
|
||||
favicon: radio.favicon
|
||||
});
|
||||
}
|
||||
response.json(result);
|
||||
//log.debug("config[0]=" + JSON.stringify(config.radios[0]));
|
||||
response.json({ radios: getRadios(), user: config.user });
|
||||
});
|
||||
|
||||
var { listenHandler, metadataHandler, getRadio } = require("./handlers.js");
|
||||
app.get('/config/radios/insert/:country/:name', function(request, response) {
|
||||
response.set({ 'Access-Control-Allow-Origin': '*' });
|
||||
insertRadio(request.params.country, request.params.name, function(err) {
|
||||
if (err) {
|
||||
log.error("/config/insert/" + request.params.country + "/" + request.params.name + ": err=" + err);
|
||||
response.writeHead(400);
|
||||
response.end();
|
||||
} else {
|
||||
response.writeHead(200);
|
||||
response.end();
|
||||
updateDlList();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/config/radios/remove/:country/:name', function(request, response) {
|
||||
response.set({ 'Access-Control-Allow-Origin': '*' });
|
||||
removeRadio(request.params.country, request.params.name, function(err) {
|
||||
if (err) {
|
||||
log.error("/config/remove/" + request.params.country + "/" + request.params.name + ": err=" + err);
|
||||
response.writeHead(400);
|
||||
response.end();
|
||||
} else {
|
||||
response.writeHead(200);
|
||||
response.end();
|
||||
updateDlList();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/config/radios/available', function(request, response) {
|
||||
response.set({ 'Access-Control-Allow-Origin': '*' });
|
||||
response.json(getAvailableInactive());
|
||||
});
|
||||
|
||||
var getRadio = function(country, name) {
|
||||
if (name) { // both parameters used
|
||||
for (var j=0; j<config.radios.length; j++) {
|
||||
if (config.radios[j].country == country && config.radios[j].name == name) {
|
||||
return config.radios[j];
|
||||
}
|
||||
}
|
||||
} else { // only first parameter used
|
||||
for (var j=0; j<config.radios.length; j++) {
|
||||
if (config.radios[j].country + "_" + config.radios[j].name == country) {
|
||||
return config.radios[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
var listenRequestDate = null;
|
||||
|
||||
app.get('/:action/:radio/:delay', function(request, response) {
|
||||
var action = request.params.action;
|
||||
@@ -69,34 +133,86 @@ app.get('/:action/:radio/:delay', function(request, response) {
|
||||
switch(action) {
|
||||
case "listen":
|
||||
var ext = ".mp3"; // TODO check for other extensions
|
||||
/*var state = {
|
||||
nSegmentsInitialBuffer: Math.floor(LISTEN_BUFFER/SEG_DURATION),
|
||||
lastSentDate: new Date(),
|
||||
ext: ext,
|
||||
requestDate: new Date()
|
||||
}
|
||||
listenRequestDate = state.requestDate;*/
|
||||
|
||||
switch(ext) {
|
||||
case ".aac": response.set('Content-Type', 'audio/aacp'); break;
|
||||
case ".mp3": response.set('Content-Type', 'audio/mpeg'); break;
|
||||
}
|
||||
/*listenHandler(response, radio, after, state, function() {
|
||||
|
||||
var state = { newRequest: true, requestDate: new Date() };
|
||||
var initialBuffer = getRadio(radio).liveStatus.audioCache.readLast(+delay+config.user.streamInitialBuffer,config.user.streamInitialBuffer);
|
||||
|
||||
if (!initialBuffer) {
|
||||
log.error("/listen/" + radio + "/" + delay + ": initialBuffer not available");
|
||||
response.writeHead(400);
|
||||
return response.end();
|
||||
}
|
||||
|
||||
log.info("listen: send initial buffer of " + initialBuffer.length + " bytes");
|
||||
response.write(initialBuffer);
|
||||
|
||||
var finish = function() {
|
||||
clearInterval(listenTimer);
|
||||
response.end();
|
||||
});*/
|
||||
listenHandler(response, radio, delay, {
|
||||
newRequest: true,
|
||||
requestDate: new Date()
|
||||
}, function() {
|
||||
response.end();
|
||||
});
|
||||
}
|
||||
|
||||
var listenTimer = setInterval(function() {
|
||||
if (state.newRequest) {
|
||||
listenRequestDate = state.requestDate;
|
||||
state.newRequest = false;
|
||||
} else if (listenRequestDate !== state.requestDate) {
|
||||
log.warn("request canceled because another one has been initiated");
|
||||
return finish();
|
||||
}
|
||||
var willWaitDrain = !response.write("");
|
||||
if (!willWaitDrain) { // detect congestion of stream
|
||||
sendMore();
|
||||
} else {
|
||||
log.debug("listenHandler: will wait for drain event");
|
||||
|
||||
var drainCallback = function() {
|
||||
clearTimeout(timeoutMonitor);
|
||||
sendMore();
|
||||
}
|
||||
response.once("drain", drainCallback);
|
||||
var timeoutMonitor = setTimeout(function() {
|
||||
response.removeListener("drain", drainCallback);
|
||||
log.error("listenHandler: drain event not emitted, connection timeout");
|
||||
return finish();
|
||||
}, config.user.streamGranularity*1500);
|
||||
}
|
||||
}, 1000*config.user.streamGranularity);
|
||||
var sendMore = function() {
|
||||
var radioObj = getRadio(radio);
|
||||
if (!radioObj) {
|
||||
log.error("/listen/" + radio + "/" + delay + ": radio not available");
|
||||
return finish();
|
||||
}
|
||||
var audioCache = radioObj.liveStatus.audioCache;
|
||||
if (!audioCache) {
|
||||
log.error("/listen/" + radio + "/" + delay + ": audioCache not available");
|
||||
return finish();
|
||||
}
|
||||
response.write(audioCache.readAmountAfterCursor(config.user.streamGranularity));
|
||||
}
|
||||
break;
|
||||
|
||||
case "metadata":
|
||||
//metadataHandler(response, radio);
|
||||
response.set({ 'Access-Control-Allow-Origin': '*' });
|
||||
var result = getRadio(radio).liveStatus.metaCache.read();
|
||||
response.json(result);
|
||||
var radio = getRadio(radio);
|
||||
if (!radio) {
|
||||
log.error("/metadata/" + radio + "/" + delay + ": radio not available");
|
||||
response.writeHead(400);
|
||||
return response.end();
|
||||
}
|
||||
var metaCache = radio.liveStatus.metaCache;
|
||||
if (!metaCache) {
|
||||
log.error("/metadata/" + radio + "/" + delay + ": metadata not available");
|
||||
response.writeHead(400);
|
||||
return response.end();
|
||||
} else {
|
||||
response.json(metaCache.read());
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
Generated
+3
-3
@@ -1742,9 +1742,9 @@
|
||||
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
|
||||
},
|
||||
"webradio-metadata": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/webradio-metadata/-/webradio-metadata-0.1.4.tgz",
|
||||
"integrity": "sha512-46GrZPxd9rQFmHEiRFjzVfV09XBrOlWVGrc+b2NVNLtXe5fMMgi5C/ftNGl9JEGy/UFLMny73WpmkWuVYtKt5A==",
|
||||
"version": "0.1.6",
|
||||
"resolved": "https://registry.npmjs.org/webradio-metadata/-/webradio-metadata-0.1.6.tgz",
|
||||
"integrity": "sha512-345c6QzlnstTB9CZ4s535P2DwqSD0R1Uw/E9yHd4hwCpw3jNITelnxQ7biK78zXIKTDy4Po1dkKUwqUVHyiNOA==",
|
||||
"requires": {
|
||||
"express": "4.16.2",
|
||||
"helmet": "3.9.0",
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@
|
||||
"adblockradio-sdk": "git://github.com/dest4/adblockradio-sdk.git#master",
|
||||
"async": "^2.6.0",
|
||||
"loglevel": "^1.6.1",
|
||||
"webradio-metadata": "^0.1.4"
|
||||
"webradio-metadata": "^0.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^4.15.0"
|
||||
|
||||
Reference in New Issue
Block a user