(function () { var API_BASE = '{localhost}'; var activeJob = null; var heartbeatTimer = null; var waitHandle = null; function account(url) { url = url + ''; if (url.indexOf('account_email=') == -1) { var email = Lampa.Storage.get('account_email'); if (email) url = Lampa.Utils.addUrlComponent(url, 'account_email=' + encodeURIComponent(email)); } if (url.indexOf('uid=') == -1) { var uid = Lampa.Storage.get('lampac_unic_id', ''); if (uid) url = Lampa.Utils.addUrlComponent(url, 'uid=' + encodeURIComponent(uid)); } if (url.indexOf('token=') == -1) { var token = '{token}'; if (token != '') url = Lampa.Utils.addUrlComponent(url, 'token={token}'); } return url; } function showWait(message) { Lampa.Loading.start(function(){}, message); } function hideWait() { Lampa.Loading.stop(); } function log() { try { var args = Array.prototype.slice.call(arguments); args.unshift('Transcoder'); console.log.apply(console, args); } catch (err) { /* noop */ } } function notify(message) { if (window.Lampa && Lampa.Noty && Lampa.Noty.show) { Lampa.Noty.show(message); } else { log(message); } } function resolveMediaUrl(data) { if (data && data.url) return data.url; return ''; } function isMkvSource(data) { var url = resolveMediaUrl(data); if (!url) return false; return /\.(mkv|avi|flv)($|\?|#)/i.test(url) || /\/lite\/pidtor\//i.test(url); } function requestFfprobe(mediaUrl, onSuccess, onError) { if (!mediaUrl) { if (onError) onError(new Error('Empty media url')); return; } var net = new Lampa.Reguest(); var target = account(API_BASE + '/ffprobe?media=' + encodeURIComponent(mediaUrl)); log('request ffprobe', target); net.native(target, function (response) { try { var json = typeof response === 'string' ? JSON.parse(response) : response; try { var streams = json && Array.isArray(json.streams) ? json.streams : []; var v = streams.find(function(s){ return s.codec_type === 'video'; }); var rawCodec = (v && (v.codec_name || (v.tags && v.tags.codec_name))) || ''; } catch(e) { /* noop */ } if (onSuccess) onSuccess(json || {}); } catch(e) { if (onError) onError(e); } }, function (error) { hideWait(); if (onError) onError(error || new Error('ffprobe request failed')); }, null, { dataType: 'text', timeout: 1000 * 40 }); } function formatAudioItem(track, index) { var tags = track && track.tags ? track.tags : {}; var title = tags.title || tags.handler_name || ('Audio #' + (index + 1)); var lang = (tags.language || '').toUpperCase(); var codec = (track.codec_name || '').toUpperCase(); var channels = (track.channel_layout || '').replace('(side)', '').replace('stereo', '2.0'); var rate = track.bit_rate ? Math.round(track.bit_rate / 1000) + ' kbps' : ''; var subtitleParts = []; if (lang) subtitleParts.push(lang); if (codec) subtitleParts.push(codec); if (channels) subtitleParts.push(channels); if (rate) subtitleParts.push(rate); return { title: title, subtitle: subtitleParts.join(' • '), track: track }; } function selectAutoAudioTrack(audioTracks) { for (var i = 0; i < audioTracks.length; i++) { var disposition = audioTracks[i] && audioTracks[i].disposition; if (disposition && (disposition.default === 1 || disposition.default === true)) { return audioTracks[i]; } } var language = String(Lampa.Storage.get('language', 'ru') || 'ru').toLowerCase().split('-')[0]; var aliases = { ru: ['ru', 'rus'], en: ['en', 'eng'], uk: ['uk', 'ukr'], be: ['be', 'bel'], bg: ['bg', 'bul'], he: ['he', 'heb'], cs: ['cs', 'ces', 'cze'], ro: ['ro', 'ron', 'rum'], de: ['de', 'deu', 'ger'], fr: ['fr', 'fra', 'fre'], es: ['es', 'spa'], it: ['it', 'ita'], pt: ['pt', 'por'], pl: ['pl', 'pol'], tr: ['tr', 'tur'], ja: ['ja', 'jpn'], ko: ['ko', 'kor'], zh: ['zh', 'zho', 'chi'] }; var preferred = aliases[language] || (language ? [language] : []); for (var j = 0; j < audioTracks.length; j++) { var tags = audioTracks[j] && audioTracks[j].tags ? audioTracks[j].tags : {}; var trackLanguage = String(tags.language || '').toLowerCase(); if (preferred.indexOf(trackLanguage) !== -1) { return audioTracks[j]; } } return audioTracks[0]; } function showAudioSelector(data, audioTracks) { if (!audioTracks.length) { notify('Не удалось найти аудиодорожки для MKV файла'); return; } var manualSelection = Lampa.Storage.get('transcoding_audio_prompt', false); if (!(manualSelection === true || manualSelection === 'true')) { startTranscoding(data, selectAutoAudioTrack(audioTracks)); return; } var items = audioTracks.map(function (track, index) { return formatAudioItem(track, index); }); var last_controller = Lampa.Controller.enabled().name Lampa.Select.show({ title: 'Выберите аудиодорожку', items: items, onSelect: function (item) { Lampa.Select.close(); if (!item || !item.track) { notify('Выбрана пустая дорожка'); return; } startTranscoding(data, item.track); }, onBack: function () { Lampa.Controller.toggle(last_controller) } }); } function startTranscoding(data, track) { stopHeartbeat(); ensureJobStopped(true); var payload = { src: resolveMediaUrl(data), audio: { index: track.index -1 }, live: false }; /*if (data && data.subtitles) { payload.subtitles = !!data.subtitles; }*/ var net = new Lampa.Reguest(); net.native(account(API_BASE + '/transcoding/start'), function (response) { var json = typeof response === 'string' ? JSON.parse(response) : response; if (!json || !json.streamId || !json.playlistUrl) { notify('Некорректный ответ от сервера перекодирования'); log('invalid start response', response); return; } activeJob = { streamId: json.streamId, playlistUrl: json.playlistUrl }; var playback = data || {}; playback.transcoding = true; playback.ffprobe = null; playback.url = json.playlistUrl; playback.subtitles_call = json.subtitlesUrl; playback.hls_manifest_timeout = json.hls_timeout_seconds * 1000; Lampa.Player.play(playback); }, function (error) { hideWait(); notify('Не удалось запустить перекодирование MKV'); log('start error', error); }, JSON.stringify(payload), { dataType: 'json', type: 'POST', timeout: 1000 * 20, headers: { 'Content-Type': 'application/json' } }); } function sendHeartbeat() { if (!activeJob) return; var net = new Lampa.Reguest(); net.native(account(API_BASE + '/transcoding/' + activeJob.streamId + '/heartbeat'), function () { log('heartbeat sent'); }, function (error) { hideWait(); log('heartbeat error', error); }, null, { dataType: 'text', timeout: 1000 * 10 }); } function stopHeartbeat() { if (heartbeatTimer) { clearInterval(heartbeatTimer); heartbeatTimer = null; } } function ensureJobStopped(sendRemote) { if (!activeJob) return; var job = activeJob; activeJob = null; stopHeartbeat(); if (sendRemote) { var net = new Lampa.Reguest(); net.native(account(API_BASE + '/transcoding/' + job.streamId + '/stop'), function () { log('stop sent'); }, function (error) { hideWait(); log('stop error', error); }, null, { dataType: 'text', timeout: 1000 * 10 }); } } function registerSettings() { if (window.lampac_transcoding_audio_setting || !Lampa.SettingsApi || !Lampa.SettingsApi.addParam) return; window.lampac_transcoding_audio_setting = true; Lampa.SettingsApi.addParam({ component: 'player', param: { name: 'transcoding_audio_prompt', type: 'trigger', values: '', default: false }, field: { name: 'Спрашивать аудиодорожку при транскодировании', description: 'Включите для ручного выбора дорожки перед запуском MKV' } }); } function handlePlayerStart(e) { if (!isMkvSource(e.data) || e.data.transcoding || /\/transcoding\//i.test(e.data.url)) return; e.abort(); e.data.url = e.data.url.replace(/&(preload|stat|m3u)/g, '&play'); log('intercept mkv playback', e.data.url); showWait('Получение списка аудио дорожек...'); requestFfprobe(resolveMediaUrl(e.data), function (info) { hideWait(); var streams = info && Array.isArray(info.streams) ? info.streams : []; var audioTracks = streams.filter(function (track) { return track.codec_type === 'audio'; }); showAudioSelector(e.data, audioTracks); }, function (error) { hideWait(); notify('Ошибка анализа MKV файла'); log('ffprobe error', error); }); } function handlePlayerDestroy() { ensureJobStopped(true); } function handleVideoPause() { if (!activeJob) return; stopHeartbeat(); heartbeatTimer = setInterval(sendHeartbeat, 1000 * 10); } function handleVideoPlay() { if (!activeJob) return; stopHeartbeat(); } if (!window.lampac_transcoding_plugin) { window.lampac_transcoding_plugin = true; registerSettings(); Lampa.Player.listener.follow('create', handlePlayerStart); Lampa.Player.listener.follow('destroy', handlePlayerDestroy); Lampa.PlayerVideo.listener.follow('pause', handleVideoPause); Lampa.PlayerVideo.listener.follow('play', handleVideoPlay); } })();