Comparing version 1.0.0 to 1.1.0
{ | ||
"name": "88nine", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"description": "Library for interacting with 88nine Radio Milwaukee", | ||
@@ -27,4 +27,3 @@ "bin": { | ||
"node-fetch": "^2.1.2", | ||
"readable-stream": "^2.3.6", | ||
"xml2js": "^0.4.19" | ||
"readable-stream": "^2.3.6" | ||
}, | ||
@@ -31,0 +30,0 @@ "devDependencies": { |
@@ -62,13 +62,1 @@ # 88nine Radio Milwaukee | ||
``` | ||
## Incorrect Songs | ||
We pull data from an XML feed at `https://s3.amazonaws.com/radiomilwaukee-playlist/WYMSHIS.XML` which is supposed to contain the last 100 songs. So, this package is designed with a streaming interface that _would_ write out the full 100 song history as soon as you start the stream, and then gradually write out songs as they are being played & added to the the XML feed. | ||
However, that's not possible because, as of right now (2018-06-11), the XML feed will return a large number of incorrect songs. These songs are real songs with all the information that you would expect, but they have not actually been played on the radio at the specified times. As far as I can tell, there is no information in the song data that can be used to distinguish these incorrect songs from the songs that do get played. | ||
Even the `playedAt` times and `duration` fields of incorrect songs will often match up with a realistic schedule. Attempting to filter out incorrect songs by constructing a schedule and fitting songs in based on their reported start and end times will not work. Based on the reported `playedAt` times and `duration` fields, correct songs can overlap by up to 10 seconds and can be separated by gaps of several minutes (for advertising blocks). Given these constraints, incorrect songs still fit into the resulting schedule. | ||
The `WYMSHIS.XML` feed is updated regularly and it seems like each time it is updated, the first song in the list is the song currently being played, or the last song that was played, followed by 0 or more songs that have not been played. Reading only the first song is the most reliable way of getting correct songs that I have found. Sadly, this means that nothing in the `WYMSHIS.XML` file, past the first song, can be trusted. | ||
Until this is fixed, the stream won't return any history, it will only contain songs that have been played directly before or shortly after the stream was started. |
@@ -1,5 +0,4 @@ | ||
const xml2js = require('xml2js') | ||
const fetch = require('node-fetch') | ||
const PLAYLIST_URL = 'https://s3.amazonaws.com/radiomilwaukee-playlist/WYMSHIS.XML' | ||
const PLAYLIST_URL = 'https://s3.amazonaws.com/radiomilwaukee-playlist/RMDATA' | ||
@@ -29,3 +28,3 @@ /** | ||
const fetchPlaylistXml = (retryTime = 1000) => ( | ||
let fetchPlaylist = (retryTime = 1000) => ( | ||
fetch(PLAYLIST_URL).then( | ||
@@ -43,38 +42,30 @@ checkStatus | ||
// grow the delay between retries linearly | ||
fetchPlaylistXml.bind(this, retryTime + 1000) | ||
fetchPlaylist.bind(this, retryTime + 1000) | ||
) | ||
}) | ||
}).then( | ||
response => response.json() | ||
) | ||
) | ||
let fetchPlaylistXmlAsText = () => ( | ||
fetchPlaylistXml().then(res => res.text()) | ||
) | ||
const parseDuration = (duration) => { | ||
const [hours, minutes, seconds] = duration.split(':').map((x) => parseInt(x)) | ||
return hours * 60 * 60 + minutes * 60 + seconds | ||
} | ||
function parsePlaylist (rawXml) { | ||
return new Promise((resolve, reject) => { | ||
xml2js.parseString(rawXml, (error, json) => { | ||
if (error) { return reject(error) } | ||
resolve(json.PlayList.Song) | ||
}) | ||
}) | ||
}; | ||
const cleanPlaylist = (playlist) => { | ||
// if the playlist is empty, return an empty array | ||
if (playlist.nowPlaying.length === 0) { | ||
return [] | ||
} | ||
function cleanPlaylist (playlist) { | ||
// HACK: we cannot trust anything but the most recent song in the playlist | ||
// (see the "Incorrect Songs" section in README.md). If / when this is fixed, | ||
// removing the following line will enable handing the full history that's | ||
// avaliable in WYMSHIS.XML and all 100 entries will be written out, in order, | ||
// when a new stream is created. | ||
playlist = [playlist[0]] | ||
// the playlist only gives us the current song | ||
const song = playlist.nowPlaying[0] | ||
// as far as I can tell, the "Date" element is always just a less accurate | ||
// copy of "AIRTIME", and both "Composer" & "MusicId" are always blank. "Cart" | ||
// is omitted because it's not useful. | ||
return playlist.map((song) => ({ | ||
album: song.Album[0], | ||
artist: song.Artist[0], | ||
duration: parseInt(song.Duration[0]), | ||
playedAt: new Date(song.AIRTIME[0]), | ||
title: song.Title[0] | ||
})) | ||
return [{ | ||
album: song.album, | ||
artist: song.artist, | ||
duration: parseDuration(song.duration), | ||
playedAt: new Date(song.startTime), | ||
title: song.title | ||
}] | ||
} | ||
@@ -86,3 +77,3 @@ | ||
module.exports.fetch = () => ( | ||
fetchPlaylistXmlAsText().then(parsePlaylist).then(cleanPlaylist) | ||
fetchPlaylist().then(cleanPlaylist) | ||
) |
@@ -6,30 +6,58 @@ var assert = require('assert') | ||
var playlist = rewire('../src/playlist') | ||
playlist.__set__('fetchPlaylistXmlAsText', function () { | ||
var fs = require('fs') | ||
return new Promise((resolve, reject) => { | ||
resolve(fs.readFileSync('./test/data/playlist.xml').toString()) | ||
}) | ||
}) | ||
describe('Playlist', () => { | ||
describe('#fetch()', () => { | ||
it.skip('is the correct length', () => { | ||
it('works with normal playlist', () => { | ||
playlist.__set__('fetchPlaylist', function () { | ||
var fs = require('fs') | ||
return new Promise((resolve, reject) => { | ||
resolve(JSON.parse(fs.readFileSync('./test/data/playlist.json'))) | ||
}) | ||
}) | ||
return playlist.fetch().then((playlist) => { | ||
assert.equal(100, playlist.length) | ||
var song = playlist[0] | ||
assert.deepEqual(song, { | ||
album: 'Good Days - Single', | ||
artist: 'SZA', | ||
duration: 4 * 60 + 24, | ||
playedAt: new Date('2021-02-26T18:42:20.000Z'), | ||
title: 'Good Days' | ||
}) | ||
}) | ||
}) | ||
it('has all required attributes', () => { | ||
it('works with playlist that has image', () => { | ||
playlist.__set__('fetchPlaylist', function () { | ||
var fs = require('fs') | ||
return new Promise((resolve, reject) => { | ||
resolve(JSON.parse(fs.readFileSync('./test/data/playlist-with-image.json'))) | ||
}) | ||
}) | ||
return playlist.fetch().then((playlist) => { | ||
var song = playlist[0] | ||
assert.deepEqual(song, { | ||
album: 'Single - On and On', | ||
artist: 'Curtis Harding', | ||
duration: 226, | ||
playedAt: new Date('2017-10-08T14:51:27.000Z'), | ||
title: 'On and On' | ||
album: 'Caravelle', | ||
artist: 'Polo & Pan', | ||
duration: 4 * 60 + 33, | ||
playedAt: new Date('2021-02-26T18:46:37.000Z'), | ||
title: 'Canopée' | ||
}) | ||
}) | ||
}) | ||
it('works with empty playlist', () => { | ||
playlist.__set__('fetchPlaylist', function () { | ||
var fs = require('fs') | ||
return new Promise((resolve, reject) => { | ||
resolve(JSON.parse(fs.readFileSync('./test/data/playlist-empty.json'))) | ||
}) | ||
}) | ||
return playlist.fetch().then((playlist) => { | ||
assert.deepEqual(playlist, []) | ||
}) | ||
}) | ||
}) | ||
}) |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
61890
5
10
231
2
62
3
- Removedxml2js@^0.4.19
- Removedsax@1.4.1(transitive)
- Removedxml2js@0.4.23(transitive)
- Removedxmlbuilder@11.0.1(transitive)