Skip to content

Instantly share code, notes, and snippets.

@ProgressoRU
Last active October 6, 2021 17:51
Show Gist options
  • Save ProgressoRU/ae37235a622cbbfeea202be5c7ca8346 to your computer and use it in GitHub Desktop.
Save ProgressoRU/ae37235a622cbbfeea202be5c7ca8346 to your computer and use it in GitHub Desktop.
Tizen AVPlay for Lampa
function mod() {
var seek = resetSeekObj(),
src,
captionsOn = false,
isPlayerReady = false,
torrentStatsInt, clockInt;
function playerInjector() {
MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
tizen.tvinputdevice.registerKey("MediaPlayPause");
tizen.tvinputdevice.registerKey("MediaPlay");
tizen.tvinputdevice.registerKey("MediaStop");
tizen.tvinputdevice.registerKey("MediaPause");
tizen.tvinputdevice.registerKey("MediaRewind");
tizen.tvinputdevice.registerKey("MediaFastForward");
var avPlayer = document.createElement('object');
avPlayer.type = 'application/avplayer';
avPlayer.style.display = "none";
avPlayer.style.position = 'fixed';
avPlayer.style.backgroundColor = '#000';
avPlayer.style.display = 'block';
avPlayer.style.left = 0;
avPlayer.style.top = 0;
avPlayer.style.width = '100%';
avPlayer.style.height = '100%';
//looking for a player to appear in DOM tree
var obs = new MutationObserver(function(mutations, observer) {
for(var i=0; i<mutations.length; ++i) {
for(var j=0; j<mutations[i].addedNodes.length; ++j) {
if(mutations[i].addedNodes[j].classList.contains('player')) {
var playerNode = mutations[i].addedNodes[j],
videoNode = $(playerNode).find('video');
src = videoNode.attr('src');
console.log(src);
clearTimeout(timerSub);
addButtons();
if(!isPlayerReady) {
$('.player-panel__size').on('click', interceptSizeSelectBox);
$('.player-panel__playlist').on('click', onPlaylistClick);
$('.player-panel__playpause').on('click', playPause);
$('.player-panel__prev').on('click', onPrevNext);
$('.player-panel__next').on('click', onPrevNext);
var playerPanelObs = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.attributeName === "class") {
if ($(mutation.target).hasClass('panel--visible')) {
$('#tizen-info-panel').css('display', 'block')
} else {
$('#tizen-info-panel').css('display', 'none')
}
}
});
});
playerPanelObs.observe($('.player-panel').get(0), {attributes: true});
}
if(src) {
if(!isPlayerReady) {
document.addEventListener('keydown', interceptControls);
document.addEventListener('keyup', interceptKeyUp);
webapis.avplay.setListener(avPlayerListeners);
torrentStatsInt = setInterval(clock, 1000);
clockInt = setInterval(getTorrentStats, 2500);
}
videoNode.parent().append(avPlayer);
$('.player-video__display').children().filter("video").each(function(){
this.addEventListener('playing', function(){ console.log('playing') })
$(this).attr('src', ''); //this is mandatory to proper cleanup
this.load();
$(this).remove();
});
webapis.avplay.stop();
toggleLoadingState(true, 'Предзагрузка...');
webapis.avplay.open(src + '&preload');
webapis.avplay.setDisplayMethod('PLAYER_DISPLAY_MODE_LETTER_BOX');
webapis.avplay.setDisplayRect(0, 0, 1920, 1080);
webapis.avplay.setSilentSubtitle(true);
webapis.avplay.setTimeoutForBuffering(30);
captionsOn = false;
isPlayerReady = true;
webapis.avplay.prepareAsync(function() {
console.log('[avplay]: prepare success');
$('.player-panel__timeend').html(formatTime(webapis.avplay.getDuration() / 1000));
webapis.avplay.play();
}, function(err) {
console.error('[avplay]: Error while prpearing avplay', err)
$.notify(err);
});
}
}
}
}
});
obs.observe($("body").get(0), {
childList: true
});
}
var avPlayerListeners = {
onbufferingstart: function () {
console.log("[avplay]: Buffering start.");
toggleLoadingState(true, 'Предзагрузка...');
},
onbufferingprogress: function (percent) {
toggleLoadingState(true, 'Буферизация ' + percent + '%');
},
onbufferingcomplete: function () {
console.log("[avplay]: Buffering complete.");
toggleLoadingState(false);
},
oncurrentplaytime: function (currentTime) {
updateScrubber(currentTime);
},
onevent: function (eventType, eventData) {
console.log("[avplay]: event type: " + eventType + ", data: " + eventData);
},
onsubtitlechange: function(duration, caption, type, attrs) {
if(captionsOn) {
clearTimeout(timerSub);
setCaptionsPosition('bottom');
if(caption.startsWith('{\\an8}')) {
caption = caption.replace('{\\an8}', '');
setCaptionsPosition('top');
}
$('.player-subtitles').html($.parseHTML(caption));
timerSub = setTimeout(function() {
$('.player-subtitles').html('');
}, duration)
}
},
onstreamcompleted: function () {
console.log("[avplay]: Stream Completed");
$('.player-panel__next').trigger('hover:enter');
},
onerror: function (eventType) {
console.log("[avplay]: event type error : " + eventType);
$.notify(eventType);
}
}
function setCaptionsPosition(pos) {
switch (pos) {
case 'top':
$('.player-subtitles').css('top', 75);
$('.player-subtitles').css('bottom', '');
break;
case 'bottom':
default:
$('.player-subtitles').css('top', '');
$('.player-subtitles').css('bottom', 100);
}
}
var tvKey = {
N1: 49,
N2: 50,
N3: 51,
N4: 52,
N5: 53,
N6: 54,
N7: 55,
N8: 56,
N9: 57,
N0: 48,
PRECH: 10190,
VOL_UP: 448,
VOL_DOWN: 447,
MUTE: 449,
CH_UP: 427,
CH_DOWN: 428,
TOOLS: 10135,
ENTER: 13,
RETURN: 10009,
INFO: 457,
EXIT: 10182,
UP: 38,
DOWN: 40,
LEFT: 37,
RIGHT: 39,
RED: 403,
GREEN: 404,
YELLOW: 405,
BLUE: 406,
RW: 412,
PAUSE: 19,
FF: 417,
REC: 416,
PLAY: 415,
STOP: 413,
PLAYPAUSE: 10252
};
function interceptKeyUp(e) {
e.stopPropagation();
e.stopImmediatePropagation();
switch(e.keyCode){
case tvKey.LEFT: //LEFT arrow
endSeek('bw');
break;
case tvKey.RIGHT: //RIGHT arrow
endSeek('fw');
break;
default:
break;
}
}
function interceptControls(e) {
switch(e.keyCode){
case tvKey.LEFT: //LEFT arrow
e.stopPropagation();
if(($('.player-panel.panel--visible').length === 0 && !$('body').hasClass('selectbox--open')) || seek.isSeek) {
if(isLoading()) return;
seekVid('bw');
} else {
Navigator.move('left')
}
break;
case tvKey.RIGHT: //RIGHT arrow
e.stopPropagation();
if(($('.player-panel.panel--visible').length === 0 && !$('body').hasClass('selectbox--open')) || seek.isSeek) {
if(isLoading()) return;
seekVid('fw');
} else {
Navigator.move('right')
}
break;
case tvKey.ENTER: //OK button
console.log('enter');
e.preventDefault();
e.stopImmediatePropagation();
e.stopPropagation();
if(seek.isSeek) {
if(!isLoading()) {
console.log('immediate seek!');
endSeekImmediate();
}
return;
}
if($('.player-panel.panel--visible').length === 0 && !$('body').hasClass('selectbox--open')) {
playPause();
} else {
Navigator.getFocusedElement().click()
}
break;
case tvKey.RETURN: //RETURN button
if(!$('body').hasClass('selectbox--open')) {
exitAndClenaup();
}
break;
case tvKey.PLAYPAUSE: // PLAYPAUSE button
playPause();
break;
case tvKey.PLAY: // PLAY button
webapis.avplay.play();
break;
case tvKey.PAUSE: // PAUSE button
webapis.avplay.pause();
break;
default:
break;
}
}
function seekVid(dir) {
if(!seek.interval) {
seek.isSeek = true;
seek.isPressed = true;
webapis.avplay.pause();
$('.player-panel').addClass('panel--visible');
seek.interval = setInterval(function() {
seek[dir].timePassed += 100;
if(dir === 'fw') {
seek.dst += 15 * seek.speed;
}
if(dir=== 'bw') {
seek.dst -= 15 * seek.speed;
}
seek.speed = Math.min(3, Math.ceil((seek[dir].timePassed / 1000) / 3));
updateScrubber(Math.max(0, Math.min(webapis.avplay.getCurrentTime() + seek.dst * 1000, webapis.avplay.getDuration())));
}, 100)
}
}
function endSeek(dir) {
if(seek.isSeek) {
seek.isPressed = false
seek.speed = 1;
seek[dir].timePassed = 0;
clearInterval(seek.interval);
seek.interval = null;
clearTimeout(seek.endTimeout);
seek.endTimeout = setTimeout(endSeekImmediate, 500);
}
}
function endSeekImmediate() {
if(!seek.isPressed) {
clearTimeout(seek.endTimeout);
toggleLoadingState(true);
var ts = Math.max(0, Math.min(webapis.avplay.getDuration() - 1000, webapis.avplay.getCurrentTime() + seek.dst * 1000));
console.log('seekTo', ts);
//seekTo
try {
webapis.avplay.seekTo(ts, successCallback, errorCallback);
function successCallback() {
console.log('[avplay]: seek success');
toggleLoadingState(false);
webapis.avplay.play();
seek = resetSeekObj();
}
function errorCallback(err) {
console.log('[avplay]: seek fail');
console.log(err);
seek = resetSeekObj();
}
} catch (e) {
console.error(e);
seek = resetSeekObj();
}
}
}
function updateScrubber(currentTime) {
var duration = webapis.avplay.getDuration();
if (duration > 0) {
var percent = Math.max(0, Math.min(100, ((currentTime / duration) * 100))),
time = formatTime(Math.max(0, Math.min(duration, currentTime / 1000)))
$('.player-panel__timenow').html(time);
$('.player-panel__position').css('width', percent + '%');
$('.player-panel__time').css('left', percent + '%').html(time);
}
}
function playPause() {
var playerState = webapis.avplay.getState();
if(playerState === 'PAUSED' || playerState === 'IDLE') {
webapis.avplay.play();
}
if(playerState === 'PLAYING') {
webapis.avplay.pause();
}
}
function addButtons() {
if($('.player-panel__audio').length === 0) {
var panel = $('.player-panel__right');
panel.append('<div class="player-panel__audio button selector" style="text-align:center; padding-top: .33em">🔉</div>')
panel.append('<div class="player-panel__subtitles button selector" style="text-align:center; padding-top: .33em">АБВ</div>')
$('.player-video').after('<div class="player-subtitles" style="position: fixed; padding: 0 50px; font-weight: 600; width: 100%; color: #fff; font-size: 32pt; text-align: center; text-shadow: -2px -2px 0 #000, 2px -2px 0 #000, -2px 2px 0 #000, 2px 2px 0 #000;"></div>')
$('.player-video').after('<div id="tizen-info-panel" style="display: none; color: #fff; z-index: 60; position: fixed; padding: 10px; text-shadow: -2px -2px 0 #000, 2px -2px 0 #000, -2px 2px 0 #000, 2px 2px 0 #000; text-align: right; width: 100%">' +
'<div><span style="font-size: 8pt">Mod v.1.0.1</span> <span id="tizen-clock"></span></div>' +
'<div id="tizen-torr-info"></div>' +
'</div>')
$('.player-panel__audio').on('click', nextAudioTrack);
$('.player-panel__subtitles').on('click', nextTextTrack);
}
}
function nextAudioTrack() {
if(webapis.avplay.getState() === 'PLAYING' || webapis.avplay.getState() === 'READY') {
var track = nextTrack('AUDIO');
$.notify('Дорожка аудио: ' + track.num + '/' + getTracksCount('AUDIO') + ': ' + track.extra_info.language + '(' + track.extra_info.fourCC + ' ' + (track.extra_info.channels ? track.extra_info.channels : 'Unk') + 'ch)', 'info');
}
}
function nextTextTrack() {
var current = webapis.avplay.getCurrentStreamInfo().filter(function (track) { return track.type === 'TEXT'})[0];
var track = nextTrack("TEXT");
console.log('captions', captionsOn, track.num);
if(track.num === 1) {
captionsOn = !captionsOn;
if(!captionsOn) {
webapis.avplay.setSelectTrack('TEXT', current.index); //reverting to the previous track, so next click will turn track 1 again
$.notify('Субтитры выкл.', 'info');
return;
}
}
captionsOn = true;
$.notify('Дорожка субтитров: ' + track.num + '/' + getTracksCount('TEXT') + ': ' + track.extra_info.track_lang, 'info');
}
function nextTrack(type) {
var current = webapis.avplay.getCurrentStreamInfo().filter(function (track) { return track.type === type})[0],
available = webapis.avplay.getTotalTrackInfo()
.filter(function (track) { return track.type === type})
.map(function(track) {
return {
extra_info: JSON.parse(track.extra_info),
index: parseInt(track.index),
type: track.type
}
}).sort(function(a, b) {
return a.index - b.index
}),
curIdx = available.findIndex(function (track) {
return track.index === parseInt(current.index);
}),
nextIdx = curIdx === available.length - 1 ? 0 : curIdx + 1;
//console.log(current, available, curIdx, nextIdx);
webapis.avplay.setSelectTrack(type, available[nextIdx].index);
return Object.assign(available[nextIdx], {num: nextIdx + 1});
}
function getTracksCount(type) {
return webapis.avplay.getTotalTrackInfo().filter(function (track) { return track.type === type}).length;
}
function formatTime(seconds) {
var hh = Math.floor(seconds / 3600),
mm = Math.floor(seconds / 60) % 60,
ss = Math.floor(seconds) % 60;
return (hh ? (hh < 10 ? "0" : "") + hh + ":" : "") +
((mm < 10) ? "0" : "") + mm + ":" +
((ss < 10) ? "0" : "") + ss;
}
function interceptSizeSelectBox() {
triggerHoverEnter(this);
//this solution sucks ass :(
$('.selectbox .selectbox-item__title').each(function() {
switch($(this).text()) {
case 'По умолчанию':
$(this).parent().attr('data-size-select', 'PLAYER_DISPLAY_MODE_LETTER_BOX');
break;
case 'Расширить':
$(this).parent().attr('data-size-select', 'PLAYER_DISPLAY_MODE_FULL_SCREEN');
break;
default:
console.log('[avplay]: Unknown size title!');
$(this).parent().attr('data-size-select', 'PLAYER_DISPLAY_MODE_LETTER_BOX');
}
});
$('[data-size-select]').on('click', function() {
webapis.avplay.setDisplayMethod($(this).data('size-select'));
})
}
function onPlaylistClick(e) {
triggerHoverEnter(e.target);
$('.selectbox-item').on('click', itemClick);
function itemClick(e) {
$(e.target).trigger('hover:enter');
$('.selectbox-item').off('click', itemClick);
}
}
function onPrevNext(e) {
triggerHoverEnter(e.target);
}
function triggerHoverEnter(elem) {
$(elem).trigger('hover:enter');
}
function toggleLoadingState(isShow, msg) {
msg = msg || ''
if(isShow) {
$('.player-video').addClass('video--load');
} else {
$('.player-video').removeClass('video--load');
}
$('.player-video__loader').html(msg);
}
function isLoading() {
return $('.player-video').hasClass('video--load');
}
function getTorrentStats() {
fetch(src.replace('play', 'stat')).then(function(response) {
return response.json();
}).then(function(data) {
$('#tizen-torr-info').html('<span>Пиры: ' + data.active_peers + '/' + data.total_peers + ' Сиды: ' + data.connected_seeders + '</span><br>' +
'<span>' + humanizeSpeed(data.download_speed) + '</span>');
if(isLoading() && data.preloaded_bytes < data.preload_size) {
toggleLoadingState(true, 'Предзагрузка ' + Math.ceil((data.preloaded_bytes / data.preload_size) * 100) + '%');
}
});
}
function humanizeSpeed(speed) {
if (!speed) return ''
var i = Math.floor(Math.log(speed * 8) / Math.log(1000))
return (((speed * 8) / Math.pow(1000, i)).toFixed(0) * 1) + ['бит/c', 'кбит/с', 'Мбит/c', 'Гбит/c', "Тбит/с"][i];
};
function clock() {
var date = new Date(),
hours = (date.getHours() < 10) ? '0' + date.getHours() : date.getHours(),
minutes = (date.getMinutes() < 10) ? '0' + date.getMinutes() : date.getMinutes(),
seconds = (date.getSeconds() < 10) ? '0' + date.getSeconds() : date.getSeconds();
document.getElementById('tizen-clock').innerHTML = hours + ':' + minutes;
}
function exitAndClenaup() {
webapis.avplay.stop();
webapis.avplay.close();
document.removeEventListener('keydown', interceptControls);
document.removeEventListener('keyup', interceptKeyUp);
$('.player-panel__size').off('click', interceptSizeSelectBox);
$('.player-panel__playpause').off('click', playPause);
$('.player-panel__playlist').off('click', onPlaylistClick);
$('.player-panel__prev').off('click', onPrevNext);
$('.player-panel__next').off('click', onPrevNext);
seek = resetSeekObj();
clearTimeout(timerSub);
clearInterval(clockInt);
$('.player-video__display object').remove();
clearInterval(torrentStatsInt);
src = null;
isPlayerReady = false;
history.back();
}
function resetSeekObj() {
return {
fw: {timePassed: 0},
bw: {timePassed: 0},
isSeek: false,
isPressed: false,
endTimeout: null,
dst: 0,
interval: null,
speed: 1
}
}
return playerInjector();
}
var timerSub;
document.addEventListener("DOMContentLoaded", function(event) {
mod();
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment