apply prettier

This commit is contained in:
boyska 2023-09-14 00:07:07 +01:00
parent dd96ce7b2a
commit 4e19b6304e
18 changed files with 902 additions and 852 deletions

1
.prettierignore Normal file
View file

@ -0,0 +1 @@
libs/

View file

@ -13,6 +13,5 @@ npm run test
## Supported spec
- [radiomanifest](https://radiomanifest.degenerazione.xyz/)
- [stream-meta v2](https://www.stream-meta.info/version_2_files.html): name of the radio is extracted from `/streaminfo.json`
- [radiomanifest](https://radiomanifest.degenerazione.xyz/)
- [stream-meta v2](https://www.stream-meta.info/version_2_files.html): name of the radio is extracted from `/streaminfo.json`

View file

@ -1,18 +1,18 @@
import ICAL from 'ical.js'
import shows from './shows'
import ICAL from "ical.js";
import shows from "./shows";
function max(a, b) {
if (a<b) return b
return a
if (a < b) return b;
return a;
}
/**
* Represent a schedule (ie: a .ics file)
*/
class RadioSchedule {
constructor (calendar, radio) {
this.calendar = calendar // ICAL.Calendar
this.radio = radio // radiomanifest.Radio
constructor(calendar, radio) {
this.calendar = calendar; // ICAL.Calendar
this.radio = radio; // radiomanifest.Radio
}
/**
@ -21,136 +21,138 @@ class RadioSchedule {
* @returns {Array<external:ICAL~Component>}
* */
getEvents() {
return this.calendar.getAllSubcomponents('vevent');
return this.calendar.getAllSubcomponents("vevent");
}
/**
* @returns {RadioShow} tries to get a matching show, or create a new one
*/
getShowByEvent(ev) {
if (ev === null) return null
if (ev === null) return null;
if (this.radio !== undefined) {
const showid = RadioSchedule.veventGetShowID(ev)
var show = this.radio.getShowByName(showid)
const showid = RadioSchedule.veventGetShowID(ev);
var show = this.radio.getShowByName(showid);
if (show === null || show === undefined) {
return new shows.RadioShow(RadioSchedule.veventGetSummary(ev))
return new shows.RadioShow(RadioSchedule.veventGetSummary(ev));
}
return show;
}
return new shows.RadioShow(RadioSchedule.veventGetSummary(ev))
return new shows.RadioShow(RadioSchedule.veventGetSummary(ev));
}
/**
* @returns {external:ICAL~Component} if nothing is going on right now, `null` is returned
*/
getNowEvent(now) {
var ev_now = this.getEvents().filter(function(vevent) {
const ev = new ICAL.Event(vevent)
return isNow(ev, now)
})
ev_now.sort((e1, e2) => { return this.veventGetPriority(e1) - this.veventGetPriority(e2) })
if(ev_now.length === 0)
return null
return ev_now[0]
var ev_now = this.getEvents().filter(function (vevent) {
const ev = new ICAL.Event(vevent);
return isNow(ev, now);
});
ev_now.sort((e1, e2) => {
return this.veventGetPriority(e1) - this.veventGetPriority(e2);
});
if (ev_now.length === 0) return null;
return ev_now[0];
}
/**
* @returns {RadioShow} if nothing is going on right now, `null` is returned
*/
getNowShow(now) {
const ev = this.getNowEvent(now)
return this.getShowByEvent(ev)
const ev = this.getNowEvent(now);
return this.getShowByEvent(ev);
}
/**
* @returns {NextEvent} if there is none, `null` is returned
*/
getNextEvent(now) {
var nowEvent = this.getNowEvent(now)
let future_events = this.getEvents().filter((e) => { return e != nowEvent })
.map((e) => {
const vEvent = new ICAL.Event(e)
return {event: e, time: getNext(vEvent, now)}
var nowEvent = this.getNowEvent(now);
let future_events = this.getEvents()
.filter((e) => {
return e != nowEvent;
})
.filter((x) => { return x.time !== null && x.time !== undefined })
.map((e) => {
const vEvent = new ICAL.Event(e);
return { event: e, time: getNext(vEvent, now) };
})
.filter((x) => {
return x.time !== null && x.time !== undefined;
});
// since ".sort()" is guaranteed to be stable, we can sort by priority, then by date, so that two events
// starting at the same time will be sorted observing priority
future_events.sort((x1, x2) => this.veventGetPriority(x1.event) - this.veventGetPriority(x2.event))
future_events.sort((x, y) => x.time.toUnixTime() - y.time.toUnixTime())
future_events.sort(
(x1, x2) =>
this.veventGetPriority(x1.event) - this.veventGetPriority(x2.event),
);
future_events.sort((x, y) => x.time.toUnixTime() - y.time.toUnixTime());
if (future_events.length === 0) {
return null
return null;
}
return future_events[0]
return future_events[0];
}
/**
* @returns {NextShow} if there is no next show, null will be returned
*/
getNextShow(now) {
const next = this.getNextEvent(now)
if (next === null) return null
const ev = next.event
const show = this.getShowByEvent(ev)
return {show: show, time: next.time}
const next = this.getNextEvent(now);
if (next === null) return null;
const ev = next.event;
const show = this.getShowByEvent(ev);
return { show: show, time: next.time };
}
static veventGetSummary(vevent) {
return vevent.getFirstProperty('summary').getFirstValue()
return vevent.getFirstProperty("summary").getFirstValue();
}
static veventGetShowID(vevent) {
return RadioSchedule.veventGetSummary(vevent) // XXX: X-Show-ID
return RadioSchedule.veventGetSummary(vevent); // XXX: X-Show-ID
}
/**
* @return {integer} a normalized version of priority, easier to compare
*/
veventGetPriority(ev) {
const prop = ev.getFirstProperty('priority')
const prop = ev.getFirstProperty("priority");
let prio;
if (prop === null) {
prio = null
prio = null;
} else {
prio = prop.getFirstValue()
prio = prop.getFirstValue();
}
if (prio === null || prio === 0) {
prio = 100
prio = 100;
}
return prio
return prio;
}
}
function isNow(vEvent, now) {
if (now === undefined) {
now = ICAL.Time.now()
}
if (vEvent.isRecurring()) {
return isNowRecurring(vEvent, now)
}
return (now < vEvent.endDate) && (now > vEvent.startDate);
if (now === undefined) {
now = ICAL.Time.now();
}
if (vEvent.isRecurring()) {
return isNowRecurring(vEvent, now);
}
return now < vEvent.endDate && now > vEvent.startDate;
}
function isNowRecurring(vEvent, now) {
var expand = vEvent.iterator(vEvent.startDate)
var expand = vEvent.iterator(vEvent.startDate);
var next, next_end;
while ((next = expand.next())) {
next_end = next.clone()
next_end.addDuration(vEvent.duration)
next_end = next.clone();
next_end.addDuration(vEvent.duration);
if (next_end > now) {
break;
}
}
return (now < next_end && now > next);
return now < next_end && now > next;
}
/*
* @private
* @param {external:ICAL~Component} vEvent a _recurring_ vEvent
@ -159,16 +161,16 @@ function isNowRecurring(vEvent, now) {
*/
function getNext(vEvent, now) {
if (now === undefined) {
now = ICAL.Time.now()
now = ICAL.Time.now();
}
if (vEvent.isRecurring()) {
return getNextRecurring(vEvent, now)
return getNextRecurring(vEvent, now);
}
if (vEvent.endDate > now) {
const val = max(now, vEvent.startDate)
return val
const val = max(now, vEvent.startDate);
return val;
}
return null
return null;
}
/*
@ -178,37 +180,36 @@ function getNext(vEvent, now) {
* @return {external:ICAL~Time} first future occurrence of this event
*/
function getNextRecurring(vEvent, now) {
var expand = vEvent.iterator(vEvent.startDate)
var expand = vEvent.iterator(vEvent.startDate);
var next, next_end;
while ((next = expand.next())) {
const start = next.clone()
next_end = start.clone()
next_end.addDuration(vEvent.duration)
const start = next.clone();
next_end = start.clone();
next_end.addDuration(vEvent.duration);
if (next_end <= now) {
continue
continue;
}
return max(start, now)
return max(start, now);
}
return null
return null;
}
async function get(manifest) {
if (manifest.scheduleURL) {
let resp = null
let resp = null;
try {
resp = await fetch(manifest.scheduleURL)
resp = await fetch(manifest.scheduleURL);
} catch (e) {
true
true;
}
if (resp !== null) {
try {
const text = await resp.text()
return parse(text)
const text = await resp.text();
return parse(text);
} catch (e) {
console.error('Error while parsing schedule', e)
throw e
console.error("Error while parsing schedule", e);
throw e;
}
}
}
@ -224,15 +225,14 @@ function parse(text) {
var jcalData = ICAL.parse(text);
var vcalendar = new ICAL.Component(jcalData);
return new RadioSchedule(vcalendar)
return new RadioSchedule(vcalendar);
}
export default {
get: get,
parse: parse,
RadioSchedule: RadioSchedule,
}
};
/**
* @typedef {Object} NextShow

View file

@ -2,5 +2,6 @@ Radiomanifest is a specification to make webradios express their offer in a stan
library aiming at making the development of expressive webapps a breeze.
You are strongly invited to start from:
- {@tutorial quickstart} tutorial
- {@link get} this is the first function you'll ever call, in 99% of user scenarios
- {@tutorial quickstart} tutorial
- {@link get} this is the first function you'll ever call, in 99% of user scenarios

View file

@ -1,9 +1,9 @@
Using radiomanifest is pretty simple. In an ideal usecase, you can easily do this:
```javascript
const radiomanifest = require('radiomanifest')
const radio = radiomanifest.get('http://myradio.com/')
console.log(radio.getName())
const radiomanifest = require("radiomanifest");
const radio = radiomanifest.get("http://myradio.com/");
console.log(radio.getName());
```
Now we have `radio`, a {@link Radio} object, which can be seen as the "center" of our data. From here, we can
@ -11,13 +11,13 @@ get more data.
## Streaming
The first thing we could want to do is just to *play* the radio. Let's use the {@link RadioStreaming#pickURLs
The first thing we could want to do is just to _play_ the radio. Let's use the {@link RadioStreaming#pickURLs
RadioStreaming.pickURLs} method then
```javascript
var streaming = radio.getStreaming()
var urls = await streaming.pickURLs()
console.log(urls)
var streaming = radio.getStreaming();
var urls = await streaming.pickURLs();
console.log(urls);
```
and here we go! This is a list of URLs that the radio is indicating to us as their preferred ones. Why not a
@ -30,17 +30,18 @@ Our {@link Radio} keeps track of those, and for each show we can have useful met
for more details.
```javascript
var shows = radio.getShows()
console.log(shows.map(s => s.getName()))
var shows = radio.getShows();
console.log(shows.map((s) => s.getName()));
```
## Schedule
```javascript
const show = radio.getShowAtTime()
const show = radio.getShowAtTime();
if (show !== null) {
console.log(show.getName())
console.log(show.getName());
} else {
console.log("Nothing special going on right now, sorry")
console.log("Nothing special going on right now, sorry");
}
```
@ -51,4 +52,4 @@ now and {@link RadioSchedule#getNextShow what will go on next}
## Conclusions
I hope this tutorial helped you get your feet wet. Hopefully, using radiomanifest you'd be able to create
great webapps that work on *any* webradio (well, any webradio that supports radiomanifest, at least).
great webapps that work on _any_ webradio (well, any webradio that supports radiomanifest, at least).

View file

@ -1,34 +1,30 @@
module.exports = function (config) {
config.set({
frameworks: ['mocha'],
frameworks: ["mocha"],
// plugins: ['karma-webpack', 'karma-mocha', 'karma-chai-as-promised'],
webpack: {
// karma watches the test entry points
// Do NOT specify the entry option
// webpack watches dependencies
// webpack configuration
},
preprocessors: {
'test/**/*.js': ['webpack'],
'radiomanifest.js': ['webpack']
"test/**/*.js": ["webpack"],
"radiomanifest.js": ["webpack"],
},
files: [
'radiomanifest.js',
'test/**/*.js'
],
reporters: ['progress'],
files: ["radiomanifest.js", "test/**/*.js"],
reporters: ["progress"],
port: 9876, // karma web server port
colors: true,
logLevel: config.LOG_INFO,
browsers: ['ChromeHeadless', 'FirefoxHeadless'],
browsers: ["ChromeHeadless", "FirefoxHeadless"],
autoWatch: false,
concurrency: Infinity,
customLaunchers: {
FirefoxHeadless: {
base: 'Firefox',
flags: ['-headless']
}
}
})
}
base: "Firefox",
flags: ["-headless"],
},
},
});
};

View file

@ -1,174 +1,174 @@
import fetch from 'isomorphic-unfetch'
import shows from './shows.js'
import calendar from './calendar.js'
import fetch from "isomorphic-unfetch";
import shows from "./shows.js";
import calendar from "./calendar.js";
function getStreaminfoUrl(siteurl) {
return siteurl + "/streaminfo.json" // XXX: improve this logic
return siteurl + "/streaminfo.json"; // XXX: improve this logic
}
function getManifestUrl(siteurl) {
return siteurl + "/radiomanifest.xml" // XXX: improve this logic
return siteurl + "/radiomanifest.xml"; // XXX: improve this logic
}
function getAttribute(el, attr, default_value) {
if (el.hasAttribute(attr)) return el.getAttribute(attr)
return default_value
if (el.hasAttribute(attr)) return el.getAttribute(attr);
return default_value;
}
/**
* Represents everything we know about a radio. This includes, but is not limited to, radiomanifest.xml.
*/
class Radio {
/**
* @param {Array} [sources] optional
* @param {string} [scheduleURL]
* @param {string} [showsURL]
* @param {string} [feed]
*/
constructor(sources, scheduleURL, showsURL, feed) {
this.streaming = new RadioStreaming(sources)
this.scheduleURL = scheduleURL
this.showsURL = showsURL
this.feed = feed
this.name = ""
this.description = ""
this.logo = null
this.shows = []
this.schedule = null
/**
* @param {Array} [sources] optional
* @param {string} [scheduleURL]
* @param {string} [showsURL]
* @param {string} [feed]
*/
constructor(sources, scheduleURL, showsURL, feed) {
this.streaming = new RadioStreaming(sources);
this.scheduleURL = scheduleURL;
this.showsURL = showsURL;
this.feed = feed;
this.name = "";
this.description = "";
this.logo = null;
this.shows = [];
this.schedule = null;
}
/**
* @returns {RadioStreaming}
*/
getStreaming() {
return this.streaming;
}
setName(name) {
this.name = name;
}
/**
* The radio name, as inferred by stream-meta
*
* @returns {string}
*/
getName() {
return this.name;
}
setDescription(desc) {
this.description = desc;
}
/**
* The description of the radio, as inferred by stream-meta
*
* @returns {string}
*/
getDescription() {
return this.description;
}
setLogo(logo) {
this.logo = logo;
}
/**
* @returns {string} the URL of the logo, or `null` if not found
*/
getLogo() {
return this.logo;
}
/**
*
* @returns {Array<RadioShow>}
*/
getShows() {
return this.shows;
}
/**
* @returns {RadioShow} The lookup is exact and case-sensitive. If no such show can be found, `null`
* is returned.
*/
getShowByName(showName) {
if (this.shows === undefined) return null;
return this.shows.find((s) => s.name === showName);
}
/**
* @returns {RadioSchedule} If no schedule is present, `null` is returned.
*/
getSchedule() {
return this.schedule;
}
/**
* Find if a show is running at the given moment. If there's none, `null` is returned.
* If possible, a complete {@link RadioShow} including full informations (from shows.xml) is returned.
* If, instead, we know from the `schedule` that there must be a show, but have no additional detail, a
* {@link RadioShow} object will be created on the fly.
*
* @param {ICAL.Time} [now] If omitted, the current time is used.
* @returns {RadioShow} If we don't know what's going on at the given moment, `null` is returned.
*/
getShowAtTime(now) {
if (this.schedule === undefined || this.schedule === null) return null;
return this.getSchedule().getNowShow(now);
}
/**
* This static method can create a Radio object from a valid radiomanifest.xml file
*
* @param xml An already parsed xml block
* @returns {Radio}
*/
static fromDOM(xml) {
const doc = xml.cloneNode(true);
let res = doc.evaluate(
"/radio-manifest/streaming/source",
doc,
null,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
null,
);
const sources = [];
for (let i = 0; i < res.snapshotLength; i++) {
const src = res.snapshotItem(i);
if (!src.hasAttribute("priority")) {
src.setAttribute("priority", "0");
} else if (parseInt(src.getAttribute("priority"), 10) < 0) {
continue;
}
sources.push(src);
}
/**
* @returns {RadioStreaming}
*/
getStreaming() {
return this.streaming
res = doc.evaluate("/radio-manifest/schedule", doc);
const scheduleEl = res.iterateNext();
let scheduleURL = null;
if (scheduleEl !== null) {
scheduleURL = scheduleEl.getAttribute("src");
}
setName(name) {
this.name = name
res = xml.evaluate("/radio-manifest/shows", xml);
const showsEl = res.iterateNext();
let showsURL = null;
if (showsEl !== null) {
showsURL = showsEl.getAttribute("src");
}
/**
* The radio name, as inferred by stream-meta
*
* @returns {string}
*/
getName() {
return this.name
res = xml.evaluate("/radio-manifest/feed", xml);
const feedEl = res.iterateNext();
let feed = null;
if (feedEl !== null) {
feed = feedEl.getAttribute("src");
}
setDescription(desc) {
this.description = desc
}
/**
* The description of the radio, as inferred by stream-meta
*
* @returns {string}
*/
getDescription() {
return this.description
}
setLogo(logo) {
this.logo = logo
}
/**
* @returns {string} the URL of the logo, or `null` if not found
*/
getLogo() {
return this.logo
}
/**
*
* @returns {Array<RadioShow>}
*/
getShows() {
return this.shows
}
/**
* @returns {RadioShow} The lookup is exact and case-sensitive. If no such show can be found, `null`
* is returned.
*/
getShowByName(showName) {
if (this.shows === undefined) return null
return this.shows.find(s => s.name === showName)
}
/**
* @returns {RadioSchedule} If no schedule is present, `null` is returned.
*/
getSchedule() {
return this.schedule
}
/**
* Find if a show is running at the given moment. If there's none, `null` is returned.
* If possible, a complete {@link RadioShow} including full informations (from shows.xml) is returned.
* If, instead, we know from the `schedule` that there must be a show, but have no additional detail, a
* {@link RadioShow} object will be created on the fly.
*
* @param {ICAL.Time} [now] If omitted, the current time is used.
* @returns {RadioShow} If we don't know what's going on at the given moment, `null` is returned.
*/
getShowAtTime(now) {
if (this.schedule === undefined || this.schedule === null) return null
return this.getSchedule().getNowShow(now)
}
/**
* This static method can create a Radio object from a valid radiomanifest.xml file
*
* @param xml An already parsed xml block
* @returns {Radio}
*/
static fromDOM(xml) {
const doc = xml.cloneNode(true)
let res = doc.evaluate(
"/radio-manifest/streaming/source",
doc,
null,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
null
)
const sources = []
for (let i = 0; i < res.snapshotLength; i++) {
const src = res.snapshotItem(i)
if (!src.hasAttribute("priority")) {
src.setAttribute("priority", "0")
} else if (parseInt(src.getAttribute("priority"), 10) < 0) {
continue
}
sources.push(src)
}
res = doc.evaluate("/radio-manifest/schedule", doc)
const scheduleEl = res.iterateNext()
let scheduleURL = null
if (scheduleEl !== null) {
scheduleURL = scheduleEl.getAttribute("src")
}
res = xml.evaluate("/radio-manifest/shows", xml)
const showsEl = res.iterateNext()
let showsURL = null
if (showsEl !== null) {
showsURL = showsEl.getAttribute("src")
}
res = xml.evaluate("/radio-manifest/feed", xml)
const feedEl = res.iterateNext()
let feed = null
if (feedEl !== null) {
feed = feedEl.getAttribute("src")
}
const manifest = new Radio(sources, scheduleURL, showsURL, feed)
return manifest
}
const manifest = new Radio(sources, scheduleURL, showsURL, feed);
return manifest;
}
}
/**
@ -176,71 +176,75 @@ class Radio {
* This has probably been derived from the <streaming> block in radiomanifest.xml
*/
class RadioStreaming {
constructor(sources) {
this.sources = sources.sort((a, b) => this.getPriority(a) < this.getPriority(a))
}
constructor(sources) {
this.sources = sources.sort(
(a, b) => this.getPriority(a) < this.getPriority(a),
);
}
/**
* Get the list of possible options that are provided to the user
* @returns {Array<string>}
*/
getOptions() {
return this.sources.map(function (x) {
return x.getAttribute("name")
})
}
/**
* Get the list of possible options that are provided to the user
* @returns {Array<string>}
*/
getOptions() {
return this.sources.map(function (x) {
return x.getAttribute("name");
});
}
/**
* @private
*/
getPriority(element) {
return parseInt(getAttribute(element, "priority", "1"))
}
/**
* @private
*/
getTopPrioritySources() {
var topPriority = this.getPriority(this.sources[0])
return this.sources.filter(src => parseInt(src.getAttribute("priority"), 10) === topPriority)
}
/**
* @return {string} url of the source. Note that this is much probably a playlist, in M3U format
*/
getSource(name) {
if (name === undefined) {
return this.getTopPrioritySources()[0]
}
const s = this.sources.find(function (x) {
return x.getAttribute("name") === name
})
if (s === undefined) return s
return s.getAttribute("src")
/**
* @private
*/
getPriority(element) {
return parseInt(getAttribute(element, "priority", "1"));
}
/**
* @private
*/
getTopPrioritySources() {
var topPriority = this.getPriority(this.sources[0]);
return this.sources.filter(
(src) => parseInt(src.getAttribute("priority"), 10) === topPriority,
);
}
/**
* @return {string} url of the source. Note that this is much probably a playlist, in M3U format
*/
getSource(name) {
if (name === undefined) {
return this.getTopPrioritySources()[0];
}
const s = this.sources.find(function (x) {
return x.getAttribute("name") === name;
});
if (s === undefined) return s;
return s.getAttribute("src");
}
/**
* This is your go-to function whenever you need a list of URLs to play.
* They will be picked honoring priorities, and expanding the playlist source
* @return {Array<string>}
*/
async pickURLs() {
var allSources = this.getTopPrioritySources()
var allAudios = []
for (let src of allSources) {
let url = src.getAttribute("src")
let resp = await fetch(url)
allAudios.unshift(...parseM3U(await resp.text()))
}
return allAudios
/**
* This is your go-to function whenever you need a list of URLs to play.
* They will be picked honoring priorities, and expanding the playlist source
* @return {Array<string>}
*/
async pickURLs() {
var allSources = this.getTopPrioritySources();
var allAudios = [];
for (let src of allSources) {
let url = src.getAttribute("src");
let resp = await fetch(url);
allAudios.unshift(...parseM3U(await resp.text()));
}
return allAudios;
}
/**
* Just like {@link RadioStreaming#pickURLs}, but get a single URL
* @return {string}
*/
async pickURL() {
var allAudios = await this.pickURLs()
return allAudios[0]
}
/**
* Just like {@link RadioStreaming#pickURLs}, but get a single URL
* @return {string}
*/
async pickURL() {
var allAudios = await this.pickURLs();
return allAudios[0];
}
}
/**
@ -251,87 +255,87 @@ class RadioStreaming {
* @return {Radio}
*/
async function get(siteurl, options) {
let resp = await fetch(getManifestUrl(siteurl))
let text = await resp.text()
let resp = await fetch(getManifestUrl(siteurl));
let text = await resp.text();
const parser = new DOMParser()
const dom = parser.parseFromString(text, "text/xml")
const manifest = Radio.fromDOM(dom)
const parser = new DOMParser();
const dom = parser.parseFromString(text, "text/xml");
const manifest = Radio.fromDOM(dom);
try {
manifest.shows = await shows.get(manifest);
} catch (e) {
console.error("Error while fetching shows file", e);
}
try {
manifest.schedule = await calendar.get(manifest);
if (manifest.schedule !== undefined) manifest.schedule.radio = manifest;
} catch (e) {
console.error("Error while fetching shows file", e);
}
resp = null;
try {
resp = await fetch(getStreaminfoUrl(siteurl));
} catch (e) {
true;
}
if (resp !== null) {
try {
manifest.shows = await shows.get(manifest)
text = await resp.text();
const data = JSON.parse(text);
const name = data["icy-name"];
if (name !== undefined) {
manifest.setName(name);
}
const desc = data["icy-description"];
if (desc !== undefined) {
manifest.setDescription(desc);
}
const logo = data["icy-logo"];
if (desc !== undefined) {
manifest.setLogo(logo);
}
} catch (e) {
console.error("Error while fetching shows file", e)
if (e instanceof SyntaxError) {
true;
} else {
console.error("Error", e);
throw e;
}
}
}
try {
manifest.schedule = await calendar.get(manifest)
if (manifest.schedule !== undefined) manifest.schedule.radio = manifest
} catch (e) {
console.error("Error while fetching shows file", e)
}
resp = null
try {
resp = await fetch(getStreaminfoUrl(siteurl))
} catch (e) {
true
}
if (resp !== null) {
try {
text = await resp.text()
const data = JSON.parse(text)
const name = data["icy-name"]
if (name !== undefined) {
manifest.setName(name)
}
const desc = data["icy-description"]
if (desc !== undefined) {
manifest.setDescription(desc)
}
const logo = data["icy-logo"]
if (desc !== undefined) {
manifest.setLogo(logo)
}
} catch (e) {
if (e instanceof SyntaxError) {
true
} else {
console.error("Error", e)
throw e
}
}
}
return manifest
return manifest;
}
function parseM3U(body) {
return body.split("\n").filter(line => {
if (line.startsWith("#")) {
return false
} else {
try {
new URL(line)
return true
} catch {
return false
}
}
})
return body.split("\n").filter((line) => {
if (line.startsWith("#")) {
return false;
} else {
try {
new URL(line);
return true;
} catch {
return false;
}
}
});
}
export default {
get: get,
objs: {
Radio: Radio,
RadioStreaming: RadioStreaming
},
parsers: {
M3U: parseM3U,
radioManifest: Radio.fromDOM,
shows: shows.parse
}
}
get: get,
objs: {
Radio: Radio,
RadioStreaming: RadioStreaming,
},
parsers: {
M3U: parseM3U,
radioManifest: Radio.fromDOM,
shows: shows.parse,
},
};

132
shows.js
View file

@ -3,30 +3,28 @@
* schedule.
*/
class RadioShow {
constructor(name, description, website, feed, schedule, radio_calendar) {
this.name = name
this.description = description
this.website = website
this.feed = feed
this.schedule = schedule
this.radio_calendar = radio_calendar
this.name = name;
this.description = description;
this.website = website;
this.feed = feed;
this.schedule = schedule;
this.radio_calendar = radio_calendar;
}
getName() {
return this.name
return this.name;
}
getWebsite() {
return this.website
return this.website;
}
getFeed() {
return this.feed
return this.feed;
}
getSchedule() {
return this.schedule
return this.schedule;
}
}
/**
@ -34,51 +32,98 @@ class RadioShow {
* @return {Array<RadioShow>}
*/
function parseRadioShows(xml) {
const doc = xml.cloneNode(true)
const bookmarks = doc.evaluate('//bookmark', doc, showsNamespaceResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)
const shows = []
const doc = xml.cloneNode(true);
const bookmarks = doc.evaluate(
"//bookmark",
doc,
showsNamespaceResolver,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
null,
);
const shows = [];
for (let i = 0; i < bookmarks.snapshotLength; i++) {
const bm = bookmarks.snapshotItem(i)
const bm = bookmarks.snapshotItem(i);
let name = doc.evaluate('./info/metadata/show:name', bm, showsNamespaceResolver, XPathResult.STRING_TYPE).stringValue
if (name === '') {
name = doc.evaluate('./title', bm, showsNamespaceResolver, XPathResult.STRING_TYPE).stringValue
let name = doc.evaluate(
"./info/metadata/show:name",
bm,
showsNamespaceResolver,
XPathResult.STRING_TYPE,
).stringValue;
if (name === "") {
name = doc.evaluate(
"./title",
bm,
showsNamespaceResolver,
XPathResult.STRING_TYPE,
).stringValue;
}
let website = doc.evaluate('./info/metadata/show:website', bm, showsNamespaceResolver, XPathResult.STRING_TYPE).stringValue
if (website === '') {
website = bm.getAttribute('href')
let website = doc.evaluate(
"./info/metadata/show:website",
bm,
showsNamespaceResolver,
XPathResult.STRING_TYPE,
).stringValue;
if (website === "") {
website = bm.getAttribute("href");
}
const feed = doc.evaluate('./info/metadata/show:feed', bm, showsNamespaceResolver, XPathResult.STRING_TYPE).stringValue
const schedule = doc.evaluate('./info/metadata/show:schedule', bm, showsNamespaceResolver, XPathResult.STRING_TYPE).stringValue
let description = doc.evaluate('./info/metadata/show:description', bm, showsNamespaceResolver, XPathResult.STRING_TYPE).stringValue
if (description === '') {
description = doc.evaluate('./desc', bm, showsNamespaceResolver, XPathResult.STRING_TYPE).stringValue
const feed = doc.evaluate(
"./info/metadata/show:feed",
bm,
showsNamespaceResolver,
XPathResult.STRING_TYPE,
).stringValue;
const schedule = doc.evaluate(
"./info/metadata/show:schedule",
bm,
showsNamespaceResolver,
XPathResult.STRING_TYPE,
).stringValue;
let description = doc.evaluate(
"./info/metadata/show:description",
bm,
showsNamespaceResolver,
XPathResult.STRING_TYPE,
).stringValue;
if (description === "") {
description = doc.evaluate(
"./desc",
bm,
showsNamespaceResolver,
XPathResult.STRING_TYPE,
).stringValue;
}
const show = new RadioShow(name, description || null, website || null, feed || null, schedule || null)
shows.push(show)
const show = new RadioShow(
name,
description || null,
website || null,
feed || null,
schedule || null,
);
shows.push(show);
}
return shows
return shows;
}
async function getShows(manifest) {
if (manifest.showsURL) {
let resp = null
let resp = null;
try {
resp = await fetch(manifest.showsURL)
resp = await fetch(manifest.showsURL);
} catch (e) {
true
true;
}
if (resp !== null) {
try {
const text = await resp.text()
const parser = new DOMParser()
const showsDom = parser.parseFromString(text, 'text/xml')
return parseRadioShows(showsDom)
const text = await resp.text();
const parser = new DOMParser();
const showsDom = parser.parseFromString(text, "text/xml");
return parseRadioShows(showsDom);
} catch (e) {
console.error('Error while parsing shows file', e)
throw e
console.error("Error while parsing shows file", e);
throw e;
}
}
}
@ -86,14 +131,13 @@ async function getShows(manifest) {
function showsNamespaceResolver(prefix) {
const prefixes = {
show: 'https://radiomanifest.degenerazione.xyz/shows/',
}
return prefixes[prefix] || null
show: "https://radiomanifest.degenerazione.xyz/shows/",
};
return prefixes[prefix] || null;
}
export default {
get: getShows,
parse: parseRadioShows,
RadioShow: RadioShow,
}
};

View file

@ -1,17 +1,17 @@
const radiomanifest = require('../radiomanifest.js')
const chai = require('chai')
chai.use(require('chai-as-promised'))
const assert = chai.assert
const radiomanifest = require("../radiomanifest.js");
const chai = require("chai");
chai.use(require("chai-as-promised"));
const assert = chai.assert;
const expect = chai.expect
const testName = 'idontexist'
const url = 'https://example.org/radiomanifest/examples/' + testName + '/'
const expect = chai.expect;
const testName = "idontexist";
const url = "https://example.org/radiomanifest/examples/" + testName + "/";
describe('radiomanifest.js supports example ' + testName, () => {
describe('Get empty radiomanifest', () => {
it('should throw', () => {
const p = radiomanifest.get(url)
expect(p).to.be.rejected
})
})
})
describe("radiomanifest.js supports example " + testName, () => {
describe("Get empty radiomanifest", () => {
it("should throw", () => {
const p = radiomanifest.get(url);
expect(p).to.be.rejected;
});
});
});

View file

@ -1,26 +1,29 @@
const radiomanifest = require('../radiomanifest.js')
const chai = require('chai')
chai.use(require('chai-as-promised'))
const assert = chai.assert
const radiomanifest = require("../radiomanifest.js");
const chai = require("chai");
chai.use(require("chai-as-promised"));
const assert = chai.assert;
const expect = chai.expect
const tests = ['empty', 'empty-no-streaminfo', 'empty-invalid-streaminfo']
const expect = chai.expect;
const tests = ["empty", "empty-no-streaminfo", "empty-invalid-streaminfo"];
for (const exampleName of tests) {
let url = 'https://radiomanifest.degenerazione.xyz/v0.2/examples/' + exampleName + '/'
let url =
"https://radiomanifest.degenerazione.xyz/v0.2/examples/" +
exampleName +
"/";
describe('examples/' + exampleName, () => {
describe('Get radiomanifest', () => {
it('should return a Promise', () => {
const p = radiomanifest.get(url)
expect(p instanceof Promise).to.be.eql(true)
})
})
describe("examples/" + exampleName, () => {
describe("Get radiomanifest", () => {
it("should return a Promise", () => {
const p = radiomanifest.get(url);
expect(p instanceof Promise).to.be.eql(true);
});
});
describe('streaming', () => {
it('shoud return no streaming option', async () => {
const p = await radiomanifest.get(url)
expect(p.getStreaming().getOptions().length).to.be.equal(0)
})
})
})
describe("streaming", () => {
it("shoud return no streaming option", async () => {
const p = await radiomanifest.get(url);
expect(p.getStreaming().getOptions().length).to.be.equal(0);
});
});
});
}

View file

@ -1,107 +1,104 @@
const ICAL = require('ical.js')
const ICAL = require("ical.js");
const radiomanifest = require('../radiomanifest.js')
const chai = require('chai')
chai.use(require('chai-as-promised'))
const assert = chai.assert
const radiomanifest = require("../radiomanifest.js");
const chai = require("chai");
chai.use(require("chai-as-promised"));
const assert = chai.assert;
const exampleName = 'full-ondarossa'
const expect = chai.expect
const url = 'https://radiomanifest.degenerazione.xyz/v0.2/examples/' + exampleName + '/'
const exampleName = "full-ondarossa";
const expect = chai.expect;
const url =
"https://radiomanifest.degenerazione.xyz/v0.2/examples/" + exampleName + "/";
const testShowName = 'Entropia Massima'
const testWebsite = "http://www.ondarossa.info/trx/entropia-massima"
const testFeed = 'http://www.ondarossa.info/podcast/by-trx-id/10497/podcast.xml'
const testShowName = "Entropia Massima";
const testWebsite = "http://www.ondarossa.info/trx/entropia-massima";
const testFeed =
"http://www.ondarossa.info/podcast/by-trx-id/10497/podcast.xml";
describe('examples/' + exampleName, () => {
describe('shows', () => {
it('shoud find many shows', async () => {
const rm = await radiomanifest.get(url)
assert.isAbove(rm.getShows().length, 1)
})
describe("examples/" + exampleName, () => {
describe("shows", () => {
it("shoud find many shows", async () => {
const rm = await radiomanifest.get(url);
assert.isAbove(rm.getShows().length, 1);
});
it('one of which is called "Entropia Massima"', async () => {
const rm = await radiomanifest.get(url)
const show = rm.getShowByName(testShowName)
assert.equal(show.getName(), testShowName)
assert.equal(show.getWebsite(), testWebsite)
assert.equal(show.getSchedule(), null)
})
})
const rm = await radiomanifest.get(url);
const show = rm.getShowByName(testShowName);
assert.equal(show.getName(), testShowName);
assert.equal(show.getWebsite(), testWebsite);
assert.equal(show.getSchedule(), null);
});
});
describe('schedule', () => {
it('should find many event', async () => {
const rm = await radiomanifest.get(url)
assert.isAbove(rm.getSchedule().getEvents().length, 1)
})
describe("schedule", () => {
it("should find many event", async () => {
const rm = await radiomanifest.get(url);
assert.isAbove(rm.getSchedule().getEvents().length, 1);
});
it('At 1AM, nothing is going on', async () => {
const rm = await radiomanifest.get(url)
const rs = rm.getSchedule()
it("At 1AM, nothing is going on", async () => {
const rm = await radiomanifest.get(url);
const rs = rm.getSchedule();
const now = new ICAL.Time({
year: 2022,
month: 1,
day: 31,
hour: 1,
minute: 0,
second: 0,
isDate: false
});
const ev = rs.getNowEvent(now)
assert.equal(ev, null)
})
year: 2022,
month: 1,
day: 31,
hour: 1,
minute: 0,
second: 0,
isDate: false,
});
const ev = rs.getNowEvent(now);
assert.equal(ev, null);
});
it('monday at 8PM, "Entropia Massima" is going on', async () => {
const rm = await radiomanifest.get(url)
const rs = rm.getSchedule()
const rm = await radiomanifest.get(url);
const rs = rm.getSchedule();
const now = new ICAL.Time({
year: 2022,
month: 1,
day: 31,
hour: 20,
minute: 10,
second: 0,
isDate: false
},
);
const vevent = rs.getNowEvent(now)
assert.notEqual(vevent, null)
const show = rs.getNowShow(now)
assert.notEqual(show, null)
assert.equal(show.getName(), testShowName)
year: 2022,
month: 1,
day: 31,
hour: 20,
minute: 10,
second: 0,
isDate: false,
});
const vevent = rs.getNowEvent(now);
assert.notEqual(vevent, null);
const show = rs.getNowShow(now);
assert.notEqual(show, null);
assert.equal(show.getName(), testShowName);
assert.equal(show.getFeed(), testFeed)
})
assert.equal(show.getFeed(), testFeed);
});
it('monday at 7PM, "Entropia Massima" is next', async () => {
const rm = await radiomanifest.get(url)
const rs = rm.getSchedule()
const rm = await radiomanifest.get(url);
const rs = rm.getSchedule();
const now = new ICAL.Time({
year: 2022,
month: 1,
day: 31,
hour: 19,
minute: 10,
second: 0,
isDate: false
},
);
const vevent = rs.getNowEvent(now)
assert.notEqual(vevent, null)
const show = rs.getNowShow(now)
assert.notEqual(show, null)
assert.equal(show.getName(), 'Baraonda')
const next_event = rs.getNextEvent(now)
assert.notEqual(next_event, null)
assert.notEqual(next_event.event, null)
const next_show = rs.getNextShow(now)
assert.notEqual(next_show, null)
assert.notEqual(next_show.show, null)
assert.equal(next_show.show.getName(), testShowName)
assert.equal(next_show.show.getFeed(), testFeed)
})
})
})
year: 2022,
month: 1,
day: 31,
hour: 19,
minute: 10,
second: 0,
isDate: false,
});
const vevent = rs.getNowEvent(now);
assert.notEqual(vevent, null);
const show = rs.getNowShow(now);
assert.notEqual(show, null);
assert.equal(show.getName(), "Baraonda");
const next_event = rs.getNextEvent(now);
assert.notEqual(next_event, null);
assert.notEqual(next_event.event, null);
const next_show = rs.getNextShow(now);
assert.notEqual(next_show, null);
assert.notEqual(next_show.show, null);
assert.equal(next_show.show.getName(), testShowName);
assert.equal(next_show.show.getFeed(), testFeed);
});
});
});

View file

@ -1,79 +1,75 @@
const ICAL = require('ical.js')
const ICAL = require("ical.js");
const radiomanifest = require('../radiomanifest.js')
const chai = require('chai')
chai.use(require('chai-as-promised'))
const assert = chai.assert
const radiomanifest = require("../radiomanifest.js");
const chai = require("chai");
chai.use(require("chai-as-promised"));
const assert = chai.assert;
const exampleName = 'full-spore'
const expect = chai.expect
const url = 'https://radiomanifest.degenerazione.xyz/v0.2/examples/' + exampleName + '/'
const exampleName = "full-spore";
const expect = chai.expect;
const url =
"https://radiomanifest.degenerazione.xyz/v0.2/examples/" + exampleName + "/";
const testShowName = 'scaricomerci'
const testNextShowName = 'nastrone notte'
const testShowName = "scaricomerci";
const testNextShowName = "nastrone notte";
describe('examples/' + exampleName, () => {
describe('schedule.getNow', () => {
it('observes priority correctly', async () => {
describe("examples/" + exampleName, () => {
describe("schedule.getNow", () => {
it("observes priority correctly", async () => {
// tuesday, half past midnight
const rm = await radiomanifest.get(url)
const rs = rm.getSchedule()
const now = new ICAL.Time({
year: 2022,
month: 1,
day: 30,
hour: 2,
minute: 20,
second: 0,
isDate: false
const rm = await radiomanifest.get(url);
const rs = rm.getSchedule();
const now = new ICAL.Time(
{
year: 2022,
month: 1,
day: 30,
hour: 2,
minute: 20,
second: 0,
isDate: false,
},
ICAL.Timezone.utcTimezone
ICAL.Timezone.utcTimezone,
);
const vevent = rs.getNowEvent(now)
assert.notEqual(vevent, null)
const show = rs.getNowShow(now)
assert.notEqual(show, null)
assert.equal(show.getName(), testShowName)
})
})
describe('schedule.getNext', () => {
it('getNext observes priority correctly', async () => {
const vevent = rs.getNowEvent(now);
assert.notEqual(vevent, null);
const show = rs.getNowShow(now);
assert.notEqual(show, null);
assert.equal(show.getName(), testShowName);
});
});
describe("schedule.getNext", () => {
it("getNext observes priority correctly", async () => {
// tuesday, half past midnight
const rm = await radiomanifest.get(url)
const rs = rm.getSchedule()
const rm = await radiomanifest.get(url);
const rs = rm.getSchedule();
const now = new ICAL.Time({
year: 2022,
month: 2,
day: 2,
hour: 2,
minute: 20,
second: 0,
isDate: false
},
);
const vevent = rs.getNowEvent(now)
assert.notEqual(vevent, null)
const show = rs.getNowShow(now)
assert.notEqual(show, null)
assert.equal(show.getName(), testShowName)
const next_event = rs.getNextEvent(now)
assert.notEqual(next_event, null)
assert.isObject(next_event.event)
assert.isObject(next_event.time)
const next_show = rs.getNextShow(now)
assert.isObject(next_show)
assert.isObject(next_show.show)
assert.equal(next_show.show.getName(), testNextShowName)
const time = next_event.time.toJSDate()
assert.equal(time.getHours(), 2)
assert.equal(time.getMinutes(), 58)
})
})
})
year: 2022,
month: 2,
day: 2,
hour: 2,
minute: 20,
second: 0,
isDate: false,
});
const vevent = rs.getNowEvent(now);
assert.notEqual(vevent, null);
const show = rs.getNowShow(now);
assert.notEqual(show, null);
assert.equal(show.getName(), testShowName);
const next_event = rs.getNextEvent(now);
assert.notEqual(next_event, null);
assert.isObject(next_event.event);
assert.isObject(next_event.time);
const next_show = rs.getNextShow(now);
assert.isObject(next_show);
assert.isObject(next_show.show);
assert.equal(next_show.show.getName(), testNextShowName);
const time = next_event.time.toJSDate();
assert.equal(time.getHours(), 2);
assert.equal(time.getMinutes(), 58);
});
});
});

View file

@ -1,60 +1,59 @@
const ICAL = require('ical.js')
const ICAL = require("ical.js");
const radiomanifest = require('../radiomanifest.js')
const chai = require('chai')
chai.use(require('chai-as-promised'))
const assert = chai.assert
const radiomanifest = require("../radiomanifest.js");
const chai = require("chai");
chai.use(require("chai-as-promised"));
const assert = chai.assert;
const exampleName = 'onlyics'
const expect = chai.expect
const url = 'https://radiomanifest.degenerazione.xyz/v0.2/examples/' + exampleName + '/'
const exampleName = "onlyics";
const expect = chai.expect;
const url =
"https://radiomanifest.degenerazione.xyz/v0.2/examples/" + exampleName + "/";
describe('examples/' + exampleName, () => {
describe('schedule', () => {
it('should find one event', async () => {
const rm = await radiomanifest.get(url)
assert.equal(rm.getSchedule().getEvents().length, 1)
})
it('with a specific name', async () => {
const rm = await radiomanifest.get(url)
const ev = rm.getSchedule().getEvents()[0]
const summary = ev.getFirstProperty('summary').getFirstValue()
assert.equal(summary, 'JavaScript show')
})
describe("examples/" + exampleName, () => {
describe("schedule", () => {
it("should find one event", async () => {
const rm = await radiomanifest.get(url);
assert.equal(rm.getSchedule().getEvents().length, 1);
});
it("with a specific name", async () => {
const rm = await radiomanifest.get(url);
const ev = rm.getSchedule().getEvents()[0];
const summary = ev.getFirstProperty("summary").getFirstValue();
assert.equal(summary, "JavaScript show");
});
it('happens every monday at 6AM', async () => {
const rm = await radiomanifest.get(url)
const rs = rm.getSchedule()
it("happens every monday at 6AM", async () => {
const rm = await radiomanifest.get(url);
const rs = rm.getSchedule();
const now = new ICAL.Time({
year: 2022,
month: 1,
day: 3,
hour: 6,
minute: 30,
second: 0,
isDate: false
});
const show = rs.getNowShow(now)
assert.notEqual(show, null)
assert.equal(show.getName(), 'JavaScript show')
})
year: 2022,
month: 1,
day: 3,
hour: 6,
minute: 30,
second: 0,
isDate: false,
});
const show = rs.getNowShow(now);
assert.notEqual(show, null);
assert.equal(show.getName(), "JavaScript show");
});
it('doesnt happen at any other time than 6AM', async () => {
const rm = await radiomanifest.get(url)
const rs = rm.getSchedule()
it("doesnt happen at any other time than 6AM", async () => {
const rm = await radiomanifest.get(url);
const rs = rm.getSchedule();
const now = new ICAL.Time({
year: 2022,
month: 1,
day: 3,
hour: 9,
minute: 0,
second: 0,
isDate: false
});
const ev = rs.getNowEvent(now)
assert.equal(ev, null)
})
})
})
year: 2022,
month: 1,
day: 3,
hour: 9,
minute: 0,
second: 0,
isDate: false,
});
const ev = rs.getNowEvent(now);
assert.equal(ev, null);
});
});
});

View file

@ -1,23 +1,27 @@
const radiomanifest = require('../radiomanifest.js')
const chai = require('chai')
chai.use(require('chai-as-promised'))
const assert = chai.assert
const radiomanifest = require("../radiomanifest.js");
const chai = require("chai");
chai.use(require("chai-as-promised"));
const assert = chai.assert;
const exampleName = 'source404'
const expect = chai.expect
const url = 'https://radiomanifest.degenerazione.xyz/v0.2/examples/' + exampleName + '/'
const exampleName = "source404";
const expect = chai.expect;
const url =
"https://radiomanifest.degenerazione.xyz/v0.2/examples/" + exampleName + "/";
describe('examples/' + exampleName, () => {
describe('streaming', () => {
it('shoud return one streaming option', async () => {
const p = await radiomanifest.get(url)
assert.equal(p.getStreaming().getOptions().length, 1)
})
it('... whose url is stream.m3u', async () => {
const p = await radiomanifest.get(url)
assert.equal(p.getStreaming().getOptions()[0], 'try to find me')
const name = p.getStreaming().getOptions()[0]
assert.equal(p.getStreaming().getSource(name), 'https://www.radioexample.org/stream.m3u')
})
})
})
describe("examples/" + exampleName, () => {
describe("streaming", () => {
it("shoud return one streaming option", async () => {
const p = await radiomanifest.get(url);
assert.equal(p.getStreaming().getOptions().length, 1);
});
it("... whose url is stream.m3u", async () => {
const p = await radiomanifest.get(url);
assert.equal(p.getStreaming().getOptions()[0], "try to find me");
const name = p.getStreaming().getOptions()[0];
assert.equal(
p.getStreaming().getSource(name),
"https://www.radioexample.org/stream.m3u",
);
});
});
});

View file

@ -1,53 +1,52 @@
const parseM3U = require('../radiomanifest.js').parsers.M3U
const chai = require('chai')
chai.use(require('chai-as-promised'))
const assert = chai.assert
const parseM3U = require("../radiomanifest.js").parsers.M3U;
const chai = require("chai");
chai.use(require("chai-as-promised"));
const assert = chai.assert;
const expect = chai.expect
describe('parseM3U parses basic M3U', () => {
describe('empty M3U', () => {
it('should return empty list', () => {
var r = parseM3U("")
assert.equal(r.length, 0)
})
it('should discard empty lines', () => {
var r = parseM3U("\n\n\n")
assert.equal(r.length, 0)
})
})
describe('just some lines', () => {
it('should return appropriate list', () => {
var r = parseM3U("http://foo")
assert.equal(r.length, 1)
assert.equal(r[0], "http://foo")
})
it('should work with longer list', () => {
var r = parseM3U("http://foo\nhttp://baz\nhttp://bar\n")
assert.equal(r.length, 3)
assert.equal(r[0], "http://foo")
assert.equal(r[1], "http://baz")
assert.equal(r[2], "http://bar")
})
it('should discard empty lines', () => {
var r = parseM3U("http://foo\n\nhttp://baz\nhttp://bar\n\n\n")
assert.equal(r.length, 3)
assert.equal(r[0], "http://foo")
assert.equal(r[1], "http://baz")
assert.equal(r[2], "http://bar")
})
})
describe('comments', () => {
it('comments should be ignored', () => {
var r = parseM3U("http://foo\n#asd")
assert.equal(r.length, 1)
assert.equal(r[0], "http://foo")
})
it('mid-line hash is not a comment', () => {
var r = parseM3U("http://foo#asd")
assert.equal(r.length, 1)
assert.equal(r[0], "http://foo#asd")
})
})
})
const expect = chai.expect;
describe("parseM3U parses basic M3U", () => {
describe("empty M3U", () => {
it("should return empty list", () => {
var r = parseM3U("");
assert.equal(r.length, 0);
});
it("should discard empty lines", () => {
var r = parseM3U("\n\n\n");
assert.equal(r.length, 0);
});
});
describe("just some lines", () => {
it("should return appropriate list", () => {
var r = parseM3U("http://foo");
assert.equal(r.length, 1);
assert.equal(r[0], "http://foo");
});
it("should work with longer list", () => {
var r = parseM3U("http://foo\nhttp://baz\nhttp://bar\n");
assert.equal(r.length, 3);
assert.equal(r[0], "http://foo");
assert.equal(r[1], "http://baz");
assert.equal(r[2], "http://bar");
});
it("should discard empty lines", () => {
var r = parseM3U("http://foo\n\nhttp://baz\nhttp://bar\n\n\n");
assert.equal(r.length, 3);
assert.equal(r[0], "http://foo");
assert.equal(r[1], "http://baz");
assert.equal(r[2], "http://bar");
});
});
describe("comments", () => {
it("comments should be ignored", () => {
var r = parseM3U("http://foo\n#asd");
assert.equal(r.length, 1);
assert.equal(r[0], "http://foo");
});
it("mid-line hash is not a comment", () => {
var r = parseM3U("http://foo#asd");
assert.equal(r.length, 1);
assert.equal(r[0], "http://foo#asd");
});
});
});

View file

@ -1,25 +1,23 @@
const radiomanifest = require('../radiomanifest.js')
const chai = require('chai')
chai.use(require('chai-as-promised'))
const radiomanifest = require("../radiomanifest.js");
const chai = require("chai");
chai.use(require("chai-as-promised"));
const expect = chai.expect
const expect = chai.expect;
describe('radiomanifest.js', () => {
describe('Get a radiomanifest', () => {
it('should return a Promise', () => {
const p = radiomanifest.get('http://example.com/')
expect(p instanceof Promise).to.be.eql(true)
})
describe("radiomanifest.js", () => {
describe("Get a radiomanifest", () => {
it("should return a Promise", () => {
const p = radiomanifest.get("http://example.com/");
expect(p instanceof Promise).to.be.eql(true);
});
it('should reject on invalid URL', () => {
const p = radiomanifest.get('http://example.com/')
expect(p).to.eventually.be.rejected
})
})
it("should reject on invalid URL", () => {
const p = radiomanifest.get("http://example.com/");
expect(p).to.eventually.be.rejected;
});
});
describe('streaming', () => {
it('shoud return a valid streaming URL', () => {
})
})
})
describe("streaming", () => {
it("shoud return a valid streaming URL", () => {});
});
});

92
ui.js
View file

@ -1,55 +1,65 @@
// const radiomanifest = require('radiomanifest.js')
function updateNow(radio) {
var box = document.querySelector('#now-info')
const show = radio.getSchedule().getNowShow()
var box = document.querySelector("#now-info");
const show = radio.getSchedule().getNowShow();
try {
var showText = show.getName() + '\nfeed: ' + show.getWebsite()
var showText = show.getName() + "\nfeed: " + show.getWebsite();
} catch (e) {
var showText = String(show)
var showText = String(show);
}
var text = radio.getName() + ' - ' + radio.getDescription() + ' -- ' + showText
box.textContent = text
var text =
radio.getName() + " - " + radio.getDescription() + " -- " + showText;
box.textContent = text;
}
async function fai () {
const radio = await radiomanifest.get('https://radiomanifest.degenerazione.xyz/v0.2/examples/full-ondarossa')
console.log('radio?', radio)
const s = radio.getStreaming()
console.log(s.sources)
console.log(s.getOptions())
console.log(s.getSource(s.getOptions()[0]))
console.log(s.getSource())
async function fai() {
const radio = await radiomanifest.get(
"https://radiomanifest.degenerazione.xyz/v0.2/examples/full-ondarossa",
);
console.log("radio?", radio);
const s = radio.getStreaming();
console.log(s.sources);
console.log(s.getOptions());
console.log(s.getSource(s.getOptions()[0]));
console.log(s.getSource());
var audioEl = document.querySelector('#player audio')
var urls = await s.pickURLs()
console.log('audios', urls)
urls.forEach( function(url) {
var srcEl = document.createElement("source")
srcEl.setAttribute('src', url)
console.log('src', srcEl, url)
audioEl.appendChild(srcEl)
})
var audioEl = document.querySelector("#player audio");
var urls = await s.pickURLs();
console.log("audios", urls);
urls.forEach(function (url) {
var srcEl = document.createElement("source");
srcEl.setAttribute("src", url);
console.log("src", srcEl, url);
audioEl.appendChild(srcEl);
});
const showList = document.querySelector('#shows > ul')
const showList = document.querySelector("#shows > ul");
for (const show of radio.getShows()) {
const item = document.createElement('li')
const link = document.createElement('a')
link.dataset['show'] = show.getName()
link.textContent = show.getName()
link.setAttribute('href', show.getWebsite())
item.appendChild(link)
showList.appendChild(item)
const item = document.createElement("li");
const link = document.createElement("a");
link.dataset["show"] = show.getName();
link.textContent = show.getName();
link.setAttribute("href", show.getWebsite());
item.appendChild(link);
showList.appendChild(item);
}
showList.addEventListener('mouseenter', function (evt) {
if (evt.target.dataset['show'] === undefined)
return;
const info = document.querySelector('#show-info')
info.textContent = radio.getShowByName(evt.target.dataset['show']).getFeed()
}, true)
showList.addEventListener(
"mouseenter",
function (evt) {
if (evt.target.dataset["show"] === undefined) return;
const info = document.querySelector("#show-info");
info.textContent = radio
.getShowByName(evt.target.dataset["show"])
.getFeed();
},
true,
);
updateNow(radio)
console.log(radio.getSchedule())
setInterval(function() { updateNow(radio) }, 2000)
updateNow(radio);
console.log(radio.getSchedule());
setInterval(function () {
updateNow(radio);
}, 2000);
}
fai()
fai();

View file

@ -1,27 +1,25 @@
const path = require('path')
const path = require("path");
const variants = [
{ name: 'web', libtype: 'amd', target: 'web' },
{ name: 'node', libtype: 'amd', target: 'node' },
{ name: 'oldstyle', libtype: 'umd', target: 'web' }
]
module.exports = variants.map(
variant => {
const obj = {
name: variant.name,
mode: 'production',
entry: './radiomanifest.js',
target: [variant.target],
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'radiomanifest-' + variant.name + '.bundle.js',
clean: false,
library: {
name: 'radiomanifest',
type: variant.libtype
}
}
}
return obj
}
)
{ name: "web", libtype: "amd", target: "web" },
{ name: "node", libtype: "amd", target: "node" },
{ name: "oldstyle", libtype: "umd", target: "web" },
];
module.exports = variants.map((variant) => {
const obj = {
name: variant.name,
mode: "production",
entry: "./radiomanifest.js",
target: [variant.target],
output: {
path: path.resolve(__dirname, "dist"),
filename: "radiomanifest-" + variant.name + ".bundle.js",
clean: false,
library: {
name: "radiomanifest",
type: variant.libtype,
},
},
};
return obj;
});