-
-
Save ProgressoRU/ae37235a622cbbfeea202be5c7ca8346 to your computer and use it in GitHub Desktop.
Tizen AVPlay for Lampa
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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