Files
Zos/Skills/@be/node_modules/jibo-radio/lib/jibo-radio.js

1030 lines
43 KiB
JavaScript

(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.jiboRadio = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const axios_1 = require("axios");
const jibo_cai_utils_1 = require("jibo-cai-utils");
const crypto = require("crypto");
const dns = require("dns");
const events_1 = require("events");
const HLS = require("hls.js");
const http = require("http");
const icecast = require("icecast");
const jibo_log_1 = require("jibo-log");
const url = require("url");
const HJ_VOLUME = 1;
const VOLUME_NORMAL = 0.06;
const DNS_TIMEOUT = 5000;
const STREAM_TIMEOUT = 10000;
const promisify = jibo_cai_utils_1.PromiseUtils.promisify;
var MediaType;
(function (MediaType) {
MediaType["Music"] = "Music";
MediaType["Talk"] = "Talk";
MediaType["Other"] = "Other";
})(MediaType = exports.MediaType || (exports.MediaType = {}));
var Mode;
(function (Mode) {
Mode["HLS"] = "HLS";
Mode["Shoutcast"] = "Shoutcast";
Mode["PLS"] = "PLS";
})(Mode = exports.Mode || (exports.Mode = {}));
var Locality;
(function (Locality) {
Locality["Local"] = "Local";
Locality["National"] = "National";
})(Locality = exports.Locality || (exports.Locality = {}));
const DEVICE_ID_SALT = '6464e9ec0098c3e04d65392f607b9ed9';
class RadioPlayer extends events_1.EventEmitter {
constructor() {
super();
this._promises = new jibo_cai_utils_1.CancelTokenSession();
this._isCurrentlySpeaking = false;
this._songDataTimers = [];
this._hjHeard = () => {
this._volume = HJ_VOLUME;
};
this._listenFinished = () => {
if (!this._isCurrentlySpeaking) {
this._volume = this._volumePlugin.currentVolume;
}
};
this._volumeChanged = (volume) => {
this._volume = volume;
};
this._hlsMediaAttached = () => {
if (this._hls) {
this._hls.loadSource(this._hlsURI);
}
};
this._hlsManifestParsed = (event, data) => {
if (this._hls) {
this._log.info(`Manifest loaded, found ${data.levels.length} quality levels`);
this._mode = Mode.HLS;
this._volume = this._volumePlugin.currentVolume;
this.resume();
}
};
this._hlsFragParsing = (event, fragment) => __awaiter(this, void 0, void 0, function* () {
this._log.debug(event, fragment);
if (fragment.frag.title) {
try {
this._emitSongData(this._processHLSMetadata(fragment.frag.title));
}
catch (err) {
this._log.info('Problem parsing HLS stream metadata; trying sideband', err);
this._emitSongData(yield this._getSidebandData(), true);
}
}
});
this._handleHLSErrors = (event, data) => {
if (!this._hls) {
return;
}
if (!data.fatal) {
this._log.info('HLS warning', data);
return;
}
switch (data.type) {
case HLS.ErrorTypes.MEDIA_ERROR:
if (!this._recoveringMedia) {
this._log.info('Trying to recover from media error', data);
this._recoveringMedia =
setTimeout(() => this._recoveringMedia = null, 2000);
this._hls.recoverMediaError();
}
else if (!this._swappingAudioCodec) {
this._log.info('Swapping audio codec this time, then trying again to recover from media error', data);
clearTimeout(this._recoveringMedia);
this._recoveringMedia = null;
this._swappingAudioCodec = true;
setTimeout(() => this._swappingAudioCodec = false, 2000);
this._hls.swapAudioCodec();
this._hls.recoverMediaError();
}
else {
this._recoveringMedia = null;
this._swappingAudioCodec = false;
this._handleStreamError(data);
}
break;
default:
this._handleStreamError(data);
break;
}
};
this._handleMediaPlaying = () => {
this._mediaTagPlayPromise = null;
this._streamStarted();
this._resolveStreamPromise();
};
this._handleAudioError = () => {
if (!this._log) {
return;
}
if (this._audio) {
this._audio.removeEventListener('loadeddata', this._handleMediaPlaying);
this._audio.removeEventListener('stalled', this._handleAudioError);
this._audio.removeEventListener('error', this._handleAudioError);
}
this._mediaTagPlayPromise = null;
const code = this._audio && this._audio.error && this._audio.error.code;
if (code !== MediaError.MEDIA_ERR_ABORTED) {
const err = RadioPlayer._getMediaError(code);
if (this._mode === Mode.PLS) {
this._nextPLSStream(err);
}
else {
this._handleStreamError(err);
}
}
};
this._handleVideoError = () => {
if (!this._log) {
return;
}
if (this._video) {
this._video.removeEventListener('loadeddata', this._handleMediaPlaying);
this._video.removeEventListener('error', this._handleAudioError);
}
this._mediaTagPlayPromise = null;
const code = this._video && this._video.error && this._video.error.code;
if (code !== MediaError.MEDIA_ERR_ABORTED) {
this._handleStreamError(RadioPlayer._getMediaError(code));
}
};
if (!HLS.isSupported()) {
throw new Error('Oh no! HTTP Live Streaming isn\'t supported!');
}
}
static _hash(name) {
const shasum = crypto.createHash('sha256');
shasum.update(`${DEVICE_ID_SALT}-${name}`);
return shasum.digest('hex');
}
static _songDataDifferent(left, right) {
return !!left && !right
|| !left && !!right
|| left.mediaType !== right.mediaType
|| left.artist !== right.artist
|| left.title !== right.title;
}
static _promiseTimeout(promise, millis) {
return Promise.race([
promise,
new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), millis)),
]);
}
init(serialNumber, releaseVersion, country, lat, lng, listenEvents, volumePlugin) {
return __awaiter(this, void 0, void 0, function* () {
this._deviceId = RadioPlayer._hash(serialNumber);
this._releaseVersion = releaseVersion;
this._country = country;
this._lat = lat;
this._lng = lng;
this._listenEvents = listenEvents;
this._listenEvents.hjHeard.on(this._hjHeard);
this._listenEvents.hjOnly.on(this._listenFinished);
this._listenEvents.noGlobalMatch.on(this._listenFinished);
this._listenEvents.nonInterruptingGlobal.on(this._listenFinished);
this._volumePlugin = volumePlugin;
this._volumePlugin.on('change', this._volumeChanged);
this._log = new jibo_log_1.Log('Jibo.Radio');
});
}
getCountry() {
return __awaiter(this, void 0, void 0, function* () {
return this._country;
});
}
resizeArtwork(uri, width) {
return uri;
}
pause() {
return __awaiter(this, void 0, void 0, function* () {
if (this._mediaTagPlayPromise) {
try {
yield this._mediaTagPlayPromise;
}
catch (err) {
this._log.info('HTMLMediaElement#play rejected', err);
}
this._mediaTagPlayPromise = null;
}
if (this._audio) {
this._audio.pause();
}
if (this._video) {
this._video.pause();
}
});
}
resume() {
return __awaiter(this, void 0, void 0, function* () {
if (this._mode === 'HLS' && !this._video
|| this._mode !== 'HLS' && !this._audio) {
return;
}
try {
yield this.pause();
}
catch (err) {
this._log.info('Error pausing stream', err);
}
this._mediaTagPlayPromise = this._promises.wrap(this._mode === Mode.HLS
? this._video.play()
: this._audio.play()).catch(err => {
this._log.info('HTMLMediaElement#play rejected', err);
if (this._mode === Mode.HLS) {
this._handleVideoError();
}
else {
this._handleAudioError();
}
});
});
}
stop() {
return __awaiter(this, void 0, void 0, function* () {
this._lastSongData = undefined;
if (this._songDataSession) {
this._songDataSession.cancel();
this._songDataSession = null;
}
this._songDataTimers.forEach(clearTimeout);
this._songDataTimers = [];
if (this._pollSongData) {
clearTimeout(this._pollSongData);
this._pollSongData = null;
}
this._icecastClient = null;
if (this._shoutcastServer) {
this._shoutcastServer.close();
this._shoutcastServer = null;
}
if (this._hls) {
this._hls.off(HLS.Events.ERROR, this._handleHLSErrors);
this._hls.off(HLS.Events.FRAG_PARSING_METADATA, this._hlsFragParsing);
this._hls.off(HLS.Events.MANIFEST_PARSED, this._hlsManifestParsed);
this._hls.off(HLS.Events.MEDIA_ATTACHED, this._hlsMediaAttached);
this._hls.destroy();
this._hls = null;
}
try {
yield this.pause();
}
catch (err) {
this._log.info('Error during pause', err);
}
if (this._audio) {
this._audio.removeEventListener('loadeddata', this._handleMediaPlaying);
this._audio.removeEventListener('stalled', this._handleAudioError);
this._audio.removeEventListener('error', this._handleAudioError);
this._audio = null;
}
if (this._video) {
this._video.removeEventListener('loadeddata', this._handleMediaPlaying);
this._video.removeEventListener('error', this._handleVideoError);
this._video = null;
}
});
}
destroy() {
this._listenEvents.hjHeard.off(this._hjHeard);
this._listenEvents.hjOnly.off(this._listenFinished);
this._listenEvents.noGlobalMatch.off(this._listenFinished);
this._listenEvents.nonInterruptingGlobal.off(this._listenFinished);
this._log = null;
this._playPromise = null;
this._playReject = null;
this._playResolve = null;
this._promises.cancel();
this._promises = null;
this._volumePlugin.removeListener('change', this._volumeChanged);
}
stopAndDestroy() {
return __awaiter(this, void 0, void 0, function* () {
try {
yield this.stop();
}
catch (err) {
this._log.warn('Error when stopping during cleanup', err);
}
this.destroy();
});
}
_handleStreamError(err) {
this._rejectOrEmit(err);
}
_streamHLS(uri, config = undefined, failover = false) {
return __awaiter(this, void 0, void 0, function* () {
this._log.debug('_streamHLS called', uri, config);
yield this.stop();
this._mode = Mode.HLS;
this._hlsURI = uri;
this._hls = new HLS(Object.assign({}, config));
this._hls.on(HLS.Events.MEDIA_ATTACHED, this._hlsMediaAttached);
this._hls.on(HLS.Events.MANIFEST_PARSED, this._hlsManifestParsed);
this._hls.on(HLS.Events.FRAG_PARSING_METADATA, this._hlsFragParsing);
this._hls.on(HLS.Events.ERROR, this._handleHLSErrors);
this._video = document.createElement('video');
this._video.addEventListener('loadeddata', this._handleMediaPlaying);
this._video.addEventListener('error', this._handleVideoError);
if (failover) {
return this._hls.attachMedia(this._video);
}
return this._promises.wrap(RadioPlayer._promiseTimeout(this._setStreamPromiseHandlers(() => {
this._hls.attachMedia(this._video);
}), STREAM_TIMEOUT));
});
}
_fillMissingSongData(songData) {
return __awaiter(this, void 0, void 0, function* () {
songData = songData || {};
if (!songData.mediaType || songData.mediaType === MediaType.Music) {
let artworkUrl = null;
if (songData.artworkUrl) {
try {
const response = yield axios_1.default.head(songData.artworkUrl);
if (response.status >= 200 && response.status < 300) {
artworkUrl = songData.artworkUrl;
}
}
catch (err) {
artworkUrl = null;
}
}
if (!songData.artist || !artworkUrl || !songData.title) {
this._log.info('Stream data was missing some fields; fetching from sideband');
this._log.info('Stream data', songData);
const sidebandData = yield this._getSidebandData();
if (sidebandData) {
this._log.info('Sideband data', sidebandData);
if (!artworkUrl && sidebandData.artworkUrl) {
songData.artworkUrl = sidebandData.artworkUrl;
}
if (!songData.artist && sidebandData.artist) {
songData.artist = sidebandData.artist;
}
if (!songData.title && sidebandData.artist) {
songData.title = sidebandData.title;
}
this._log.info('Combined data', songData);
}
}
else {
this._log.info('No sideband data found');
}
}
return songData;
});
}
_emitSongData(songData, fromSideband = false) {
return __awaiter(this, void 0, void 0, function* () {
const firstTime = this._lastSongData === undefined;
if (!this._log
|| !songData && !this._lastSongData
|| !firstTime && !RadioPlayer._songDataDifferent(songData, this._lastSongData)) {
return;
}
this._lastSongData = songData;
this._songDataSession = new jibo_cai_utils_1.CancelTokenSession();
let filledSongData = songData;
if (!fromSideband) {
try {
filledSongData = yield this._songDataSession.wrap(this._fillMissingSongData(songData));
}
catch (err) {
this._log.info('Failed to get sideband data', err);
}
}
if (this._mode === Mode.HLS && this._video && !firstTime) {
const end = this._video.buffered.length
? this._video.buffered.end(this._video.buffered.length - 1)
: 0;
const current = this._video.currentTime;
const buffer = end - current;
const emitIn = end === 0 ? 0 : buffer + 5;
this._log.debug(`end: ${end}, current: ${current}, buffer: ${buffer}`);
this._log.debug(`Will emit songData in ${emitIn} seconds`, filledSongData);
const emitData = () => {
this.emit('song-data', filledSongData);
this._songDataTimers.shift();
};
this._songDataTimers.push(setTimeout(this._promises.wrapCallback(emitData), emitIn * 1000));
}
else {
this.emit('song-data', filledSongData);
}
});
}
_streamShoutcast(uri, failover = false) {
return __awaiter(this, void 0, void 0, function* () {
this._log.debug(`Streaming Shoutcast from ${uri}; failover ${failover}`);
yield this.stop();
this._mode = Mode.Shoutcast;
try {
const hostname = url.parse(uri).hostname;
yield this._promises.wrap(RadioPlayer._promiseTimeout(promisify(cb => dns.resolve(hostname, cb)), DNS_TIMEOUT));
}
catch (err) {
this._log.info('Shoutcast server hostname failed to resolve', err);
return failover
? this._handleAudioError()
: this._setStreamPromiseHandlers(setImmediate(() => this._handleAudioError()));
}
this._icecastClient = yield this._promises.wrap(promisify(cb => icecast.get(uri, cb), false));
this._icecastClient.on('metadata', (songData) => this._emitSongData(this._processShoutcastMetadata(icecast.parse(songData))));
this._icecastClient.on('error', this._handleAudioError);
this._shoutcastServer = http.createServer((req, res) => {
res.setHeader('Cache-Control', 'no-cache, no-store');
res.setHeader('Connection', 'close');
res.setHeader('Content-Type', 'audio/aac');
res.setHeader('Expires', 'Mon, 26 Jul 1997 05:00:00 GMT');
res.setHeader('Pragma', 'no-cache');
this._icecastClient.pipe(res);
});
this._shoutcastServer.listen(0, '127.0.0.1');
yield promisify(cb => this._shoutcastServer.once('listening', cb));
if (!this._shoutcastServer) {
return;
}
const port = this._shoutcastServer.address().port;
this._log.debug(`Shoutcast server listening on ${port}`);
this._audio = document.createElement('audio');
this._audio.addEventListener('loadeddata', this._handleMediaPlaying);
this._audio.addEventListener('stalled', this._handleAudioError);
this._audio.addEventListener('error', this._handleAudioError);
this._audio.src = `http://127.0.0.1:${port}/`;
this._volume = this._volumePlugin.currentVolume;
if (failover) {
return this.resume();
}
return this._promises.wrap(RadioPlayer._promiseTimeout(this._setStreamPromiseHandlers(() => this.resume()), STREAM_TIMEOUT));
});
}
_streamPLS(uri, failover = false) {
return __awaiter(this, void 0, void 0, function* () {
this._log.debug(`Streaming PLS from ${uri}`);
yield this.stop();
this._mode = Mode.PLS;
try {
const plsContent = (yield this._promises.wrap(axios_1.default.get(uri))).data;
this._plsStreams = plsContent.split('\n')
.filter(url => url.startsWith('File'))
.map(url => url.split('=', 2)[1]);
this._plsStreamFirstIndex = this._plsStreamIndex =
Math.floor(Math.random() * this._plsStreams.length);
}
catch (err) {
return this._handleAudioError();
}
this._audio = document.createElement('audio');
this._audio.addEventListener('loadeddata', this._handleMediaPlaying);
this._audio.addEventListener('stalled', this._handleAudioError);
this._audio.addEventListener('error', this._handleAudioError);
this._volume = this._volumePlugin.currentVolume;
if (failover) {
return this.resume();
}
return this._promises.wrap(RadioPlayer._promiseTimeout(this._setStreamPromiseHandlers(() => this._playPLSStream()), STREAM_TIMEOUT));
});
}
_streamStarted() { }
setCurrentlySpeaking(isCurrentlySpeaking) {
this._isCurrentlySpeaking = isCurrentlySpeaking;
this._volume = isCurrentlySpeaking ? HJ_VOLUME : this._volumePlugin.currentVolume;
}
get _volume() {
return this._audio ? this._audio.volume / VOLUME_NORMAL
: this._video ? this._video.volume / VOLUME_NORMAL
: 0;
}
set _volume(value) {
if (value < 1 || value > 10) {
throw new Error(`Value must be between 1 and 10, inclusive`);
}
const volume = value * VOLUME_NORMAL;
if (this._audio) {
this._audio.volume = volume;
}
if (this._video) {
this._video.volume = volume;
}
}
_playPLSStream() {
return __awaiter(this, void 0, void 0, function* () {
this._audio.src = this._plsStreams[this._plsStreamIndex];
yield this.resume();
const getAndEmitData = () => __awaiter(this, void 0, void 0, function* () {
const data = yield this._getSidebandData();
yield this._emitSongData(data, true);
setTimeout(getAndEmitData, 1000);
});
getAndEmitData();
});
}
_nextPLSStream(data) {
return __awaiter(this, void 0, void 0, function* () {
const oldIndex = this._plsStreamIndex;
if (!this._plsStreams) {
return this._handleStreamError(data);
}
if (++this._plsStreamIndex === this._plsStreams.length) {
this._plsStreamIndex = 0;
}
if (this._plsStreamIndex !== this._plsStreamFirstIndex) {
this._log.info(`PLS stream ${oldIndex} failed`);
yield this._playPLSStream();
}
else {
this._handleStreamError(data);
}
});
}
static _getMediaError(code) {
switch (code) {
case 1: return 'MEDIA_ERR_ABORTED';
case 2: return 'MEDIA_ERR_NETWORK';
case 3: return 'MEDIA_ERR_DECODE';
case 4: return 'MEDIA_ERR_SRC_NOT_SUPPORTED';
default: return 'UNKNOWN';
}
}
_setStreamPromiseHandlers(startStream) {
if (this._playPromise) {
startStream();
return this._playPromise;
}
return this._playPromise = this._promises.wrap(new Promise((resolve, reject) => {
this._playReject = reject;
this._playResolve = resolve;
startStream();
}));
}
_resolveStreamPromise() {
if (this._playPromise) {
this._log.info('Resolving play promise');
this._playResolve();
this._playPromise = null;
this._playReject = null;
this._playResolve = null;
}
}
_rejectOrEmit(reason) {
if (this._playPromise) {
this._log.info('Rejecting play promise');
this._playReject(reason);
this._playPromise = null;
this._playReject = null;
this._playResolve = null;
}
else {
this.emit('error', `Error while playing stream: ${reason}`);
}
}
}
exports.default = RadioPlayer;
},{"axios":undefined,"crypto":undefined,"dns":undefined,"events":undefined,"hls.js":undefined,"http":undefined,"icecast":undefined,"jibo-cai-utils":undefined,"jibo-log":undefined,"url":undefined}],2:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.IHeartGenres = {
Alternative: 1,
ChristianAndGospel: 2,
ClassicRock: 3,
Classical: 4,
Country: 5,
HipHopAndRAndB: 6,
HipHop: 6,
Jazz: 7,
MixAndVariety: 8,
NewsAndTalk: 9,
Oldies: 10,
ReggaeAndIsland: 11,
Reggae: 11,
Rock: 12,
SoftRock: 13,
Spanish: 14,
Sports: 15,
Top40AndPop: 16,
Pop: 16,
World: 17,
EightiesAndNinetiesHits: 18,
Comedy: 19,
Dance: 77,
PublicRadio: 93,
NPR: 93,
HostsAndDJs: 95,
TrafficWeatherAndNews: 96,
Holiday: 97,
CollegeRadio: 98,
Mexico: 100,
Personalities: 101,
PopularArtists: 102,
EDM: 103,
RAndB: 104,
KidsAndFamily: 106,
Punk: 107,
Blues: 108,
International: 110,
Metal: 1191,
Latin: 1192,
};
},{}],3:[function(require,module,exports){
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const axios_1 = require("axios");
const IHeartInterfaces_1 = require("./IHeartInterfaces");
const RadioPlayer_1 = require("../RadioPlayer");
const querystring = require("querystring");
const CLIENT_VERSION = '1.0.0';
const DEFAULT_NUM_STATIONS_PER_GENRE = 20;
const DEVICE_NAME = 'Jibo';
const INITIAL_HOSTNAME = 'jibo.appliance';
const INIT_ID = 8169;
const NATIONAL_DIGITAL = 575;
const PLAYER = 'iHeartRadioJibo';
const GET_LOCATION_URI = `https://global.api.iheart.com/api/v3/locationConfig?hostname=${INITIAL_HOSTNAME}&version=1.0.0`;
const ENDPOINTS = Object.freeze({
clientConfig: 'v1/bootstrap/getClientConfig',
countries: 'v2/content/countries',
login: 'v1/account/loginOrCreateOauthUser',
states: 'v2/content/states',
cities: 'v2/content/cities',
markets: 'v2/content/markets',
genres: 'v2/content/liveStationGenres',
liveStations: 'v2/content/liveStations',
metadata: 'v1/liveMetaData/getStationTrack',
reportStreamStarted: 'v1/liveRadio/reportStreamStarted',
});
const DEFAULT_OPTIONS = Object.freeze({
responseType: 'json',
});
const HLS_CONFIG = Object.freeze({
maxBufferLength: 10,
maxMaxBufferLength: 33,
});
class IHeartPlayer extends RadioPlayer_1.default {
constructor() {
super(...arguments);
this._stationCache = {};
this._getSidebandData = () => __awaiter(this, void 0, void 0, function* () {
if (!this._iHeartLog) {
return;
}
const result = yield this._promises.wrap(this._getSongData({ stationId: this._station.id }));
const metadata = result.trackRestLiteValues
&& result.trackRestLiteValues.length
&& result.trackRestLiteValues[0];
if (!metadata) {
return null;
}
const fullLength = metadata.trackDuration || 0;
const minutes = Math.floor(fullLength / 60);
const seconds = fullLength - minutes * 60;
const artworkUrl = !metadata.imagePath || metadata.imagePath.includes('imscale')
? null
: metadata.imagePath;
return {
artist: metadata.artistName,
title: metadata.title,
length: `${minutes}:${seconds < 10 ? `0${seconds}` : seconds}`,
artworkUrl,
};
});
}
static _getMediaType(fromIHeart) {
switch (fromIHeart) {
case 'M': return RadioPlayer_1.MediaType.Music;
case 'T': return RadioPlayer_1.MediaType.Talk;
case 'O': return RadioPlayer_1.MediaType.Other;
default: return null;
}
}
init(serialNumber, releaseVersion, country, lat, lng, listenEvents, volumePlugin) {
const _super = name => super[name];
return __awaiter(this, void 0, void 0, function* () {
yield _super("init").call(this, serialNumber, releaseVersion, country, lat, lng, listenEvents, volumePlugin);
this._iHeartLog = this._log.createChild('iHeart');
this._locationConfigPromise = this._promises.wrap(this._getLocation().catch(err => this._locationConfigError = err));
this._initPromise = this._promises.wrap(this._init().catch(err => this._initError = err));
});
}
getCountry() {
return __awaiter(this, void 0, void 0, function* () {
yield this._locationConfigPromise;
if (this._locationConfigError) {
throw this._locationConfigError;
}
return this._countryCode;
});
}
getStations(options = {}) {
return __awaiter(this, void 0, void 0, function* () {
yield this._initPromise;
if (this._initError) {
throw this._initError;
}
const limit = options.limit || DEFAULT_NUM_STATIONS_PER_GENRE;
const genreId = IHeartInterfaces_1.IHeartGenres[options.genreName];
let stations = yield this._promises.wrap(this._getLiveStations(Object.assign({ genreId }, (options.locality === RadioPlayer_1.Locality.Local
? { lat: this._lat, lng: this._lng }
: { marketId: NATIONAL_DIGITAL }), { limit })));
stations = stations.sort((left, right) => {
if (!left.streams || !right.streams) {
return left.streams ? -1 : right.streams ? 1 : 0;
}
const leftScore = left.streams.hls_stream ? 2 :
left.streams.shoutcast_stream ? 1 :
0;
const rightScore = right.streams.hls_stream ? 2 :
right.streams.shoutcast_stream ? 1 :
0;
return rightScore - leftScore;
});
if (options.preferredStation) {
const station = stations.find(station => station.callLetters === options.preferredStation);
if (station) {
stations = stations.sort((left, right) => {
const leftScore = left === station ? 1 : 0;
const rightScore = right === station ? 1 : 0;
return rightScore - leftScore;
});
}
else {
const gottenStations = yield this._promises.wrap(this._getLiveStations({
callLetters: options.preferredStation,
}));
if (gottenStations && gottenStations.length) {
stations = [gottenStations[0], ...stations.slice(0, limit - 1)];
}
}
}
stations.forEach((station) => {
this._stationCache[station.callLetters] = station;
});
return stations.map((station) => {
return {
band: station.band,
callLetters: station.callLetters,
description: station.description,
frequency: station.freq,
logoUrl: station.logo,
name: station.name,
website: station.website,
};
});
});
}
play(callLetters) {
return __awaiter(this, void 0, void 0, function* () {
this._iHeartLog.info(`Playing ${callLetters}`);
yield this._initPromise;
if (this._initError) {
throw this._initError;
}
const station = this._stationCache[callLetters];
if (station) {
this._station = station;
}
else {
const stations = yield this._promises.wrap(this._getLiveStations({
callLetters,
limit: 1,
}));
if (!stations.length) {
throw new Error(`Station ${callLetters} not found`);
}
this._station =
this._stationCache[this._station.callLetters] =
stations[0];
}
return this._promises.wrap(this._streamStation());
});
}
resizeArtwork(uri, width) {
const normalized = Math.min(width, 1920);
return `http://img.iheart.com/sca/imscale?w=${normalized}&img=${uri}`;
}
destroy() {
super.destroy();
this._initError = null;
this._initPromise = null;
this._locationConfigError = null;
this._locationConfigPromise = null;
this._session = null;
this._station = null;
this._stationCache = null;
}
_processHLSMetadata(titleField) {
if (titleField.match(/comment=".*"/)
|| titleField.match(/text=\\?"Spot Block\\?"/)
|| titleField.match(/text=\\?"Spot Block End\\?"/)) {
return { mediaType: RadioPlayer_1.MediaType.Talk };
}
const mediaTypeMatch = titleField.match(/song_spot=\\"([MTO])\\"/);
const mediaType = IHeartPlayer._getMediaType(mediaTypeMatch && mediaTypeMatch[1]);
const titleMatch = titleField.match(/title="(.*?)"/);
const title = titleMatch && titleMatch[1];
const artistMatch = titleField.match(/artist="(.*?)"/);
const artistRaw = artistMatch && artistMatch[1];
const artist = artistRaw ? artistRaw.split('|')[0].trim() : null;
const lengthMatch = titleField.match(/length=\\"([0-9:]+?)\\"/);
const length = lengthMatch && lengthMatch[1];
const artworkUrlMatch = titleField.match(/amgArtworkURL=\\"(.*?)\\"/);
const artworkUrlRaw = artworkUrlMatch && artworkUrlMatch[1];
const artworkUrl = !artworkUrlRaw || artworkUrlRaw.includes('imscale')
? null
: artworkUrlRaw;
return { mediaType, artist, title, length, artworkUrl };
}
_processShoutcastMetadata(metadata) {
this._iHeartLog.debug('Processing Shoutcast metadata', metadata);
if (!metadata.StreamTitle) {
return {};
}
const [artist, rawTitle] = metadata.StreamTitle.split(' - ');
const title = rawTitle === 'text' ? '' : rawTitle;
return { artist, title };
}
_handleStreamError(data) {
const streams = this._station.streams;
if (this._mode === RadioPlayer_1.Mode.HLS && streams.shoutcast_stream) {
this._iHeartLog.info('HLS stream failed; trying Shoutcast');
this._streamShoutcast(streams.shoutcast_stream, true);
}
else if ((this._mode === RadioPlayer_1.Mode.HLS || this._mode === RadioPlayer_1.Mode.Shoutcast)
&& streams.pls_stream) {
this._iHeartLog.info('HLS and Shoutcast failed; trying PLS');
this._streamPLS(streams.pls_stream, true);
}
else {
this._iHeartLog.info('All streams failed; emitting error');
super._handleStreamError(data);
}
}
_streamStarted() {
if (!this._session) {
this._iHeartLog.info('No active session when trying to report start of stream');
return;
}
const params = new URLSearchParams();
params.append('profileId', this._session.profileId.toString());
params.append('sessionId', this._session.sessionId);
params.append('playedFrom', '300');
params.append('host', this._iHeartHostname);
params.append('parentId', this._station.id.toString());
try {
this._promises.wrap(axios_1.default.post(this._getURI(ENDPOINTS.reportStreamStarted), params));
}
catch (err) {
this._iHeartLog.info('Error reporting start of stream', err);
}
}
_init() {
return __awaiter(this, void 0, void 0, function* () {
yield this._locationConfigPromise;
if (this._locationConfigError) {
throw this._locationConfigError;
}
yield this._promises.wrap(this._login());
});
}
_getLocation() {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this._promises.wrap(axios_1.default.get(GET_LOCATION_URI));
this._countryCode = result.data.countryCode.toLowerCase();
this._iHeartBaseURI = result.data.config.apiUrl;
this._iHeartHostname = result.data.config.hostName;
this._iHeartTerminal = result.data.config.terminalId;
});
}
_getURI(endpoint) {
return `${this._iHeartBaseURI}/api/${endpoint}`;
}
_login() {
return __awaiter(this, void 0, void 0, function* () {
const params = new URLSearchParams();
params.append('accessToken', 'anon');
params.append('accessTokenType', 'anon');
params.append('deviceId', this._deviceId);
params.append('deviceName', `anon${this._deviceId}`);
params.append('host', this._iHeartHostname);
params.append('oauthUuid', this._deviceId);
params.append('oauthoverride', 'false');
this._session = yield this._promises.wrap(this._post(ENDPOINTS.login, params));
this._iHeartLog.debug('login completed', this._session);
});
}
_get(endpoint, options = undefined) {
return __awaiter(this, void 0, void 0, function* () {
options = Object.assign({}, options);
options.headers = Object.assign({}, options.headers, { 'X-hostName': this._iHeartHostname });
const result = yield axios_1.default.get(this._getURI(endpoint), Object.assign({}, DEFAULT_OPTIONS, options));
return result.data;
});
}
_getHits(endpoint, options = undefined) {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this._promises.wrap(this._get(endpoint, options));
return result.hits;
});
}
_post(endpoint, data = undefined, options = undefined) {
return __awaiter(this, void 0, void 0, function* () {
options = Object.assign({}, options);
options.headers = Object.assign({}, options.headers, { 'X-hostName': this._iHeartHostname });
const result = yield this._promises.wrap(axios_1.default.post(this._getURI(endpoint), data, Object.assign({}, DEFAULT_OPTIONS, options)));
return result.data;
});
}
_getLiveStations(params) {
return this._promises.wrap(this._getHits(ENDPOINTS.liveStations, { params }));
}
_getSongData(params) {
return this._promises.wrap(this._get(ENDPOINTS.metadata, { params }));
}
_streamStation() {
return __awaiter(this, void 0, void 0, function* () {
const streams = this._station.streams;
if (streams.secure_hls_stream) {
return this._promises.wrap(this._iHeartStreamHLS(streams.secure_hls_stream));
}
else if (streams.hls_stream) {
return this._promises.wrap(this._iHeartStreamHLS(streams.hls_stream));
}
else if (streams.shoutcast_stream) {
return this._promises.wrap(this._streamShoutcast(streams.shoutcast_stream));
}
else if (streams.pls_stream) {
return this._promises.wrap(this._streamPLS(streams.pls_stream));
}
throw new Error('No compatible stream found');
});
}
_iHeartStreamHLS(stream) {
return __awaiter(this, void 0, void 0, function* () {
const qs = querystring.stringify({
iheartradioversion: CLIENT_VERSION,
Deviceid: this._deviceId,
clienttype: DEVICE_NAME,
osVersion: this._releaseVersion,
devicename: DEVICE_NAME,
callLetters: this._station.callLetters,
Streamid: this._station.id,
terminalid: this._iHeartTerminal,
init_id: INIT_ID,
fb_broadcast: 0,
at: 0,
pname: DEVICE_NAME,
listenerId: this._deviceId,
amsparams: `playerid:${PLAYER};skey=${new Date().toISOString()}`,
});
const uri = `${stream}?${qs}`;
return this._promises.wrap(this._streamHLS(uri, HLS_CONFIG));
});
}
}
exports.default = IHeartPlayer;
},{"../RadioPlayer":1,"./IHeartInterfaces":2,"axios":undefined,"querystring":undefined}],4:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const IHeartPlayer_1 = require("./IHeartPlayer");
exports.IHeartPlayer = IHeartPlayer_1.default;
},{"./IHeartPlayer":3}],5:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const iHeart_1 = require("./iHeart");
const RadioPlayer_1 = require("./RadioPlayer");
exports.RadioPlayer = RadioPlayer_1.default;
exports.Locality = RadioPlayer_1.Locality;
exports.MediaType = RadioPlayer_1.MediaType;
function createRadio() {
return new iHeart_1.IHeartPlayer();
}
exports.createRadio = createRadio;
},{"./RadioPlayer":1,"./iHeart":4}]},{},[5])(5)
});
//# sourceMappingURL=jibo-radio.js.map