mirror of
https://github.com/openvk/openvk
synced 2024-12-26 02:21:25 +03:00
359 lines
14 KiB
JavaScript
359 lines
14 KiB
JavaScript
|
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) {
|
|||
|
name = escapeHtml(name);
|
|||
|
author = escapeHtml(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.open('https://github.com/celestora');">
|
|||
|
- celestora
|
|||
|
</span>
|
|||
|
<hr/>
|
|||
|
<span class="bsdn_contextMenuElement" onclick="window.open('https://github.com/openvk/openvk/issues/new');">
|
|||
|
Report a problem...
|
|||
|
</span>
|
|||
|
<span class="bsdn_contextMenuElement" onclick="window.open('https://www.youtube.com/watch?v=4Hq53bN34_w');">About Adobe Flash Player...</span>
|
|||
|
</div>
|
|||
|
|
|||
|
<div class="bsdn_controls">
|
|||
|
<div>
|
|||
|
<button class="bsdn_playButton">
|
|||
|
<img src="/assets/packages/static/openvk/img/bsdn/play.png" style="padding-right: 2px; padding-top: 3px;">
|
|||
|
</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_copyVideoUrl": {
|
|||
|
click: [
|
|||
|
async () => {
|
|||
|
let videoUrl = el.querySelector(".bsdn_video > video").src;
|
|||
|
let fallback = () => {
|
|||
|
prompt("URL:", videoUrl);
|
|||
|
};
|
|||
|
|
|||
|
if(typeof navigator.clipboard == "undefined") {
|
|||
|
fallback();
|
|||
|
} else {
|
|||
|
try {
|
|||
|
await navigator.clipboard.writeText(videoUrl);
|
|||
|
confirm("👍🏼");
|
|||
|
} catch(e) {
|
|||
|
fallback();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
]
|
|||
|
},
|
|||
|
|
|||
|
".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 = "<img src='/assets/packages/static/openvk/img/bsdn/play.png' style='padding-right: 2px; padding-top: 3px; height: 19px;' />";
|
|||
|
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);
|
|||
|
}
|