openvk/Web/static/js/player.js

331 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

function _bsdnUnwrapBitMask(number) {
return number.toString(2).split("").reverse().map(x => x === "1");
}
function _bsdnToHumanTime(time) {
time = Math.ceil(time);
let mins = Math.floor(time / 60);
let secs = (time - (mins * 60));
if(secs < 10)
secs = "0" + secs;
if(mins < 10)
mins = "0" + mins;
return mins + ":" + secs;
}
function _bsdnTpl(name, author) {
return `
<div class="bsdn_contextMenu" style="display: none;">
<span class="bsdn_contextMenuElement bsdn_copyVideoUrl">Copy video link to clipboard</span>
<hr/>
<span class="bsdn_contextMenuElement">OpenVK BSDN///Player 0.1</span>
<hr/>
<span class="bsdn_contextMenuElement">Developers:</span>
<span class="bsdn_contextMenuElement" onclick="window.location.assign('https://github.com/celestora');">
- celstora
</span>
<span class="bsdn_contextMenuElement" onclick="window.location.assign('https://github.com/openvk/openvk/issues');">
- Report a problem...
</span>
<hr/>
<span class="bsdn_contextMenuElement" onclick="confirm('эм это шутка');">About Adobe Flash Player...</span>
</div>
<div class="bsdn_controls">
<div>
<button class="bsdn_playButton">▶</button>
</div>
<div class="bsdn_terebilkaWrap">
<div class="bsdn_terebilkaUpperWrap">
<img class="bsdn_logo" src="/assets/packages/static/openvk/img/bsdn/logo.gif" style="opacity: 0; /* TODO add logo xdd */" />
<p class="bsdn_timeWrap">
<time class="bsdn_timeReal">--:--</time>
<time class="bsdn_timeFull">--:--</time>
</p>
</div>
<div class="bsdn_terebilkaLowerWrap">
<div class="bsdn_terebilkaBrick"></div>
</div>
</div>
<div>
<img class="bsdn_soundIcon" src="/assets/packages/static/openvk/img/bsdn/speaker.gif" />
</div>
<div class="bsdn_soundControl">
<div class="bsdn_soundControlPadding"></div>
<div class="bsdn_soundControlSubWrap">
<div class="bsdn_soundControlBrick" style="left: calc(100% - 10px);"></div>
</div>
</div>
<div>
<div class="bsdn_fullScreenButton">
<img src="/assets/packages/static/openvk/img/bsdn/fullscreen.gif" />
</div>
</div>
</div>
<div class="bsdn_teaserWrap">
<div class="bsdn_teaser">
<div class="bsdn_teaserTitleBox">
<b>${name}</b>
<span>${author}</span>
</div>
<div class="bsdn_teaserButton">
<img src="/assets/packages/static/openvk/img/bsdn/play.png" />
</div>
</div>
</div>
`;
}
function _bsdnTerebilkaEventFactory(el, terebilka, callback, otherListeners) {
let terebilkaSize = () => el.querySelector(terebilka).getBoundingClientRect().width; // чтобы просралось
let listeners = {
mousemove: [
e => {
let buttonsPresseed = _bsdnUnwrapBitMask(e.buttons);
if(!buttonsPresseed[0])
return; // user doesn't click so nothing should be done
let offset = e.offsetX;
let percents = Math.max(0, Math.min(100, offset / (terebilkaSize() / 100)));
return callback(percents);
}
],
mousedown: [
e => {
let offset = e.offsetX;
let percents = Math.max(0, Math.min(100, offset / (terebilkaSize() / 100)));
return callback(percents);
}
]
};
for(eventName in (otherListeners || {})) {
if(listeners.hasOwnProperty(eventName))
listeners[eventName] = otherListeners[eventName].concat(listeners[eventName]);
else
listeners[eventName] = otherListeners[eventName];
}
return listeners;
}
function _bsdnEventListenerFactory(el, v) {
return {
".bsdn-player": {
click: [
e => {
if(el.querySelector(".bsdn_controls").contains(e.target) || el.querySelector(".bsdn_teaser").contains(e.target) || el.querySelector(".bsdn_contextMenu").contains(e.target))
return;
if(el.querySelector(".bsdn_contextMenu").style.display !== "none") {
el.querySelector(".bsdn_contextMenu").style.display = "none";
return;
}
if(v.paused)
v.play();
else
v.pause();
}
],
contextmenu: [
e => {
e.preventDefault();
if(el.querySelector(".bsdn_controls").contains(e.target) || el.querySelector(".bsdn_contextMenu").contains(e.target))
return;
let rect = el.querySelector(".bsdn-player").getBoundingClientRect();
let h = rect.height, w = rect.width;
let x, y;
if(document.fullscreen) {
x = e.screenX;
y = e.screenY;
} else {
let rx = rect.x + window.scrollX, ry = rect.y + window.scrollY;
x = e.pageX - rx;
y = e.pageY - ry;
}
if(h - y < 169)
y = Math.max(0, y - 169);
if(w - x < 238)
x = Math.max(0, x - 238);
let menu = el.querySelector(".bsdn_contextMenu");
menu.style.top = y + "px";
menu.style.left = x + "px";
menu.style.display = "unset";
}
]
},
".bsdn_contextMenuElement": {
click: [ () => el.querySelector(".bsdn_contextMenu").style.display = "none" ]
},
".bsdn_video > video": {
play: [
() => {
if(!el.querySelector(".bsdn-player").classList.contains("bsdn-dirty"))
el.querySelector(".bsdn-player").classList.add("bsdn-dirty")
el.querySelector(".bsdn_playButton").innerHTML = "<img src='/assets/packages/static/openvk/img/bsdn/pause.gif' style='padding-right: 3px; padding-top: 3px;' />";
el.querySelector(".bsdn-player").classList.add("_bsdn_playing");
el.querySelector(".bsdn_teaserWrap").style.display = "none";
}
],
pause: [
() => {
el.querySelector(".bsdn_playButton").innerHTML = "▶";
el.querySelector(".bsdn-player").classList.remove("_bsdn_playing");
el.querySelector(".bsdn_teaserWrap").style.display = "flex";
}
],
timeupdate: [
() => {
el.querySelector(".bsdn_timeReal").innerHTML = _bsdnToHumanTime(v.currentTime);
let terebilkaSize = el.querySelector(".bsdn_terebilkaLowerWrap").getBoundingClientRect().width;
let brickSize = 15;
let percents = Math.ceil(v.currentTime / (v.duration / 100));
let offset = ((terebilkaSize - brickSize) / 100) * percents;
el.querySelector(".bsdn_terebilkaBrick").style.left = `min(calc(100% - 15px), ${offset}px`; // смешной мясной костыль ибо мне лень делать onresize
}
],
volumechange: [
() => {
if(v.volume === 0)
el.querySelector(".bsdn_soundIcon").src = "/assets/packages/static/openvk/img/bsdn/speaker_muted.gif";
else
el.querySelector(".bsdn_soundIcon").src = "/assets/packages/static/openvk/img/bsdn/speaker.gif";
let scSize = el.querySelector(".bsdn_soundControlSubWrap").getBoundingClientRect().width;
let brickSize = 10;
let offset = (scSize - brickSize) * v.volume;
el.querySelector(".bsdn_soundControlBrick").style.left = offset + "px";
}
],
loadedmetadata: [
() => {
el.querySelector(".bsdn_timeFull").innerHTML = _bsdnToHumanTime(v.duration);
}
]
},
".bsdn_fullScreenButton": {
click: [
() => {
if(document.fullscreen) {
document.exitFullscreen();
} else {
el.querySelector(".bsdn-player").requestFullscreen();
}
}
]
},
".bsdn_teaserButton|.bsdn_playButton": {
click: [
() => {
if(v.paused)
v.play();
else
v.pause();
}
]
},
".bsdn_terebilkaLowerWrap": _bsdnTerebilkaEventFactory(el, ".bsdn_terebilkaLowerWrap", function(p) {
let time = (v.duration / 100) * p;
setTimeout(() => {
v.currentTime = time;
if(v.currentTime === 0) {
console.warn("[!] Хромог момент");
console.warn("Теребилка не работает в хроме если сервер не реализует HTTP полностью.");
console.warn("Встроенный сервер РНР не возвращает заголовки Accept-Range из-за чего хром отказывается seek'ать. Google как всегда.");
console.warn("Установите Firefox для лучшей безопасности в сети: https://www.mozilla.org/ru/firefox/enterprise/#download");
}
}, 0);
}, {
mousedown: [
e => v.pause()
],
mouseup: [
e => v.play()
]
}),
".bsdn_soundControlSubWrap": _bsdnTerebilkaEventFactory(el, ".bsdn_soundControlSubWrap", function(p) {
let volume = p / 100;
v.volume = volume;
}),
".bsdn_soundIcon": {
click: [
e => v.volume = v.volume === 0 ? 0.75 : 0
]
}
}
}
function _bsdnApplyBindings(el, v) {
let listeners = _bsdnEventListenerFactory(el, v);
for(key in listeners) {
let selectors = key.split("|");
selectors.forEach(sel => {
for(eventName in listeners[key]) {
listeners[key][eventName].forEach(listener => {
el.querySelectorAll(sel).forEach(target => {
target.addEventListener(eventName, listener, {
passive: (["contextmenu"]).indexOf(eventName) === -1
});
});
});
}
});
}
}
function bsdnInitElement(el) {
if(el.querySelector(".bdsn-hydrated") != null) {
console.debug(el, " is already hydrated.");
return;
}
let video = el.querySelector("video");
if(!video) {
console.warning(el, " does not contain any <video>s.");
return;
}
el.innerHTML = `
<div class="bsdn-player bdsn-hydrated">
${_bsdnTpl(el.dataset.name, el.dataset.author)}
<div class="bsdn_video">
${video.outerHTML}
</div>
</div>
`;
video = el.querySelector(".bsdn_video > video");
_bsdnApplyBindings(el, video);
video.volume = 0.75;
}
function bsdnHydrate() {
document.querySelectorAll(".bsdn").forEach(bsdnInitElement);
}