(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 { 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