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 `

${name} ${author}
`; } 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 = ""; 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