- plugins/: скачаны все 30 плагинов store локально (sources.json - карта исходных URL);
патчи: showy.js (абсолютный хост подгрузки), pubtorr/td/torrentmanager - тела вместо лоадеров,
modss.js - local-patched копия (из каталога исключена, upstream мёртв)
- extensions.json: 32 позиции, все ссылки {localhost}/plugins/<файл>.js, удалены мёртвые
(Skaz TV x3, export.js, hotkeys/gold_theme/select_weapon/want/mult, MODS's, o.js, др.)
- compose: ./plugins:/lampac/wwwroot/plugins (один маунт вместо 30 file-mount)
- вычистка: удалены неиспользуемые mods (DatabaseEditor, ExternalBind, LogUserRequest-Lite,
Telemetry, Transcoding, WatchTogether) и дубль каталога modules/
- config/init.conf: секции Tracks/AdminPanel/Music, customPlugins без gst.js
- mods/Tracks: правки Controller.cs/plugin.js
- README.md / AGENT.md: mirror, кеш static, правила mount
1200 lines
60 KiB
JavaScript
1200 lines
60 KiB
JavaScript
// 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 = '<svg enable-background="new 0 0 284 80.7" viewBox="0 0 284 80.7" xmlns="http://www.w3.org/2000/svg"><g fill="#ed1f24"><path d="m-474.8-759.5h14.7c2.3 0 4.3.7 6.1 2 1.8 1.4 2.6 2.7 2.6 4 0 .9-.3 1.7-1 2.3-.6.6-1.4.9-2.1.9-1 0-1.9-.5-2.8-1.4-.8-.9-1.7-1.4-2.6-1.4h-14.4c-1.4 0-2.1.6-2.1 1.7 0 .8.5 1.4 1.4 1.8l17 7.6c1.7.8 3.1 2 4.2 3.7s1.7 3.6 1.7 5.6c0 2.7-1 5.1-2.9 7s-4.1 2.9-6.6 2.9h-13c-2.2 0-4.3-.7-6.2-2-1.9-1.4-2.9-2.7-2.9-3.9 0-.8.3-1.6.9-2.2.6-.7 1.4-1 2.4-1 .8 0 1.7.5 2.6 1.4s2.3 1.4 4 1.4h11.9c.9 0 1.7-.4 2.4-1.1.6-.7 1-1.6 1-2.5 0-1.4-.7-2.5-2.1-3.1l-17.2-7.7c-1.7-.7-2.9-1.8-3.8-3.2s-1.3-2.8-1.3-4.3c0-2.3.8-4.3 2.4-6 1.4-1.6 3.4-2.5 5.7-2.5" transform="translate(509.62346 785.52008)"/><path d="m-474.8-752.4c-2.7-2.4-5.5-3.6-8.4-3.6h-7.5c-3.1 0-5.9 1.2-8.5 3.7s-3.9 5.1-3.9 7.9v13.5c0 2.8 1.3 5.4 4 7.9s5.5 3.7 8.4 3.7h7.5c2.9 0 5.7-1.2 8.4-3.7 2.7-2.4 4.1-5.1 4.1-8v-13.5c0-2.7-1.4-5.4-4.1-7.9m-20.6 5.8 2.2-1.9c.8-.7 1.9-1 3.3-1h6.6c1 0 1.9.3 2.7 1l2.2 1.8c.8.6 1.2 1.5 1.2 2.8v12.9c0 1.1-.4 1.9-1.2 2.6l-1.9 1.7c-.9.8-1.9 1.2-3 1.2h-7.4c-1 0-1.7-.2-2.2-.7l-2.4-2.1c-.8-.7-1.3-1.6-1.3-2.8v-12.9c.1-1.1.5-2 1.2-2.6" transform="translate(564.37836 781.97068)"/><path d="m-474.8-692.2v-30.4c0-.9.3-1.6.9-2.3.6-.6 1.4-.9 2.3-.9 1.7 0 2.7.8 3.2 2.5 1.8-1.7 3.7-2.5 5.9-2.5 2.6 0 4.7 1.1 6.3 3.3 2-2.2 4.3-3.3 6.7-3.3 2 0 3.7.7 5.1 2.1 1.5 1.4 2.2 3.2 2.3 5.4l1 26.1c0 .9-.3 1.6-1 2.2-.6.6-1.4.9-2.3.9s-1.7-.3-2.3-.9-1-1.4-1-2.2l-.8-25c-.1-1.4-.5-2.1-1.3-2.1-.4 0-.9.2-1.3.6l-3.8 3.2v23.4c0 .9-.3 1.6-.9 2.2s-1.4.9-2.3.9-1.6-.3-2.3-.9c-.6-.6-1-1.4-1-2.3v-25.4c0-1.1-.5-1.6-1.4-1.6-.5 0-.9.2-1.2.5l-4.6 3.9v22.7c0 .9-.3 1.6-.9 2.2s-1.4.9-2.2.9c-.9 0-1.7-.3-2.3-1-.4-.6-.8-1.4-.8-2.2" transform="translate(573.79246 751.91648)"/><path d="m-474.8-726.3c-1.9 1.9-2.9 4.2-2.9 6.8v3.7c0 2.7.9 5 2.8 6.9s4.2 2.8 6.9 2.8h10.8l5.4-3.2c0 .9.3 1.7 1 2.3.6.6 1.4.9 2.3.9s1.6-.3 2.3-.9c.6-.6 1-1.4 1-2.3l-.8-24c-.1-2.7-1.1-5-2.9-6.8-1.9-1.9-4.1-2.8-6.8-2.8h-12.2c-.9 0-1.7.3-2.3.9s-.9 1.4-.9 2.3.3 1.6 1 2.3c.6.6 1.4 1 2.3 1h12.2c.9 0 1.7.3 2.3.9s.9 1.4.9 2.3l.1 4.5c-1.3-.3-2.3-.4-3-.4h-12.6c-2.7 0-5 .9-6.9 2.8m15.9 13.6h-9c-.9 0-1.6-.3-2.3-.9-.6-.6-1-1.4-1-2.2v-3.5c0-.9.3-1.6.9-2.2s1.4-.9 2.3-.9h12.6c.9 0 1.6.3 2.2.9s.9 1.3 1 2.2l.2 2.6z" transform="translate(614.65666 768.93358)"/><path d="m-474.8-746.6v27.2c0 .9-.3 1.6-1 2.3-.6.6-1.4.9-2.3.9s-1.7-.3-2.3-.9-1-1.4-1-2.3v-27.2h-3.3c-.9 0-1.6-.3-2.2-1-.6-.6-.9-1.4-.9-2.3 0-.8.3-1.6.8-2.2.6-.7 1.3-1 2.3-1h3.3v-2.5c0-3.3 1.1-6 3.4-8.1 2.3-2.2 5.1-3.2 8.5-3.2h7.5c.9 0 1.7.3 2.3 1 .6.6.9 1.4.9 2.3s-.3 1.6-1 2.3c-.6.6-1.4 1-2.3 1h-7.5c-1.5 0-2.8.5-3.8 1.4s-1.5 2.1-1.5 3.5v2.5h6.4c.9 0 1.6.3 2.3.9.6.6 1 1.4 1 2.3s-.3 1.6-.9 2.3c-.6.6-1.4 1-2.2 1h-6.5z" transform="translate(680.67526 779.08838)"/><path d="m-474.8-692.2v-30.4c0-.9.3-1.6.9-2.3.6-.6 1.4-.9 2.3-.9 1.7 0 2.7.8 3.2 2.5 1.8-1.7 3.7-2.5 5.9-2.5 2.6 0 4.7 1.1 6.3 3.3 2-2.2 4.3-3.3 6.7-3.3 2 0 3.7.7 5.1 2.1 1.5 1.4 2.2 3.2 2.3 5.4l1 26.1c0 .9-.3 1.6-1 2.2-.6.6-1.4.9-2.3.9s-1.7-.3-2.3-.9-1-1.4-1-2.2l-.8-25c-.1-1.4-.5-2.1-1.3-2.1-.4 0-.9.2-1.3.6l-3.8 3.2v23.4c0 .9-.3 1.6-.9 2.2s-1.4.9-2.3.9-1.6-.3-2.3-.9c-.6-.6-1-1.4-1-2.3v-25.4c0-1.1-.5-1.6-1.4-1.6-.5 0-.9.2-1.2.5l-4.6 3.9v22.7c0 .9-.3 1.6-.9 2.2s-1.4.9-2.2.9c-.9 0-1.7-.3-2.3-1-.4-.6-.8-1.4-.8-2.2" transform="translate(694.63226 751.91648)"/></g></svg>';
|
||
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 = $('<div></div>');
|
||
var body = $('<div class="category-full"></div>');
|
||
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 = '<svg enable-background="new 0 0 533.3 377.1" viewBox="0 0 533.3 377.1" xmlns="http://www.w3.org/2000/svg"><path d="m266.7 121.9c36.8 0 66.7 29.8 66.7 66.7s-29.8 66.7-66.7 66.7-66.7-29.9-66.7-66.7 29.8-66.7 66.7-66.7zm-116.7 66.7c0 32.2 13.1 61.4 34.2 82.5l-35.4 35.4c-30.2-30.2-48.8-71.8-48.8-117.9 0-46 18.7-87.7 48.8-117.9l35.4 35.4c-21.1 21.1-34.2 50.2-34.2 82.5zm233.3 0c0-32.2-13.1-61.4-34.2-82.5l35.4-35.4c30.2 30.2 48.8 71.8 48.8 117.9 0 46-18.7 87.7-48.8 117.9l-35.4-35.4c21.2-21.2 34.2-50.3 34.2-82.5zm-333.3 0c0 59.8 24.3 114 63.5 153.2l-35.4 35.4c-48.3-48.3-78.1-115-78.1-188.6s29.8-140.3 78.1-188.6l35.4 35.4c-39.2 39.2-63.5 93.3-63.5 153.2zm433.3 0c0-59.8-24.3-114-63.5-153.2l35.4-35.4c48.3 48.3 78.1 114.9 78.1 188.6s-29.8 140.3-78.1 188.6l-35.4-35.4c39.3-39.2 63.5-93.4 63.5-153.2z" fill="#eee"/></svg>'
|
||
var menu_button = $("<li class=\"menu__item selector\" data-action=\"radio\">\n\t<div class=\"menu__ico\">\n\t\t" + icon + "\n\t</div>\n\t<div class=\"menu__text\">Soma FM</div>\n</li>\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: '<svg enable-background="new 0 0 533.3 377.1" viewBox="0 0 533.3 377.1" xmlns="http://www.w3.org/2000/svg"><path d="m266.7 121.9c36.8 0 66.7 29.8 66.7 66.7s-29.8 66.7-66.7 66.7-66.7-29.9-66.7-66.7 29.8-66.7 66.7-66.7zm-116.7 66.7c0 32.2 13.1 61.4 34.2 82.5l-35.4 35.4c-30.2-30.2-48.8-71.8-48.8-117.9 0-46 18.7-87.7 48.8-117.9l35.4 35.4c-21.1 21.1-34.2 50.2-34.2 82.5zm233.3 0c0-32.2-13.1-61.4-34.2-82.5l35.4-35.4c30.2 30.2 48.8 71.8 48.8 117.9 0 46-18.7 87.7-48.8 117.9l-35.4-35.4c21.2-21.2 34.2-50.3 34.2-82.5zm-333.3 0c0 59.8 24.3 114 63.5 153.2l-35.4 35.4c-48.3-48.3-78.1-115-78.1-188.6s29.8-140.3 78.1-188.6l35.4 35.4c-39.2 39.2-63.5 93.3-63.5 153.2zm433.3 0c0-59.8-24.3-114-63.5-153.2l35.4-35.4c48.3 48.3 78.1 114.9 78.1 188.6s-29.8 140.3-78.1 188.6l-35.4-35.4c39.3-39.2 63.5-93.4 63.5-153.2z" fill="#eee"/></svg>'
|
||
});
|
||
|
||
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', "<div class=\"selector somafm-item\">\n <div class=\"somafm-item__imgbox\">\n <img class=\"somafm-item__img\" />\n <div class=\"somafm-item__listeners\">\n <svg fill=\"none\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><g fill=\"#292d32\"><path d=\"m13.1807 11.8606c-.4 0-.76-.22-.93-.58l-1.45-2.89002-.42.78c-.23.43-.69.7-1.18.7h-.73c-.41 0-.75-.34-.75-.75s.34-.75.75-.75h.64l.79-1.46c.19-.34.57-.57.93-.55.39 0 .74.23.92.57l1.43 2.86.34-.69c.23-.46.68-.74 1.2-.74h.81c.41 0 .75.34.75.75s-.34.75-.75.75h-.71l-.71 1.41002c-.18.37-.53.59-.93.59z\"/><path d=\"m2.74982 18.6508c-.41 0-.75-.34-.75-.75v-5.7c-.05-2.71002.96-5.27002 2.84-7.19002 1.88-1.91 4.4-2.96 7.10998-2.96 5.54 0 10.05 4.51 10.05 10.05002v5.7c0 .41-.34.75-.75.75s-.75-.34-.75-.75v-5.7c0-4.71002-3.83-8.55002-8.55-8.55002-2.30998 0-4.44998.89-6.03998 2.51-1.6 1.63-2.45 3.8-2.41 6.12002v5.71c0 .42-.33.76-.75.76z\"/><path d=\"m5.94 12.4492h-.13c-2.1 0-3.81 1.71-3.81 3.81v1.88c0 2.1 1.71 3.81 3.81 3.81h.13c2.1 0 3.81-1.71 3.81-3.81v-1.88c0-2.1-1.71-3.81-3.81-3.81z\"/><path d=\"m18.19 12.4492h-.13c-2.1 0-3.81 1.71-3.81 3.81v1.88c0 2.1 1.71 3.81 3.81 3.81h.13c2.1 0 3.81-1.71 3.81-3.81v-1.88c0-2.1-1.71-3.81-3.81-3.81z\"/></g></svg>\n {listeners}\n </div>\n </div>\n <div class=\"somafm-item__name\">{name}</div>\n</div>\n");
|
||
Lampa.Template.add('somafm_player', "<div class=\"selector somafm-player loading stop hide\">\n <div class=\"somafm-player__name\">Soma FM</div>\n <div id=\"somafm_player_button\" class=\"somafm-player__button\">\n <i></i>\n <i></i>\n <i></i>\n <i></i>\n </div>\n</div>\n");
|
||
Lampa.Template.add('somafm_info', "<div class=\"somafm-info\">\n <canvas id=\"canvas\"></canvas>\n <div>\n <div class=\"somafm-info__cover\"></div>\n <div class=\"somafm-info__wave\"></div>\n </div>\n <div class=\"somafm-info__close\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 329.269 329\" xml:space=\"preserve\">\n <path d=\"M194.8 164.77 323.013 36.555c8.343-8.34 8.343-21.825 0-30.164-8.34-8.34-21.825-8.34-30.164 0L164.633 134.605 36.422 6.391c-8.344-8.34-21.824-8.34-30.164 0-8.344 8.34-8.344 21.824 0 30.164l128.21 128.215L6.259 292.984c-8.344 8.34-8.344 21.825 0 30.164a21.266 21.266 0 0 0 15.082 6.25c5.46 0 10.922-2.09 15.082-6.25l128.21-128.214 128.216 128.214a21.273 21.273 0 0 0 15.082 6.25c5.46 0 10.922-2.09 15.082-6.25 8.343-8.34 8.343-21.824 0-30.164zm0 0\" fill=\"currentColor\"></path>\n </svg>\n </div>\n</div>\n");
|
||
Lampa.Template.add('somafm_cover', "<div class=\"somafm-cover\">\n <div class=\"somafm-cover__station\"></div>\n <div class=\"somafm-cover__genre\"></div>\n <div class=\"somafm-cover__img-container\">\n <div class=\"somafm-cover__img-box\">\n <img src=\"https://somafm.com/logos/SomaFM-Text-Logo-512.png\" />\n </div>\n </div>\n <div class=\"somafm-cover__album\"><svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"m0 0h24v24h-24z\" fill=\"none\"/><path d=\"m12 2c5.52 0 10 4.48 10 10s-4.48 10-10 10-10-4.48-10-10 4.48-10 10-10zm0 14c2.213 0 4-1.787 4-4s-1.787-4-4-4-4 1.787-4 4 1.787 4 4 4zm0-5c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1z\" fill=\"#eee\"/></svg><span class=\"somafm-cover__album_title\"></span></div>\n <div class=\"somafm-cover__title\"></div>\n <div class=\"somafm-cover__tooltip\"></div>\n <div class=\"somafm-cover__playlist\"></div>\n</div>\n");
|
||
Lampa.Template.add('somafm_style', "<style>\n.somafm-item {\n margin-left: 1em;\n margin-bottom: 1em;\n width: 13%;\n -webkit-flex-shrink: 0;\n -ms-flex-negative: 0;\n flex-shrink: 0;\n}\n.somafm-item__imgbox {\n background-color: #3e3e3e;\n padding-bottom: 100%;\n position: relative;\n -webkit-border-radius: 0.3em;\n -moz-border-radius: 0.3em;\n border-radius: 0.3em;\n}\n.somafm-item__listeners {\n position: absolute;\n top: 0.5em;\n left: 0.5em;\n background-color: #eee;\n padding: 0.1em 0.3em;\n font-size: 0.7em;\n font-weight: bold;\n color: #292d32;\n -webkit-border-radius: 0.25em;\n -moz-border-radius: 0.25em;\n border-radius: 0.25em;\n}\n.somafm-item__listeners > svg {\n width: 1em;\n height: 1em;\n vertical-align: bottom;\n}\n.somafm-item__img {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n -webkit-border-radius: 0.4em;\n -moz-border-radius: 0.4em;\n border-radius: 0.4em;\n}\n.somafm-item__name {\n font-size: 1.1em;\n margin-top: 0.8em;\n}\n.somafm-item.focus .somafm-item__imgbox:after {\n border: solid 0.26em #fff;\n content: \"\";\n display: block;\n position: absolute;\n left: -0.5em;\n top: -0.5em;\n right: -0.5em;\n bottom: -0.5em;\n -webkit-border-radius: 0.8em;\n -moz-border-radius: 0.8em;\n border-radius: 0.8em;\n}\n@-webkit-keyframes sound {\n 0% {\n height: 0.1em;\n }\n 100% {\n height: 1em;\n }\n}\n@-moz-keyframes sound {\n 0% {\n height: 0.1em;\n }\n 100% {\n height: 1em;\n }\n}\n@-o-keyframes sound {\n 0% {\n height: 0.1em;\n }\n 100% {\n height: 1em;\n }\n}\n@keyframes sound {\n 0% {\n height: 0.1em;\n }\n 100% {\n height: 1em;\n }\n}\n@-webkit-keyframes sound-loading {\n 0% {\n -webkit-transform: rotate(0deg);\n transform: rotate(0deg);\n }\n 100% {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n@-moz-keyframes sound-loading {\n 0% {\n -moz-transform: rotate(0deg);\n transform: rotate(0deg);\n }\n 100% {\n -moz-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n@-o-keyframes sound-loading {\n 0% {\n -o-transform: rotate(0deg);\n transform: rotate(0deg);\n }\n 100% {\n -o-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n@keyframes sound-loading {\n 0% {\n -webkit-transform: rotate(0deg);\n -moz-transform: rotate(0deg);\n -o-transform: rotate(0deg);\n transform: rotate(0deg);\n }\n 100% {\n -webkit-transform: rotate(360deg);\n -moz-transform: rotate(360deg);\n -o-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n@media screen and (max-width: 580px) {\n .somafm-item {\n width: 21%;\n }\n}\n@media screen and (max-width: 385px) {\n .somafm-item__name {\n display: none;\n }\n}\n.somafm-player {\n display: -webkit-box;\n display: -webkit-flex;\n display: -moz-box;\n display: -ms-flexbox;\n display: flex;\n -webkit-box-align: center;\n -webkit-align-items: center;\n -moz-box-align: center;\n -ms-flex-align: center;\n align-items: center;\n -webkit-border-radius: 0.3em;\n -moz-border-radius: 0.3em;\n border-radius: 0.3em;\n padding: 0.2em 0.4em;\n margin-left: 0.5em;\n margin-right: 0.5em;\n}\n.somafm-player__name {\n margin-right: 0.35em;\n white-space: nowrap;\n overflow: hidden;\n -o-text-overflow: ellipsis;\n text-overflow: ellipsis;\n max-width: 8em;\n display: none;\n}\n.somafm-player__button {\n position: relative;\n width: 2em;\n height: 2em;\n display: -webkit-box;\n display: -webkit-flex;\n display: -moz-box;\n display: -ms-flexbox;\n display: flex;\n -webkit-box-align: center;\n -webkit-align-items: center;\n -moz-box-align: center;\n -ms-flex-align: center;\n align-items: center;\n -webkit-box-pack: center;\n -webkit-justify-content: center;\n -moz-box-pack: center;\n -ms-flex-pack: center;\n justify-content: center;\n -webkit-flex-shrink: 0;\n -ms-flex-negative: 0;\n flex-shrink: 0;\n -webkit-border-radius: 0.3em;\n -moz-border-radius: 0.3em;\n border-radius: 0.3em;\n border: 0.15em solid rgba(255, 255, 255, 1);\n}\n.somafm-player__button > * {\n opacity: 0.75;\n}\n.somafm-player__button i {\n display: block;\n width: 0.2em;\n background-color: #fff;\n margin: 0 0.1em;\n -webkit-animation: sound 0ms -800ms linear infinite alternate;\n -moz-animation: sound 0ms -800ms linear infinite alternate;\n -o-animation: sound 0ms -800ms linear infinite alternate;\n animation: sound 0ms -800ms linear infinite alternate;\n -webkit-flex-shrink: 0;\n -ms-flex-negative: 0;\n flex-shrink: 0;\n}\n.somafm-player__button i:nth-child(1) {\n -webkit-animation-duration: 474ms;\n -moz-animation-duration: 474ms;\n -o-animation-duration: 474ms;\n animation-duration: 474ms;\n}\n.somafm-player__button i:nth-child(2) {\n -webkit-animation-duration: 433ms;\n -moz-animation-duration: 433ms;\n -o-animation-duration: 433ms;\n animation-duration: 433ms;\n}\n.somafm-player__button i:nth-child(3) {\n -webkit-animation-duration: 407ms;\n -moz-animation-duration: 407ms;\n -o-animation-duration: 407ms;\n animation-duration: 407ms;\n}\n.somafm-player__button i:nth-child(4) {\n -webkit-animation-duration: 458ms;\n -moz-animation-duration: 458ms;\n -o-animation-duration: 458ms;\n animation-duration: 458ms;\n}\n.somafm-player.stop .somafm-player__button i {\n display: none;\n}\n.somafm-player.stop .somafm-player__button:after {\n content: \"\";\n width: 0.5em;\n height: 0.5em;\n background-color: rgba(255, 255, 255, 1);\n}\n.somafm-player.loading .somafm-player__button:before {\n content: \"\";\n display: block;\n border-top: 0.2em solid rgba(255, 255, 255, 0.9);\n border-left: 0.2em solid transparent;\n border-right: 0.2em solid transparent;\n border-bottom: 0.2em solid transparent;\n -webkit-animation: sound-loading 1s linear infinite;\n -moz-animation: sound-loading 1s linear infinite;\n -o-animation: sound-loading 1s linear infinite;\n animation: sound-loading 1s linear infinite;\n width: 0.9em;\n height: 0.9em;\n -webkit-border-radius: 100%;\n -moz-border-radius: 100%;\n border-radius: 100%;\n -webkit-flex-shrink: 0;\n -ms-flex-negative: 0;\n flex-shrink: 0;\n}\n.somafm-player.loading .somafm-player__button i {\n display: none;\n}\n.somafm-player.focus {\n background-color: #fff;\n color: #000;\n}\n.somafm-player.focus .somafm-player__name {\n display: inline;\n}\n@media screen and (max-width: 580px) {\n .somafm-player.focus .somafm-player__name {\n display: none;\n }\n}\n@media screen and (max-width: 385px) {\n .somafm-player.focus .somafm-player__name {\n display: none;\n }\n}\n.somafm-player.focus .somafm-player__button {\n border-color: #000;\n}\n.somafm-player.focus .somafm-player__button i,\n.somafm-player.focus .somafm-player__button:after {\n background-color: #000;\n}\n.somafm-player.focus .somafm-player__button:before {\n border-top-color: #000;\n}\n.somafm-cover {\n text-align: center;\n line-height: 1.4;\n}\n.somafm-cover__img-container {\n max-width: 20em;\n margin: 0 auto;\n}\n.somafm-cover__img-box {\n position: relative;\n padding-bottom: 100%;\n background-color: rgba(0, 0, 0, 0.3);\n -webkit-border-radius: 0.5em;\n -moz-border-radius: 0.5em;\n border-radius: 0.5em;\n}\n.somafm-cover__img-box > img {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n -webkit-border-radius: 0.5em;\n -moz-border-radius: 0.5em;\n border-radius: 0.5em;\n opacity: 0;\n}\n.somafm-cover__img-box.loaded {\n background-color: transparent;\n}\n.somafm-cover__img-box.loaded > img {\n opacity: 1;\n}\n.somafm-cover__img-box.loaded-icon {\n background-color: rgba(0, 0, 0, 0.3);\n}\n.somafm-cover__img-box.loaded-icon > img {\n left: 20%;\n top: 20%;\n width: 60%;\n height: 60%;\n opacity: 0.2;\n}\n.somafm-cover__station {\n font-weight: 500;\n font-size: 1.3em;\n margin-bottom: 0.2em;\n}\n.somafm-cover__genre {\n font-weight: 200;\n font-size: 1em;\n margin-bottom: 0.6em;\n}\n.somafm-cover__album {\n font-weight: 300;\n font-size: 1em;\n margin-top: 0.4em;\n}\n.somafm-cover__album > svg {\n width: 0em;\n height: 1.25em;\n margin-right: 0.2em;\n vertical-align: text-bottom;\n}\n.somafm-cover__title {\n font-weight: 600;\n font-size: 1.5em;\n margin-top: 0.6em;\n}\n.somafm-cover__tooltip {\n font-weight: 300;\n font-size: 1.3em;\n margin-top: 0.2em;\n}\n.somafm-cover__playlist {\n font-weight: 300;\n font-size: 1.3em;\n margin-top: 0.2em;\n}\n.somafm-info {\n position: fixed;\n z-index: 100;\n left: 0;\n top: 0;\n width: 100%;\n height: 100%;\n display: -webkit-box;\n display: -webkit-flex;\n display: -moz-box;\n display: -ms-flexbox;\n display: flex;\n -webkit-box-align: center;\n -webkit-align-items: center;\n -moz-box-align: center;\n -ms-flex-align: center;\n align-items: center;\n -webkit-box-pack: center;\n -webkit-justify-content: center;\n -moz-box-pack: center;\n -ms-flex-pack: center;\n justify-content: center;\n}\n.somafm-info__cover {\n width: 30em;\n}\n.somafm-info__wave {\n display: -webkit-box;\n display: -webkit-flex;\n display: -moz-box;\n display: -ms-flexbox;\n display: flex;\n -webkit-box-align: center;\n -webkit-align-items: center;\n -moz-box-align: center;\n -ms-flex-align: center;\n align-items: center;\n -webkit-box-pack: center;\n -webkit-justify-content: center;\n -moz-box-pack: center;\n -ms-flex-pack: center;\n justify-content: center;\n margin-top: 2em;\n}\n.somafm-info__wave > div {\n width: 2px;\n background-color: #fff;\n margin: 0 0.3em;\n height: 1em;\n opacity: 0;\n}\n.somafm-info__wave > div.loading {\n -webkit-animation: somafmAnimationWaveLoading 400ms ease infinite;\n -moz-animation: somafmAnimationWaveLoading 400ms ease infinite;\n -o-animation: somafmAnimationWaveLoading 400ms ease infinite;\n animation: somafmAnimationWaveLoading 400ms ease infinite;\n}\n.somafm-info__wave > div.play {\n -webkit-animation: somafmAnimationWavePlay 50ms linear infinite alternate;\n -moz-animation: somafmAnimationWavePlay 50ms linear infinite alternate;\n -o-animation: somafmAnimationWavePlay 50ms linear infinite alternate;\n animation: somafmAnimationWavePlay 50ms linear infinite alternate;\n}\n.somafm-info__close {\n position: fixed;\n top: 5em;\n right: 50%;\n margin-right: -2em;\n -webkit-border-radius: 100%;\n -moz-border-radius: 100%;\n border-radius: 100%;\n padding: 1em;\n display: none;\n background-color: rgba(255, 255, 255, 0.1);\n}\n.somafm-info__close > svg {\n width: 1.5em;\n height: 1.5em;\n}\nbody.true--mobile .somafm-info__close {\n display: block;\n}\n@-webkit-keyframes somafmAnimationWaveLoading {\n 0% {\n -webkit-transform: scale3d(1, 0.3, 1);\n transform: scale3d(1, 0.3, 1);\n opacity: 1;\n }\n 10% {\n -webkit-transform: scale3d(1, 1.5, 1);\n transform: scale3d(1, 1.5, 1);\n opacity: 1;\n }\n 20% {\n -webkit-transform: scale3d(1, 0.3, 1);\n transform: scale3d(1, 0.3, 1);\n opacity: 1;\n }\n 100% {\n -webkit-transform: scale3d(1, 0.3, 1);\n transform: scale3d(1, 0.3, 1);\n opacity: 1;\n }\n}\n@-moz-keyframes somafmAnimationWaveLoading {\n 0% {\n -moz-transform: scale3d(1, 0.3, 1);\n transform: scale3d(1, 0.3, 1);\n opacity: 1;\n }\n 10% {\n -moz-transform: scale3d(1, 1.5, 1);\n transform: scale3d(1, 1.5, 1);\n opacity: 1;\n }\n 20% {\n -moz-transform: scale3d(1, 0.3, 1);\n transform: scale3d(1, 0.3, 1);\n opacity: 1;\n }\n 100% {\n -moz-transform: scale3d(1, 0.3, 1);\n transform: scale3d(1, 0.3, 1);\n opacity: 1;\n }\n}\n@-o-keyframes somafmAnimationWaveLoading {\n 0% {\n transform: scale3d(1, 0.3, 1);\n opacity: 1;\n }\n 10% {\n transform: scale3d(1, 1.5, 1);\n opacity: 1;\n }\n 20% {\n transform: scale3d(1, 0.3, 1);\n opacity: 1;\n }\n 100% {\n transform: scale3d(1, 0.3, 1);\n opacity: 1;\n }\n}\n@keyframes somafmAnimationWaveLoading {\n 0% {\n -webkit-transform: scale3d(1, 0.3, 1);\n -moz-transform: scale3d(1, 0.3, 1);\n transform: scale3d(1, 0.3, 1);\n opacity: 1;\n }\n 10% {\n -webkit-transform: scale3d(1, 1.5, 1);\n -moz-transform: scale3d(1, 1.5, 1);\n transform: scale3d(1, 1.5, 1);\n opacity: 1;\n }\n 20% {\n -webkit-transform: scale3d(1, 0.3, 1);\n -moz-transform: scale3d(1, 0.3, 1);\n transform: scale3d(1, 0.3, 1);\n opacity: 1;\n }\n 100% {\n -webkit-transform: scale3d(1, 0.3, 1);\n -moz-transform: scale3d(1, 0.3, 1);\n transform: scale3d(1, 0.3, 1);\n opacity: 1;\n }\n}\n@-webkit-keyframes somafmAnimationWavePlay {\n 0% {\n -webkit-transform: scale3d(1, 0.3, 1);\n transform: scale3d(1, 0.3, 1);\n opacity: 0.3;\n }\n 100% {\n -webkit-transform: scale3d(1, 2, 1);\n transform: scale3d(1, 2, 1);\n opacity: 1;\n }\n}\n@-moz-keyframes somafmAnimationWavePlay {\n 0% {\n -moz-transform: scale3d(1, 0.3, 1);\n transform: scale3d(1, 0.3, 1);\n opacity: 0.3;\n }\n 100% {\n -moz-transform: scale3d(1, 2, 1);\n transform: scale3d(1, 2, 1);\n opacity: 1;\n }\n}\n@-o-keyframes somafmAnimationWavePlay {\n 0% {\n transform: scale3d(1, 0.3, 1);\n opacity: 0.3;\n }\n 100% {\n transform: scale3d(1, 2, 1);\n opacity: 1;\n }\n}\n@keyframes somafmAnimationWavePlay {\n 0% {\n -webkit-transform: scale3d(1, 0.3, 1);\n -moz-transform: scale3d(1, 0.3, 1);\n transform: scale3d(1, 0.3, 1);\n opacity: 0.3;\n }\n 100% {\n -webkit-transform: scale3d(1, 2, 1);\n -moz-transform: scale3d(1, 2, 1);\n transform: scale3d(1, 2, 1);\n opacity: 1;\n }\n}\n@media screen and (min-height: 320px) and (max-height: 428px) and (orientation: landscape) {\n .somafm-info__close {\n position: fixed;\n top: 5%;\n right: 95%;\n margin-right: -2em;\n }\n .somafm-cover__img-container {\n max-width: 12em;\n }\n}\n#canvas {\n position: absolute;\n left: 0;\n bottom: 0;\n width: 100%;\n height: 100%;\n z-index: -1;\n}\n</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();
|
||
|
||
})();
|