// SomaFM Radio plugin for Lampa by @tsynik & @usmanec // https://somafm.com/channels.json // https://github.com/rainner/soma-fm-player // https://codeberg.org/cuschk/somafm (function () { 'use strict'; var API_URL = 'https://somafm.com/channels.json'; // var GENRES_URL = 'https://tsynik.github.io/lampa/genres.json'; var IMG_BG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAADUlEQVR42gECAP3/AAAAAgABUyucMAAAAABJRU5ErkJggg=='; // black var LOGO = ''; var useAAC = Lampa.Storage.field('somafm_use_aac'); var PREFERRED_STREAMS = (useAAC) ? [ // 320k MP3 { urlRegex: /320\.pls$/, format: 'mp3' }, // 256k MP3 { urlRegex: /256\.pls$/, format: 'mp3' }, // 128k AAC { quality: 'highest', format: 'aac' }, // 128k MP3 { quality: 'highest', format: 'mp3' }, // 64k AAC { quality: 'high', format: 'aacp' }, // 32k AAC { quality: 'low', format: 'aacp' } ] : [ // 320k MP3 { urlRegex: /320\.pls$/, format: 'mp3' }, // 256k MP3 { urlRegex: /256\.pls$/, format: 'mp3' }, // 128k MP3 { quality: 'highest', format: 'mp3' } ]; var powtwo = 1024; // power of 2 value var _context = null; var _audio = null; var _source = null; var _gain = null; var _analyser = null; var _freq = new Uint8Array(powtwo); var _hasfreq = false; var _counter = 0; var _events = {}; var _component; var played = false; var changeWave = function () { }; // setup audio routing, called after user interaction, setup once function setupAudio() { if (_audio && _context) return; _audio = new Audio(); _context = new (window.AudioContext || window.webkitAudioContext)(); _source = _context.createMediaElementSource(_audio); _analyser = _context.createAnalyser(); _gain = _context.createGain(); _analyser.fftSize = powtwo; _source.connect(_analyser); _source.connect(_gain); _gain.connect(_context.destination); _audio.addEventListener('canplay', function (e) { console.log('SomaFM', 'got canplay'); _freq = new Uint8Array(_analyser.frequencyBinCount); _audio.play(); }); // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio#events ['play', 'waiting', 'playing', 'ended', 'stalled', 'suspend'].forEach(function (event) { _audio.addEventListener(event, function (e) { return emit(event, e); }); }); } // emit saved audio event function emit(event, data) { if (event && _events.hasOwnProperty(event)) { console.log('SomaFM', 'emit', event); _events[event].map(function (fn) { fn(data) }); } } // add event listeners to the audio api function on(event, callback) { if (event && typeof callback === 'function') { if (!_events[event]) _events[event] = []; _events[event].push(callback); } } // stop playing audio function stopAudio() { if (!_audio) return; try { _audio.pause(); } catch (e) { } try { _audio.stop(); } catch (e) { } try { _audio.close(); } catch (e) { } } // set audio volume function setVolume(volume) { if (!_gain) return; volume = parseFloat(volume) || 0; volume = volume > 1 ? volume / 100 : volume; volume = volume > 1 ? 1 : volume; volume = volume < 0 ? 0 : volume; _audio.muted = volume <= 0 ? true : false; _gain.gain.value = volume; } // update and return analyser frequency value [0-1] to control animations function getFreqData(playing) { if (!_analyser) return 0; // this is not working on some devices running safari _analyser.getByteFrequencyData(_freq); var freq = Math.floor(_freq[4] | 0) / 255; // indicate that a freq value can be read if (!_hasfreq && freq) { _hasfreq = true; } // frequency data available if (_hasfreq) return freq; // return fake counter if no freq data available (safari workaround) if (played) { _counter = _counter < .6 ? _counter + .01 : _counter; } else { _counter = _counter > 0 ? _counter - .01 : _counter; } return _counter; } // parse pls INI function parseINIString(data) { var regex = { section: /^\s*\[\s*([^\]]*)\s*\]\s*$/, param: /^\s*([^=]+?)\s*=\s*(.*?)\s*$/, comment: /^\s*;.*$/ }; var value = {}; var lines = data.split(/[\r\n]+/); var section = null; lines.forEach(function (line) { if (regex.comment.test(line)) { return; } else if (regex.param.test(line)) { var match = line.match(regex.param); if (section) { value[section][match[1]] = match[2]; } else { value[match[1]] = match[2]; } } else if (regex.section.test(line)) { var match = line.match(regex.section); value[match[1]] = {}; section = match[1]; } else if (line.length == 0 && section) { section = null; }; }); return value; } // parse channels list from api response function parseChannels(channels) { var output = []; if (Array.isArray(channels)) { for (var key in channels) { var channel = channels[key]; if (!Array.isArray(channel.playlists)) continue; var streamHighestQuality = getHighestQualityStream(channel, PREFERRED_STREAMS); channel.stream = streamHighestQuality; channel.stream.urls = getStreamUrls(channel); // channel.plsfile = 'https://api.somafm.com/' + channel.id + '.pls'; // channel.mp3file = 'https://ice1.somafm.com/' + channel.id + '-128-mp3'; // channel.aacfile = 'https://ice1.somafm.com/' + channel.id + '-128-aac'; channel.songsurl = 'https://somafm.com/songs/' + channel.id + '.json'; channel.infourl = 'https://somafm.com/' + channel.id + '/'; channel.twitter = channel.twitter ? 'https://twitter.com/@' + channel.twitter : ''; // channel.route = '/channel/' + channel.id; channel.listeners = channel.listeners | 0; channel.updated = channel.updated | 0; channel.favorite = false; channel.active = false; // channel.genre = channel.genre.replace(/\|/g, ' ● '); var genres = channel.genre.split("|"); if (genres.length > 0) { genres = genres.map(function (x) { return x.charAt(0).toUpperCase() + x.slice(1); }); channel.genre = genres.join(' ● '); } output.push(channel); } } return output; } // fetch songs for a channel function fetchSongs(channel, callback) { var apiurl = channel.songsurl || ''; var title = channel.title || '...'; var error = 'There was a problem loading the list of songs for channel ' + title + ' from SomaFM.'; var network = new Lampa.Reguest(); network.timeout(5000) network.native(apiurl, function (result) { if (!result.songs) return callback(error, []); return callback(null, result.songs); }, function () { return callback(error, []) }) } function getHighestQualityStream(channel, streams) { for (var ks in streams) { var stream = streams[ks]; for (var kp in channel.playlists) { var playlist = channel.playlists[kp]; if ( (!stream.urlRegex || stream.urlRegex.test(playlist.url)) && (!stream.quality || playlist.quality === stream.quality) && (!stream.format || playlist.format === stream.format) ) { return { url: playlist.url, format: playlist.format, quality: playlist.quality }; } } } return null; } function getChannelById(id, channels) { return new Promise(function (resolve, reject) { if (id === 'random') { resolve(channels[ Math.floor(channels.length * Math.random()) ]); } for (var key in channels) { var channel = channels[key]; if (id === channel.id) { resolve(channel); } } reject(new Error('Channel not found.')); }); } function getStreamUrls(channel) { if (channel.stream.urls) { return Promise.resolve(channel.stream.urls); } return getUrlsFromPlaylist(channel.stream.url); } function getUrlsFromPlaylist(playlistUrl) { return new Promise(function (resolve, reject) { var error = 'There was a problem parse urls from playlist ' + playlistUrl + ' from SomaFM.'; var network = new Lampa.Reguest(); network.timeout(5000); network.native(playlistUrl, function (response) { try { var data = parseINIString(response); // decode pls INI var result = []; for (var key in data.playlist) { if (key.match(/^File\d+$/)) result.push(data.playlist[key]); } !result.length ? reject(error) : resolve(result); } catch (e) { console.log('SomaFM', error, e.message); reject(e); } }, function () { }, false, { dataType: 'text' }); }); } function item(data) { var item = Lampa.Template.get('somafm_item', { id: data.id, name: data.title, listeners: data.listeners }); var img = item.find('img')[0]; img.onerror = function () { img.src = './img/img_broken.svg'; }; img.src = data.largeimage; // image - 120 | largeimage - 256 | xlimage 512 this.render = function () { return item; }; this.destroy = function () { img.onerror = function () { }; img.onload = function () { }; img.src = ''; item.remove(); }; } function random_item(items) { return items[Math.floor(Math.random() * items.length)]; } // https://stackoverflow.com/questions/7624920/number-sign-in-javascript function compareChannelObjects(ch1, ch2) { if (!Math.sign) Math.sign = function sign(x) { return typeof x === 'number' ? x ? x < 0 ? -1 : 1 : x === x ? 0 : NaN : NaN; } return Math.sign(ch2.listeners - ch1.listeners); } // TODO: use cached list function list() { return new Promise(function (resolve, reject) { var network = new Lampa.Reguest(); network.native(API_URL, function (result) { Lampa.Cache.rewriteData('other', 'somafm_list', result).finally(resolve.bind(resolve, result)) }, function () { Lampa.Cache.getData('other', 'somafm_list').then(resolve).catch(reject) }) }) } function Component() { var network = new Lampa.Reguest(); var scroll = new Lampa.Scroll({ mask: true, over: true, step: 250 }); var player = window.somafm_player; var items = []; var html = $('
'); var body = $('
'); var active; var last; _component = this; this.create = function () { var _this = this; this.activity.loader(true); var prox = Lampa.Platform.is('webos') || Lampa.Platform.is('tizen') || Lampa.Storage.field('proxy_other') === false ? '' : ''; network["native"](prox + API_URL, this.build.bind(this), function () { var empty = new Lampa.Empty(); html.append(empty.render()); _this.start = empty.start; _this.activity.loader(false); _this.activity.toggle(); }); return this.render(); }; this.build = function (data) { scroll.minus(); var stations = parseChannels(data.channels); var sortChannels = Lampa.Storage.field('somafm_sort_stations'); // TODO: add sorting options if (sortChannels) // sort by popularity stations = stations.sort(compareChannelObjects) else stations = stations.sort(function (a, b) { return a.sort - b.sort; }); this.append(stations); scroll.append(body); html.append(scroll.render()); this.activity.loader(false); this.activity.toggle(); }; this.append = function (element) { element.forEach(function (station) { var item$1 = new item(station); item$1.render().on('hover:focus', function () { last = item$1.render()[0]; active = items.indexOf(item$1); scroll.update(items[active].render(), true); }).on('hover:enter', function () { player.play(station); }); body.append(item$1.render()); items.push(item$1); }); }; this.back = function () { Lampa.Activity.backward(); }; this.background = function () { Lampa.Background.immediately(IMG_BG); }; this.start = function () { if (Lampa.Activity.active().activity !== this.activity) return; this.background(); Lampa.Controller.add('content', { toggle: function toggle() { Lampa.Controller.collectionSet(scroll.render()); Lampa.Controller.collectionFocus(last || false, scroll.render()); }, left: function left() { if (Navigator.canmove('left')) Navigator.move('left'); else Lampa.Controller.toggle('menu'); }, right: function right() { Navigator.move('right'); }, up: function up() { if (Navigator.canmove('up')) Navigator.move('up'); else Lampa.Controller.toggle('head'); }, down: function down() { if (Navigator.canmove('down')) Navigator.move('down'); }, back: this.back }); Lampa.Controller.toggle('content'); }; this.pause = function () { }; this.stop = function () { }; this.render = function () { return html; }; this.destroy = function () { network.clear(); Lampa.Arrays.destroy(items); scroll.destroy(); html.remove(); items = null; network = null; }; } var levenshtein = (function () { function _min(d0, d1, d2, bx, ay) { return d0 < d1 || d2 < d1 ? d0 > d2 ? d2 + 1 : d0 + 1 : bx === ay ? d1 : d1 + 1; } return function (a, b) { if (a === b) { return 0; } if (a.length > b.length) { var tmp = a; a = b; b = tmp; } var la = a.length; var lb = b.length; while (la > 0 && (a.charCodeAt(la - 1) === b.charCodeAt(lb - 1))) { la--; lb--; } var offset = 0; while (offset < la && (a.charCodeAt(offset) === b.charCodeAt(offset))) { offset++; } la -= offset; lb -= offset; if (la === 0 || lb < 3) { return lb; } var x = 0; var y; var d0; var d1; var d2; var d3; var dd; var dy; var ay; var bx0; var bx1; var bx2; var bx3; var vector = []; for (y = 0; y < la; y++) { vector.push(y + 1); vector.push(a.charCodeAt(offset + y)); } var len = vector.length - 1; for (; x < lb - 3;) { bx0 = b.charCodeAt(offset + (d0 = x)); bx1 = b.charCodeAt(offset + (d1 = x + 1)); bx2 = b.charCodeAt(offset + (d2 = x + 2)); bx3 = b.charCodeAt(offset + (d3 = x + 3)); dd = (x += 4); for (y = 0; y < len; y += 2) { dy = vector[y]; ay = vector[y + 1]; d0 = _min(dy, d0, d1, bx0, ay); d1 = _min(d0, d1, d2, bx1, ay); d2 = _min(d1, d2, d3, bx2, ay); dd = _min(d2, d3, dd, bx3, ay); vector[y] = dd; d3 = d2; d2 = d1; d1 = d0; d0 = dy; } } for (; x < lb;) { bx0 = b.charCodeAt(offset + (d0 = x)); dd = ++x; for (y = 0; y < len; y += 2) { dy = vector[y]; vector[y] = dd = _min(dy, d0, dd, bx0, vector[y + 1]); d0 = dy; } } return dd; }; })(); var noCoverTitle = []; var albumCoverCache = {}; // https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/iTuneSearchAPI/Searching.html#//apple_ref/doc/uid/TP40017632-CH5-SW1 function getTrackCover(title, album, callback) { var albumHash = Lampa.Utils.hash(album); var setTrackCover = callback || function () { }; if (albumHash && albumCoverCache[albumHash]) { setTrackCover(albumCoverCache[albumHash]); return; } var network = new Lampa.Reguest(); var regex = /[\s.,{}\-\\\/()\[\]:;'"!@#$%^&*]+/g; // punctuation and spaces if (noCoverTitle.indexOf(title) < 0) { var request = 'https://itunes.apple.com/search?term=' + encodeURIComponent(title) + '&media=music&entity=song'; network.native( request, function (data) { var bigCover = false; if (!data || !data['resultCount'] || !data['results'] || !data['results'].length) { if (data !== false) { noCoverTitle.push(title); } } var albumLC = album.toLowerCase().replace(regex, ""); var filtered = data['results'].filter(function (r) { r.collectionNameLC = r.collectionName ? r.collectionName.toLowerCase().replace(regex, "") : ''; return r.collectionNameLC && (r.collectionNameLC.indexOf(albumLC) >= 0 || albumLC.indexOf(r.collectionNameLC) >= 0 ); }); // console.log('SomaFM', 'getTrackCover request:', request, 'data resultCount', data['resultCount'], "filtered", filtered.length); if (!filtered.length) { var accuracyPercent = 60; // Допустимая погрешность % var accuracyMaxLen = albumLC.length * accuracyPercent / 100; filtered = data['results'].filter(function (r) { r.levenshtein = levenshtein(r.collectionNameLC, albumLC); return r.levenshtein <= accuracyMaxLen; }).sort(function (a, b) { return a.levenshtein - b.levenshtein }); // if (filtered.length) // console.log('SomaFM', 'getTrackCover levenshtein:', '"' + albumLC + '"', 'accuracyPercent', accuracyPercent, "filtered", filtered.length, "min", filtered[0].levenshtein, "max", filtered[filtered.length - 1].levenshtein) // else // console.log('SomaFM', 'getTrackCover levenshtein:', '"' + albumLC + '"', 'accuracyPercent', accuracyPercent, "filtered", 0); } if (!filtered.length || !filtered[0]['artworkUrl100']) { noCoverTitle.push(title); } else { bigCover = filtered[0]['artworkUrl100'].replace('100x100bb.jpg', '500x500bb.jpg'); // увеличиваем разрешение albumCoverCache[albumHash] = bigCover; } setTrackCover(bigCover) }, function () { setTrackCover(false) } ); } else { setTrackCover(false); } } function Info(station) { var info_html = Lampa.Template.js('somafm_info'); var currTrack = {}; var lastSongs = []; var img_elm; var songsupdate; var showAnalyzer = Lampa.Storage.field('somafm_show_analyzer'); var canvas = info_html.find("canvas"); if (showAnalyzer && canvas) { canvas.width = window.innerWidth; canvas.height = window.innerHeight; var ctx = canvas.getContext("2d"); var bufferLength = _analyser.frequencyBinCount; //var dataArray = new Uint8Array(bufferLength); var WIDTH = canvas.width; var HEIGHT = canvas.height; var barWidth = (WIDTH / bufferLength) * 2.5; var barHeight; var x = 0; // https://wesbos.com/javascript/15-final-round-of-exercise/85-audio-visualization function renderFrame() { // get data getFreqData(played) //_analyser.getByteFrequencyData(dataArray); // clear draw ctx.clearRect(0, 0, canvas.width, canvas.height); x = 0; for (var i = 0; i < bufferLength; i++) { barHeight = _freq[i] * 2; // var r = barHeight + (25 * (i/bufferLength)); // var g = 250 * (i/bufferLength); // var b = 50; // var opacity = 0.75; var r = 255; var g = 255; var b = 255; var opacity = _freq[i] / 510 // 0 to 0.5, data = [0 to 255] ctx.fillStyle = "rgba(" + r + "," + g + "," + b + "," + opacity + ")"; ctx.fillRect(x, HEIGHT - barHeight, barWidth, barHeight); x += barWidth + 4; } requestAnimationFrame(renderFrame); } renderFrame(); } if (songsupdate) { clearInterval(songsupdate); songsupdate = null; } // getSongs(station); // no delay on show info // Playing Info update task songsupdate = setInterval(function () { getSongs(station); }, 5000); // songs update internal // get songs list for a channel from api function getSongs(channel) { if (!channel || !channel.id || !channel.songsurl) return; fetchSongs(channel, function (err, songs) { var size = Object.keys(songs).length; if (!err && size > 0 && (!currTrack.date || (songs[0].date && currTrack.date !== songs[0].date)) ) { currTrack = songs.shift(); lastSongs = songs.slice(0, 3); updatePlayingInfo(currTrack); } }); } function setTrackCover(cover) { img_elm.src = cover || station.largeimage; // image - 120 | largeimage - 256 | xlimage 512 Lampa.Background.immediately(img_elm.src); } function updatePlayingInfo(playingTrack) { var fetchCovers = Lampa.Storage.field('somafm_fetch_covers'); if (playingTrack.title) info_html.find('.somafm-cover__title').text(playingTrack.title); // TODO: use playlist for lastSongs // info_html.find('.somafm-cover__playlist').text(playlist); var album_cont = info_html.find('.somafm-cover__album'); var album_info = album_cont.find('span'); album_info.text(playingTrack.album || ''); var album_svg = album_cont.find('svg'); playingTrack.album ? album_svg.style.width = "1em" : album_svg.style.width = "0em"; info_html.find('.somafm-cover__title').text(playingTrack.title || ''); info_html.find('.somafm-cover__tooltip').text(playingTrack.artist || ''); var albumart = playingTrack.albumart; if (albumart) setTrackCover(albumart); else if (fetchCovers) getTrackCover(playingTrack.artist + " - " + playingTrack.title, playingTrack.album, setTrackCover); } changeWave = function (class_name) { var lines = info_html.find('.somafm-info__wave').querySelectorAll('div'); for (var i = 0; i < lines.length; i++) { lines[i].removeClass('play loading').addClass(class_name); lines[i].style['animation-duration'] = (class_name == 'loading' ? 400 : 200 + Math.random() * 200) + 'ms'; lines[i].style['animation-delay'] = (class_name == 'loading' ? Math.round(400 / lines.length * i) : 0) + 'ms'; } } function createWave() { var box = info_html.find('.somafm-info__wave'); for (var i = 0; i < 15; i++) { var div = document.createElement('div'); box.append(div); } changeWave(played ? 'play' : 'loading'); } on("playing", function () { changeWave('play'); }); on("waiting", function () { changeWave('loading'); }); this.create = function () { var cover = Lampa.Template.js('somafm_cover'); cover.find('.somafm-cover__station').text(station.title || ''); cover.find('.somafm-cover__genre').text(station.genre || ''); cover.find('.somafm-cover__tooltip').text(station.description || ''); cover.find('.somafm-cover__album span').text(station.dj ? 'DJ – ' + station.dj : ''); var img_box = cover.find('.somafm-cover__img-box'); img_box.removeClass('loaded loaded-icon'); img_elm = img_box.find('img'); img_elm.onload = function () { img_box.addClass('loaded'); }; img_elm.onerror = function () { img_elm.src = './img/icons/menu/movie.svg'; img_box.addClass('loaded-icon'); }; img_elm.src = station.largeimage; // image - 120 | largeimage - 256 | xlimage 512 info_html.find('.somafm-info__cover').append(cover); info_html.find('.somafm-info__close').on('click', function () { window.history.back(); }); document.body.append(info_html); createWave(); }; this.destroy = function () { info_html.remove(); clearInterval(songsupdate); songsupdate = null; // release songs update timer currTrack = {}; lastSongs = []; }; } function Player() { var player_html = Lampa.Template.get('somafm_player', {}); var url = ''; var format = ''; var hls; var screenreset; var info; setupAudio(); function prepare() { if (_audio.canPlayType('audio/vnd.apple.mpegurl')) load(); else if (Hls.isSupported() && format == "aacp") { try { hls = new Hls(); hls.attachMedia(_audio); hls.loadSource(url); hls.on(Hls.Events.ERROR, function (event, data) { if (data.details === Hls.ErrorDetails.MANIFEST_PARSING_ERROR) { if (data.reason === "no EXTM3U delimiter") { Lampa.Noty.show(Lampa.Lang.translate('somafm_error')); } } }); hls.on(Hls.Events.MANIFEST_LOADED, function () { start(); }); } catch (e) { Lampa.Noty.show(Lampa.Lang.translate('somafm_error')); } } else load(); } function load() { _audio.src = url; _audio.preload = 'metadata'; _audio.crossOrigin = 'anonymous'; _audio.autoplay = false; _audio.load(); start(); } function start() { var playPromise; try { playPromise = _audio.play(); } catch (e) { } if (playPromise !== undefined) { playPromise.then(function () { console.log('SomaFM', 'start playing', url); })["catch"](function (e) { console.log('SomaFM', 'play promise error:', e.message); }); } } function play() { // stopAudio(); if (_context.state === 'suspended') { _context.resume().then(function () { console.log('SomaFM', 'Audio context has been resumed.'); }); } player_html.toggleClass('loading', true); player_html.toggleClass('stop', false); prepare(); } function stop() { // stopAudio(); clearInterval(screenreset); screenreset = null; // release timer from the variable played = false; player_html.toggleClass('stop', true); player_html.toggleClass('loading', false); if (hls) { hls.destroy(); hls = false; } _audio.src = ''; // remove info if (info) { info.destroy(); info = false; } } // handle audio stream state changes on("play", function () { played = true; }); on("playing", function () { player_html.toggleClass('loading', false); if (!screenreset) { screenreset = setInterval(function () { Lampa.Screensaver.resetTimer(); }, 5000); } }); on("waiting", function () { player_html.toggleClass('loading', true); }); // handle player button click player_html.on('hover:enter', function () { if (played) stop(); else if (url) play(); }); this.create = function () { $('.head__actions .open--search').before(player_html); }; var curPlayID = null; this.play = function (station) { // todo player.destroy() start if (window.currentPlayer && window.currentPlayer !== this && window.currentPlayer.destroy) { window.currentPlayer.destroy(); } window.currentPlayer = this; // todo player.destroy() end if (curPlayID !== station.id || !played) stop(); // add info if (Lampa.Storage.field('somafm_show_info') === true) { info = new Info(station); info.create(); Lampa.Controller.add('content', { invisible: true, toggle: function toggle() { Lampa.Controller.clear(); document.body.addClass('ambience--enable'); Lampa.Background.change(station.largeimage || IMG_BG); // image - 120 | largeimage - 256 | xlimage 512 }, back: function back() { document.body.removeClass('ambience--enable'); // player.destroy(); if (info) { info.destroy(); info = false; } if (_component) _component.activity.toggle(); Lampa.Controller.toggle('content'); }, }); Lampa.Controller.toggle('content'); } // url = data.aacfile ? data.aacfile : data.mp3file; if (curPlayID !== station.id || !played) { Promise.resolve(station.stream.urls).then(function (urls) { if (urls.length > 0) { url = random_item(urls); play(); } }); curPlayID = station.id; } // setup player button player_html.find('.somafm-player__name').text(station.title); player_html.toggleClass('hide', false); var btn = player_html.find('.somafm-player__button'); if (btn) { btn.css({ "background-image": "url('" + station.largeimage + "')", // image - 120 | largeimage - 256 | xlimage 512 "background-size": "cover" }); } }; this.destroy = function () { // todo player.destroy() stop(); player_html.toggleClass('hide', true); curPlayID = null; // todo player.destroy() } } function add() { var icon = '' var menu_button = $("
  • \n\t
    \n\t\t" + icon + "\n\t
    \n\t
    Soma FM
    \n
  • \n"); menu_button.on('hover:enter', function () { Lampa.Activity.push({ url: '', title: Lampa.Lang.translate('somafm_title'), component: 'somafm', page: 1 }); }); $('.menu .menu__list').eq(0).append(menu_button); $('body').append(Lampa.Template.get('somafm_style', {}, true)); window.somafm_player = new Player(); window.somafm_player.create(); addSettings(); } function addSettings() { if (window.somafm_add_param_ready) return; window.somafm_add_param_ready = true; Lampa.SettingsApi.addComponent({ component: 'somafm', name: 'Soma FM', icon: '' }); Lampa.SettingsApi.addParam({ component: 'somafm', param: { name: 'somafm_use_aac', type: 'trigger', "default": false }, field: { name: Lampa.Lang.translate('somafm_use_aac_title'), description: Lampa.Lang.translate('somafm_use_aac_desc') }, onRender: function onRender(item) { } }); Lampa.SettingsApi.addParam({ component: 'somafm', param: { name: 'somafm_show_info', type: 'trigger', "default": true }, field: { name: Lampa.Lang.translate('somafm_show_info_title'), description: Lampa.Lang.translate('somafm_show_info_desc') }, onRender: function onRender(item) { } }); Lampa.SettingsApi.addParam({ component: 'somafm', param: { name: 'somafm_sort_stations', type: 'trigger', "default": true }, field: { name: Lampa.Lang.translate('somafm_sort_stations_title'), description: Lampa.Lang.translate('somafm_sort_stations_desc') }, onRender: function onRender(item) { } }); Lampa.SettingsApi.addParam({ component: 'somafm', param: { name: 'somafm_fetch_covers', type: 'trigger', "default": true }, field: { name: Lampa.Lang.translate('somafm_fetch_covers_title'), description: Lampa.Lang.translate('somafm_fetch_covers_desc') }, onRender: function onRender(item) { } }); Lampa.SettingsApi.addParam({ component: 'somafm', param: { name: 'somafm_show_analyzer', type: 'trigger', "default": false }, field: { name: Lampa.Lang.translate('somafm_show_analyzer_title'), description: Lampa.Lang.translate('somafm_show_analyzer_desc') }, onRender: function onRender(item) { } }); } function createSomaFM() { window.plugin_somafm_ready = true; Lampa.Lang.add({ somafm_title: { ru: "Радио SomaFM", en: "SomaFM Radio", uk: "Радіо SomaFM", be: "Радыё SomaFM", zh: "SomaFM 电台", pt: "Rádio SomFM", bg: "SomaFM радио", he: "רדיו SomaFM" }, somafm_use_aac_title: { ru: "Предпочтение AAC", en: "Use AAC streams", uk: "Перевага AAC", be: "Перавага AAC", zh: "AAC 偏好", pt: "Preferência AAC", bg: "AAC предпочитание", he: "העדפת AAC" }, somafm_use_aac_desc: { ru: "Использовать AAC-потоки при доступности", en: "Prefer AAC streams if available", uk: "Віддавати перевагу потокам AAC, якщо вони доступні", be: "Аддавайце перавагу патокам AAC, калі яны даступныя", zh: "优先选择 AAC 流(如果可用)", pt: "Prefira streams AAC, se disponíveis", bg: "Предпочитайте AAC потоци, ако има такива", he: "העדיפו זרמי AAC אם זמינים" }, somafm_show_info_title: { ru: "Показывать информацию", en: "Show Info screen", uk: "Показати екран інформації", be: "Паказаць экран інфармацыі", zh: "显示信息屏幕", pt: "Mostrar tela de informações", bg: "Показване на екрана с информация", he: "הצג מסך מידע" }, somafm_show_info_desc: { ru: "Открывать информацию о станции при выборе", en: "Show Playing Info screen on select", uk: "Показати екран інформації про відтворення на вибраному", be: "Паказаць экран Інфармацыя аб прайграванні пры выбары", zh: "选择时显示播放信息屏幕", pt: "Mostrar tela de informações de jogo ao selecionar", bg: "Показване на екрана с информация за възпроизвеждане при избор", he: "הצג מידע על משחק בבחירה" }, somafm_sort_stations_title: { ru: "Сортировка по популярности", en: "Sort by Popularity", uk: "Сортувати за популярністю", be: "Сартаваць па папулярнасці", zh: "按受欢迎程度排序", pt: "Classificar por popularidade", bg: "Сортиране по популярност", he: "מיין לפי פופולריות" }, somafm_sort_stations_desc: { ru: "Сотрировать список по слушающим", en: "Sorting stations list by listeners", uk: "Сортування списку станцій за слухачами", be: "Сартаванне спісу станцый па слухачах", zh: "按听众对电台列表进行排序", pt: "Classificando lista de estações por ouvintes", bg: "Сортиране на списъка със станции по слушатели", he: "מיון רשימת תחנות לפי מאזינים" }, somafm_fetch_covers_title: { ru: "Получать обложки", en: "Fetch Music Covers", uk: "Отримати обкладинки", be: "Атрымаць вокладкі", zh: "获取音乐封面", pt: "Buscar capas de músicas", bg: "Извличане на обложки", he: "אחזר עטיפות מוזיקה" }, somafm_fetch_covers_desc: { ru: "Загружать обложки альбомов из Apple Music", en: "Search music covers on Apple Music", uk: "Пошук музичних обкладинок в Apple Music", be: "Пошук вокладак музыкі ў Apple Music", zh: "在 Apple Music 上搜索音乐封面", pt: "Pesquisando capas de músicas no Apple Music", bg: "Търсене на музикални обложки в Apple Music", he: "חיפוש עטיפות מוזיקה ב-Apple Music" }, somafm_show_analyzer_title: { ru: "Показать визуализатор", en: "Show visualizer", uk: "Показати візуалізатор", be: "Паказаць візуалізатар", zh: "显示可视化工具", pt: "Mostrar visualizador", bg: "Покажи визуализатор", he: "הצג מכשיר חזותי" }, somafm_show_analyzer_desc: { ru: "Анализатор аудиоспектра на заднем плане", en: "Audio spectrum analyzer on the background", uk: "Аналізатор аудіо спектру на тлі", be: "Аўдыё аналізатар спектру на фоне", zh: "背景上的音频频谱分析仪", pt: "Analisador de espectro de áudio em segundo plano", bg: "Аудио спектрален анализатор на заден план", he: "מנתח ספקטרום שמע ברקע" }, somafm_error: { ru: "Ошибка загрузки данных", en: "Error loading stations", uk: "Помилка завантаження станції", be: "Памылка загрузкі станцыі", zh: "错误加载站点", pt: "Erro ao carregar estação", bg: "Грешка при зареждане на станцията", he: "שגיאה בטעינת התחנה" } }); var manifest = { type: 'audio', version: '1.0.8', name: Lampa.Lang.translate('somafm_title'), description: 'Over 30 unique channels of listener-supported, commercial-free, underground/alternative radio broadcasting to the world. All music hand-picked by SomaFM`s award-winning DJs and music directors.', component: 'radio' }; Lampa.Manifest.plugins = manifest; Lampa.Template.add('somafm_item', "
    \n
    \n \n
    \n \n {listeners}\n
    \n
    \n
    {name}
    \n
    \n"); Lampa.Template.add('somafm_player', "
    \n
    Soma FM
    \n
    \n \n \n \n \n
    \n
    \n"); Lampa.Template.add('somafm_info', "
    \n \n
    \n
    \n
    \n
    \n
    \n \n \n \n
    \n
    \n"); Lampa.Template.add('somafm_cover', "
    \n
    \n
    \n
    \n
    \n \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n"); Lampa.Template.add('somafm_style', "\n"); Lampa.Component.add("somafm", Component); if (window.appready) { add(); } else { Lampa.Listener.follow("app", function (e) { if (e.type == "ready") add(); }); } } if (!window.plugin_somafm_ready) createSomaFM(); })();