mirror of
https://codeberg.org/ashley/poke.git
synced 2025-06-24 02:57:04 -04:00
owo
This commit is contained in:
parent
f431111611
commit
72143fede3
100 changed files with 12438 additions and 0 deletions
2
core/LightTube/wwwroot/js/hls.js/hls.min.js
vendored
Normal file
2
core/LightTube/wwwroot/js/hls.js/hls.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
735
core/LightTube/wwwroot/js/lt-video/player-desktop.js
Normal file
735
core/LightTube/wwwroot/js/lt-video/player-desktop.js
Normal file
|
@ -0,0 +1,735 @@
|
|||
class Player {
|
||||
constructor(query, info, sources, externalPlayer, externalPlayerType) {
|
||||
// vars
|
||||
this.externalPlayerType = externalPlayerType ?? "html5";
|
||||
this.muted = false;
|
||||
this.info = info;
|
||||
this.sources = sources;
|
||||
this.__videoElement = document.querySelector(query);
|
||||
this.__videoElement.removeAttribute("controls");
|
||||
this.__externalPlayer = externalPlayer;
|
||||
|
||||
// container
|
||||
const container = document.createElement("div");
|
||||
container.classList.add("player");
|
||||
this.__videoElement.parentElement.appendChild(container);
|
||||
container.appendChild(this.__videoElement);
|
||||
this.container = container;
|
||||
if (info.embed) {
|
||||
this.container.classList.add("embed");
|
||||
this.__videoElement.classList.remove("embed");
|
||||
}
|
||||
|
||||
// default source
|
||||
switch (this.externalPlayerType) {
|
||||
case "html5":
|
||||
for (let source of sources) {
|
||||
if (source.height <= 720) {
|
||||
this.__videoElement.src = source.src;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "hls.js":
|
||||
for (let level = this.__externalPlayer.levels.length - 1; level >= 0; level--) {
|
||||
if (this.__externalPlayer.levels[level].height <= 720) {
|
||||
this.__externalPlayer.currentLevel = level;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "shaka":
|
||||
let variants = this.__externalPlayer.getVariantTracks();
|
||||
for (let variant = variants.length - 1; variant >= 0; variant--) {
|
||||
let v = variants[variant];
|
||||
if (v.height <= 720) {
|
||||
this.__externalPlayer.selectVariantTrack(v, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// controls
|
||||
const createButton = (tag, icon) => {
|
||||
const b = document.createElement(tag);
|
||||
b.classList.add("player-button");
|
||||
if (icon !== "")
|
||||
b.innerHTML = `<i class="bi bi-${icon}"></i>`;
|
||||
return b;
|
||||
}
|
||||
|
||||
this.controls = {
|
||||
play: createButton("div", "play-fill"),
|
||||
pause: createButton("div", "pause-fill"),
|
||||
volume: createButton("div", "volume-up-fill"),
|
||||
time: document.createElement("span"),
|
||||
skipToLive: createButton("div", "skip-forward-fill"),
|
||||
div: document.createElement("div"),
|
||||
settings: createButton("div", "gear-fill"),
|
||||
embed: createButton("a", ""),
|
||||
pip: createButton("div", "pip"),
|
||||
fullscreen: createButton("div", "fullscreen")
|
||||
}
|
||||
|
||||
if (!info.live) this.controls.skipToLive.style.display = "none"
|
||||
|
||||
const controlHolder = document.createElement("div");
|
||||
controlHolder.classList.add("player-controls");
|
||||
|
||||
this.controls.embed.innerHTML = "<span style='text-align: center; width: 100%'>l<b>t</b></span>";
|
||||
this.controls.embed.setAttribute("target", "_blank");
|
||||
this.controls.embed.setAttribute("href", "/watch?v=" + info.id);
|
||||
if (!info.embed) this.controls.embed.style.display = "none";
|
||||
|
||||
const els = [
|
||||
document.createElement("div"),
|
||||
document.createElement("div"),
|
||||
]
|
||||
for (const padding of els)
|
||||
padding.classList.add("player-controls-padding");
|
||||
|
||||
controlHolder.appendChild(els[0]);
|
||||
for (const control of Object.values(this.controls)) {
|
||||
controlHolder.appendChild(control);
|
||||
}
|
||||
controlHolder.appendChild(els[1]);
|
||||
container.appendChild(controlHolder);
|
||||
|
||||
this.controls.play.onclick = () => this.togglePlayPause();
|
||||
this.controls.pause.onclick = () => this.togglePlayPause();
|
||||
this.controls.volume.onclick = e => this.mute(e);
|
||||
this.controls.volume.classList.add("player-volume");
|
||||
this.controls.fullscreen.onclick = () => this.fullscreen();
|
||||
this.controls.skipToLive.onclick = () => this.skipToLive();
|
||||
|
||||
if (document.pictureInPictureEnabled === true)
|
||||
this.controls.pip.onclick = () => this.pip();
|
||||
else
|
||||
this.controls.pip.style.display = "none";
|
||||
|
||||
let vol = null;
|
||||
if (localStorage !== undefined)
|
||||
vol = localStorage?.getItem("ltvideo.volume");
|
||||
let volumeRange = document.createElement("input");
|
||||
volumeRange.oninput = e => this.setVolume(e);
|
||||
volumeRange.setAttribute("min", "0");
|
||||
volumeRange.setAttribute("max", "1");
|
||||
volumeRange.setAttribute("step", "0.01");
|
||||
volumeRange.setAttribute("value", vol ?? "1");
|
||||
volumeRange.setAttribute("type", "range");
|
||||
if (vol != null)
|
||||
this.setVolume({target: {value: Number(vol)}});
|
||||
this.controls.volume.appendChild(volumeRange);
|
||||
|
||||
this.controls.div.classList.add("player-button-divider")
|
||||
|
||||
// playback bar
|
||||
this.playbackBar = {
|
||||
bg: document.createElement("div"),
|
||||
played: document.createElement("div"),
|
||||
buffered: document.createElement("div"),
|
||||
hover: document.createElement("div"),
|
||||
sb: document.createElement("div"),
|
||||
sbC: document.createElement("div"),
|
||||
hoverText: document.createElement("span")
|
||||
}
|
||||
this.playbackBar.bg.classList.add("player-playback-bar");
|
||||
this.playbackBar.bg.classList.add("player-playback-bar-bg");
|
||||
this.playbackBar.played.classList.add("player-playback-bar");
|
||||
this.playbackBar.played.classList.add("player-playback-bar-fg");
|
||||
this.playbackBar.buffered.classList.add("player-playback-bar");
|
||||
this.playbackBar.buffered.classList.add("player-playback-bar-buffer");
|
||||
this.playbackBar.bg.appendChild(this.playbackBar.buffered);
|
||||
this.playbackBar.bg.appendChild(this.playbackBar.played);
|
||||
|
||||
this.playbackBar.hover.classList.add("player-playback-bar-hover");
|
||||
if (!this.info.live) {
|
||||
this.playbackBar.sb.classList.add("player-storyboard-image");
|
||||
this.playbackBar.sbC.classList.add("player-storyboard-image-container");
|
||||
this.playbackBar.sb.style.backgroundImage = `url("/proxy/storyboard/${info.id}")`;
|
||||
this.playbackBar.sbC.appendChild(this.playbackBar.sb);
|
||||
} else {
|
||||
this.playbackBar.sb.remove();
|
||||
}
|
||||
|
||||
let playbackBarContainer = document.createElement("div");
|
||||
playbackBarContainer.classList.add("player-playback-bar-container")
|
||||
this.playbackBar.bg.onclick = e => {
|
||||
this.playbackBarSeek(e)
|
||||
}
|
||||
this.playbackBar.bg.ondragover = e => {
|
||||
this.playbackBarSeek(e)
|
||||
}
|
||||
this.playbackBar.bg.onmouseenter = () => {
|
||||
this.playbackBar.hover.style.display = "block";
|
||||
}
|
||||
this.playbackBar.bg.onmouseleave = () => {
|
||||
this.playbackBar.hover.style.display = "none";
|
||||
}
|
||||
this.playbackBar.bg.onmousemove = e => {
|
||||
this.moveHover(e)
|
||||
}
|
||||
playbackBarContainer.appendChild(this.playbackBar.bg);
|
||||
this.playbackBar.hover.appendChild(this.playbackBar.sbC)
|
||||
this.playbackBar.hover.appendChild(this.playbackBar.hoverText)
|
||||
playbackBarContainer.appendChild(this.playbackBar.hover);
|
||||
container.appendChild(playbackBarContainer);
|
||||
|
||||
// title
|
||||
this.titleElement = document.createElement("div");
|
||||
this.titleElement.classList.add("player-title");
|
||||
this.titleElement.innerText = info.title;
|
||||
container.appendChild(this.titleElement);
|
||||
if (!info.embed)
|
||||
this.titleElement.style.display = "none";
|
||||
|
||||
// events
|
||||
container.onfullscreenchange = () => {
|
||||
if (!document.fullscreenElement) {
|
||||
this.controls.fullscreen.querySelector("i").setAttribute("class", "bi bi-fullscreen");
|
||||
if (!info.embed)
|
||||
this.titleElement.style.display = "none";
|
||||
} else {
|
||||
this.titleElement.style.display = "block";
|
||||
this.controls.fullscreen.querySelector("i").setAttribute("class", "bi bi-fullscreen-exit");
|
||||
}
|
||||
}
|
||||
const updatePlayButtons = () => {
|
||||
if (this.__videoElement.paused) {
|
||||
this.controls.pause.style.display = "none";
|
||||
this.controls.play.style.display = "block";
|
||||
} else {
|
||||
this.controls.pause.style.display = "block";
|
||||
this.controls.play.style.display = "none";
|
||||
}
|
||||
}
|
||||
this.__videoElement.onplay = () => updatePlayButtons();
|
||||
this.__videoElement.onpause = () => updatePlayButtons();
|
||||
updatePlayButtons();
|
||||
this.__videoElement.onclick = () => this.togglePlayPause();
|
||||
this.__videoElement.ondblclick = () => this.fullscreen();
|
||||
this.container.onkeydown = e => this.keyboardHandler(e);
|
||||
|
||||
this.container.onmousemove = () => {
|
||||
let d = new Date();
|
||||
d.setSeconds(d.getSeconds() + 3);
|
||||
this.controlsDisappearTimeout = d.getTime();
|
||||
}
|
||||
|
||||
switch (this.externalPlayerType) {
|
||||
case "shaka":
|
||||
externalPlayer.addEventListener("variantchanged", () => {
|
||||
this.updateMenu();
|
||||
});
|
||||
externalPlayer.addEventListener('error', this.fallbackFromShaka);
|
||||
break;
|
||||
case "hls.js":
|
||||
// uhhhhhh...
|
||||
break;
|
||||
}
|
||||
|
||||
// menu
|
||||
this.controls.settings.onclick = e => this.menuButtonClick(e);
|
||||
this.controls.settings.setAttribute("data-action", "toggle");
|
||||
this.controls.settings.querySelector("i").setAttribute("data-action", "toggle");
|
||||
this.updateMenu(sources);
|
||||
|
||||
// buffering
|
||||
this.bufferingScreen = document.createElement("div");
|
||||
this.bufferingScreen.classList.add("player-buffering");
|
||||
this.container.appendChild(this.bufferingScreen);
|
||||
|
||||
let bufferingSpinner = document.createElement("img");
|
||||
bufferingSpinner.classList.add("player-buffering-spinner");
|
||||
bufferingSpinner.src = "/img/spinner.gif";
|
||||
this.bufferingScreen.appendChild(bufferingSpinner);
|
||||
|
||||
setInterval(() => this.update(), 100);
|
||||
}
|
||||
|
||||
togglePlayPause() {
|
||||
if (this.__videoElement.paused)
|
||||
this.__videoElement.play();
|
||||
else
|
||||
this.__videoElement.pause();
|
||||
}
|
||||
|
||||
updateMenu() {
|
||||
const makeButton = (label, action, icon) => {
|
||||
const b = document.createElement("div");
|
||||
//todo: yes fix this
|
||||
b.innerHTML = `<i class="bi bi-${icon}"></i>${label}`;
|
||||
b.onclick = e => this.menuButtonClick(e);
|
||||
b.setAttribute("data-action", action)
|
||||
b.classList.add("player-menu-item")
|
||||
return b;
|
||||
}
|
||||
|
||||
const makeMenu = (id, buttons) => {
|
||||
const menu = document.createElement("div");
|
||||
menu.id = id;
|
||||
for (const button of buttons) {
|
||||
menu.appendChild(makeButton(button.label, button.action, button.icon));
|
||||
}
|
||||
return menu;
|
||||
}
|
||||
|
||||
if (this.menuElement) {
|
||||
this.menuElement.remove();
|
||||
this.menuElement = undefined;
|
||||
}
|
||||
this.menuElement = document.createElement("div");
|
||||
this.menuElement.classList.add("player-menu");
|
||||
this.menuElement.appendChild(makeMenu("menu-main", [
|
||||
{
|
||||
icon: "sliders",
|
||||
label: "Quality",
|
||||
action: "menu res"
|
||||
},
|
||||
{
|
||||
icon: "badge-cc",
|
||||
label: "Subtitles",
|
||||
action: "menu sub"
|
||||
},
|
||||
{
|
||||
icon: "speedometer2",
|
||||
label: "Speed",
|
||||
action: "menu speed"
|
||||
}
|
||||
]))
|
||||
const resButtons = [
|
||||
{
|
||||
icon: "arrow-left",
|
||||
label: "Back",
|
||||
action: "menu main"
|
||||
}
|
||||
]
|
||||
|
||||
switch (this.externalPlayerType) {
|
||||
case "html5":
|
||||
for (const index in this.sources) {
|
||||
resButtons.push({
|
||||
icon: this.sources[index].src === this.__videoElement.src ? "check2" : "",
|
||||
label: this.sources[index].label,
|
||||
action: "videosrc " + index
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "shaka":
|
||||
resButtons.pop();
|
||||
let tracks = this.__externalPlayer.getVariantTracks();
|
||||
for (const index in tracks) {
|
||||
if (tracks[index].audioId === 2)
|
||||
resButtons.unshift({
|
||||
icon: tracks[index].active ? "check2" : "",
|
||||
label: tracks[index].height + "p",
|
||||
action: "shakavariant " + index
|
||||
});
|
||||
}
|
||||
resButtons.unshift({
|
||||
icon: this.__externalPlayer.getConfiguration().abr.enabled ? "check2" : "",
|
||||
label: "Auto",
|
||||
action: "shakavariant -1"
|
||||
});
|
||||
resButtons.unshift(
|
||||
{
|
||||
icon: "arrow-left",
|
||||
label: "Back",
|
||||
action: "menu main"
|
||||
});
|
||||
break;
|
||||
case "hls.js":
|
||||
resButtons.pop();
|
||||
for (const level in this.__externalPlayer.levels) {
|
||||
resButtons.unshift({
|
||||
icon: level === this.__externalPlayer.currentLevel ? "check2" : "",
|
||||
label: this.__externalPlayer.levels[level].height + "p",
|
||||
action: "hlslevel " + level
|
||||
});
|
||||
}
|
||||
resButtons.unshift(
|
||||
{
|
||||
icon: -1 === this.__externalPlayer.currentLevel ? "check2" : "",
|
||||
label: "Auto",
|
||||
action: "hlslevel -1"
|
||||
});
|
||||
resButtons.unshift(
|
||||
{
|
||||
icon: "arrow-left",
|
||||
label: "Back",
|
||||
action: "menu main"
|
||||
});
|
||||
break;
|
||||
}
|
||||
this.menuElement.appendChild(makeMenu("menu-res", resButtons));
|
||||
const subButtons = [
|
||||
{
|
||||
icon: "arrow-left",
|
||||
label: "Back",
|
||||
action: "menu main"
|
||||
}
|
||||
]
|
||||
|
||||
for (let index = 0; index < this.__videoElement.textTracks.length; index++) {
|
||||
if (this.__videoElement.textTracks[index].label.includes("Shaka Player")) continue;
|
||||
subButtons.push({
|
||||
icon: this.__videoElement.textTracks[index].mode === "showing" ? "check2" : "",
|
||||
label: this.__videoElement.textTracks[index].label,
|
||||
action: "texttrack " + index
|
||||
});
|
||||
}
|
||||
this.menuElement.appendChild(makeMenu("menu-sub", subButtons));
|
||||
this.menuElement.appendChild(makeMenu("menu-speed", [
|
||||
{
|
||||
icon: "arrow-left",
|
||||
label: "Back",
|
||||
action: "menu main"
|
||||
},
|
||||
{
|
||||
icon: this.__videoElement.playbackRate === 0.25 ? "check2" : "",
|
||||
label: "0.25",
|
||||
action: "speed 0.25"
|
||||
},
|
||||
{
|
||||
icon: this.__videoElement.playbackRate === 0.50 ? "check2" : "",
|
||||
label: "0.50",
|
||||
action: "speed 0.5"
|
||||
},
|
||||
{
|
||||
icon: this.__videoElement.playbackRate === 0.75 ? "check2" : "",
|
||||
label: "0.75",
|
||||
action: "speed 0.75"
|
||||
},
|
||||
{
|
||||
icon: this.__videoElement.playbackRate === 1 ? "check2" : "",
|
||||
label: "Normal",
|
||||
action: "speed 1"
|
||||
},
|
||||
{
|
||||
icon: this.__videoElement.playbackRate === 1.25 ? "check2" : "",
|
||||
label: "1.25",
|
||||
action: "speed 1.25"
|
||||
},
|
||||
{
|
||||
icon: this.__videoElement.playbackRate === 1.50 ? "check2" : "",
|
||||
label: "1.50",
|
||||
action: "speed 1.5"
|
||||
},
|
||||
{
|
||||
icon: this.__videoElement.playbackRate === 1.75 ? "check2" : "",
|
||||
label: "1.75",
|
||||
action: "speed 1.75"
|
||||
},
|
||||
{
|
||||
icon: this.__videoElement.playbackRate === 2 ? "check2" : "",
|
||||
label: "2",
|
||||
action: "speed 2"
|
||||
},
|
||||
]))
|
||||
|
||||
this.container.appendChild(this.menuElement);
|
||||
for (const child of this.menuElement.children) {
|
||||
if (child.tagName === "DIV")
|
||||
child.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
openMenu(id) {
|
||||
for (const child of this.menuElement.children) {
|
||||
if (child.tagName === "DIV")
|
||||
child.style.display = "none";
|
||||
}
|
||||
try {
|
||||
this.menuElement.querySelector("#menu-" + id).style.display = "block";
|
||||
} catch {
|
||||
// intended
|
||||
}
|
||||
}
|
||||
|
||||
menuButtonClick(e) {
|
||||
let args = (e.target.getAttribute("data-action") ?? e.target.parentElement.getAttribute("data-action")).split(" ");
|
||||
let command = args.shift();
|
||||
let closeMenu = true;
|
||||
switch (command) {
|
||||
case "toggle":
|
||||
closeMenu = this.menuElement.clientHeight !== 0;
|
||||
if (!closeMenu)
|
||||
this.openMenu("main");
|
||||
break;
|
||||
case "menu":
|
||||
this.openMenu(args[0]);
|
||||
closeMenu = false;
|
||||
break;
|
||||
case "speed":
|
||||
this.__videoElement.playbackRate = Number.parseFloat(args[0]);
|
||||
this.updateMenu();
|
||||
break;
|
||||
case "texttrack":
|
||||
let i = Number.parseFloat(args[0]);
|
||||
for (let index = 0; index < this.__videoElement.textTracks.length; index++) {
|
||||
this.__videoElement.textTracks[index].mode = "hidden";
|
||||
|
||||
}
|
||||
this.__videoElement.textTracks[i].mode = "showing";
|
||||
this.updateMenu();
|
||||
break;
|
||||
case "videosrc":
|
||||
let time = this.__videoElement.currentTime;
|
||||
let shouldPlay = !this.__videoElement.paused;
|
||||
this.__videoElement.src = this.sources[Number.parseFloat(args[0])].src;
|
||||
this.__videoElement.currentTime = time;
|
||||
if (shouldPlay)
|
||||
this.__videoElement.play();
|
||||
this.updateMenu();
|
||||
break;
|
||||
case "shakavariant":
|
||||
if (args[0] !== "-1")
|
||||
this.__externalPlayer.selectVariantTrack(this.__externalPlayer.getVariantTracks()[Number.parseFloat(args[0])], true, 2)
|
||||
this.__externalPlayer.configure({abr: {enabled: args[0] === "-1"}})
|
||||
break;
|
||||
case "hlslevel":
|
||||
this.__externalPlayer.nextLevel = Number.parseInt(args[0]);
|
||||
break;
|
||||
}
|
||||
if (closeMenu)
|
||||
this.openMenu();
|
||||
};
|
||||
|
||||
mute(e) {
|
||||
if (e.target.tagName === "INPUT") return;
|
||||
this.muted = !this.muted;
|
||||
if (this.muted) {
|
||||
this.controls.volume.querySelector("i").setAttribute("class", "bi bi-volume-mute-fill");
|
||||
this.__videoElement.volume = 0;
|
||||
} else {
|
||||
this.controls.volume.querySelector("i").setAttribute("class", "bi bi-volume-up-fill");
|
||||
this.__videoElement.volume = this.controls.volume.querySelector("input").value;
|
||||
}
|
||||
}
|
||||
|
||||
fullscreen() {
|
||||
if (!document.fullscreenElement) {
|
||||
this.container.requestFullscreen();
|
||||
} else {
|
||||
document.exitFullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
pip() {
|
||||
this.__videoElement.requestPictureInPicture();
|
||||
}
|
||||
|
||||
timeUpdate() {
|
||||
if (this.info.live) {
|
||||
let timeBack = this.__videoElement.duration - this.__videoElement.currentTime;
|
||||
this.controls.time.innerHTML = timeBack > 10 ? this.getTimeString(timeBack) : "LIVE";
|
||||
} else
|
||||
this.controls.time.innerHTML = this.getTimeString(this.__videoElement.currentTime) + " / " + this.getTimeString(this.__videoElement.duration);
|
||||
this.playbackBar.played.style.width = ((this.__videoElement.currentTime / this.__videoElement.duration) * 100) + "%";
|
||||
this.playbackBar.buffered.style.width = ((this.getLoadEnd() / this.__videoElement.duration) * 100) + "%";
|
||||
|
||||
if (this.controlsDisappearTimeout - Date.now() < 0 && !this.container.classList.contains("hide-controls") && !this.__videoElement.paused)
|
||||
this.container.classList.add("hide-controls");
|
||||
|
||||
|
||||
if (this.controlsDisappearTimeout - Date.now() > 0 && this.container.classList.contains("hide-controls"))
|
||||
this.container.classList.remove("hide-controls");
|
||||
|
||||
if (this.__videoElement.paused && this.container.classList.contains("hide-controls"))
|
||||
this.container.classList.add("hide-controls");
|
||||
}
|
||||
|
||||
setVolume(e) {
|
||||
this.__videoElement.volume = e.target.value;
|
||||
localStorage.setItem("ltvideo.volume", e.target.value);
|
||||
}
|
||||
|
||||
getLoadEnd() {
|
||||
let longest = -1;
|
||||
for (let i = 0; i < this.__videoElement.buffered.length; i++) {
|
||||
const end = this.__videoElement.buffered.end(i);
|
||||
if (end > longest) longest = end;
|
||||
}
|
||||
return longest;
|
||||
}
|
||||
|
||||
playbackBarSeek(e) {
|
||||
let percentage = (e.offsetX / (this.playbackBar.bg.clientLeft + this.playbackBar.bg.clientWidth));
|
||||
this.playbackBar.played.style.width = (percentage * 100) + "%";
|
||||
this.__videoElement.currentTime = this.__videoElement.duration * percentage;
|
||||
}
|
||||
|
||||
moveHover(e) {
|
||||
let percentage = (e.offsetX / (this.playbackBar.bg.clientLeft + this.playbackBar.bg.clientWidth));
|
||||
let rPercent = Math.round(percentage * 100);
|
||||
|
||||
if (!this.info.live) {
|
||||
this.playbackBar.sb.style.backgroundPositionX = `-${rPercent % 10 * 48}px`;
|
||||
this.playbackBar.sb.style.backgroundPositionY = `-${Math.floor(rPercent / 10) * 27}px`;
|
||||
}
|
||||
|
||||
this.playbackBar.hover.style.top = (this.playbackBar.bg.getBoundingClientRect().y - 4 - this.playbackBar.hover.clientHeight) + 'px';
|
||||
this.playbackBar.hover.style.left = (e.clientX - this.playbackBar.hover.clientWidth / 2) + 'px';
|
||||
this.playbackBar.hoverText.innerText = this.getTimeString(this.__videoElement.duration * percentage);
|
||||
}
|
||||
|
||||
skipToLive() {
|
||||
this.__videoElement.currentTime = this.__videoElement.duration;
|
||||
}
|
||||
|
||||
keyboardHandler(e) {
|
||||
let pd = true;
|
||||
if (e.shiftKey || e.ctrlKey || e.altKey || e.metaKey) return;
|
||||
switch (e.code) {
|
||||
case "Space":
|
||||
this.togglePlayPause();
|
||||
break;
|
||||
case "Digit1":
|
||||
if (!this.info.live)
|
||||
this.__videoElement.currentTime = this.__videoElement.duration * 0.1;
|
||||
break;
|
||||
case "Digit2":
|
||||
if (!this.info.live)
|
||||
this.__videoElement.currentTime = this.__videoElement.duration * 0.2;
|
||||
break;
|
||||
case "Digit3":
|
||||
if (!this.info.live)
|
||||
this.__videoElement.currentTime = this.__videoElement.duration * 0.3;
|
||||
break;
|
||||
case "Digit4":
|
||||
if (!this.info.live)
|
||||
this.__videoElement.currentTime = this.__videoElement.duration * 0.4;
|
||||
break;
|
||||
case "Digit5":
|
||||
if (!this.info.live)
|
||||
this.__videoElement.currentTime = this.__videoElement.duration * 0.5;
|
||||
break;
|
||||
case "Digit6":
|
||||
if (!this.info.live)
|
||||
this.__videoElement.currentTime = this.__videoElement.duration * 0.6;
|
||||
break;
|
||||
case "Digit7":
|
||||
if (!this.info.live)
|
||||
this.__videoElement.currentTime = this.__videoElement.duration * 0.7;
|
||||
break;
|
||||
case "Digit8":
|
||||
if (!this.info.live)
|
||||
this.__videoElement.currentTime = this.__videoElement.duration * 0.8;
|
||||
break;
|
||||
case "Digit9":
|
||||
if (!this.info.live)
|
||||
this.__videoElement.currentTime = this.__videoElement.duration * 0.9;
|
||||
break;
|
||||
case "Digit0":
|
||||
if (!this.info.live)
|
||||
this.__videoElement.currentTime = 0;
|
||||
break;
|
||||
case "ArrowLeft":
|
||||
if (!this.info.live)
|
||||
this.__videoElement.currentTime -= 5;
|
||||
break;
|
||||
case "ArrowRight":
|
||||
if (!this.info.live)
|
||||
this.__videoElement.currentTime += 5;
|
||||
break;
|
||||
case "ArrowUp":
|
||||
if (!this.info.live)
|
||||
this.__videoElement.volume += 0.1;
|
||||
break;
|
||||
case "ArrowDown":
|
||||
if (!this.info.live)
|
||||
this.__videoElement.volume -= 0.1;
|
||||
break;
|
||||
case "KeyF":
|
||||
this.fullscreen();
|
||||
break;
|
||||
case "KeyM":
|
||||
this.mute({target: {tagName: ""}});
|
||||
break;
|
||||
default:
|
||||
pd = false;
|
||||
break;
|
||||
}
|
||||
if (pd) e.preventDefault();
|
||||
}
|
||||
|
||||
getTimeString(s) {
|
||||
let res = s < 3600 ? new Date(s * 1000).toISOString().substr(14, 5) : new Date(s * 1000).toISOString().substr(11, 8);
|
||||
if (res.startsWith("0"))
|
||||
res = res.substr(1);
|
||||
return res;
|
||||
}
|
||||
|
||||
update() {
|
||||
this.timeUpdate();
|
||||
|
||||
if (this.info.live) {
|
||||
let timeBack = Math.abs(this.__videoElement.currentTime - this.__videoElement.buffered.end(this.__videoElement.buffered.length - 1));
|
||||
this.bufferingScreen.style.display = timeBack < .1 ? "flex" : "none";
|
||||
} else {
|
||||
switch (this.__videoElement.readyState) {
|
||||
case 1:
|
||||
this.bufferingScreen.style.display = "flex";
|
||||
break;
|
||||
default:
|
||||
this.bufferingScreen.style.display = "none";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fallbackFromShaka() {
|
||||
if (this.externalPlayerType !== "shaka") return;
|
||||
this.externalPlayerType = "html5";
|
||||
console.log("Shaka player crashed, falling back");
|
||||
let cTime = this.__videoElement.currentTime;
|
||||
await this.__externalPlayer.detach();
|
||||
await this.__externalPlayer.destroy();
|
||||
this.__videoElement.src = this.sources[0].src;
|
||||
this.__externalPlayer = undefined;
|
||||
this.__videoElement.currentTime = cTime;
|
||||
this.updateMenu();
|
||||
console.log("Fallback complete!");
|
||||
}
|
||||
}
|
||||
|
||||
const loadPlayerWithShaka = async (query, info, sources, manifestUri) => {
|
||||
let player;
|
||||
if (manifestUri !== undefined) {
|
||||
shaka.polyfill.installAll();
|
||||
let shakaUsable = shaka.Player.isBrowserSupported();
|
||||
|
||||
if (shakaUsable) {
|
||||
const video = document.querySelector(query);
|
||||
player = new shaka.Player(video);
|
||||
|
||||
try {
|
||||
await player.load(manifestUri);
|
||||
} catch (e) {
|
||||
await player.destroy();
|
||||
return new Player(query, info, sources, undefined, "html5");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Player(query, info, sources, await player, "shaka");
|
||||
}
|
||||
|
||||
const loadPlayerWithHls = (query, info, manifestUri) => {
|
||||
return new Promise((res, rej) => {
|
||||
let hls;
|
||||
|
||||
const video = document.querySelector(query);
|
||||
|
||||
if (Hls.isSupported()) {
|
||||
hls = new Hls();
|
||||
hls.loadSource(manifestUri);
|
||||
hls.attachMedia(video);
|
||||
hls.on(Hls.Events.MANIFEST_PARSED, function (event, data) {
|
||||
res(new Player(query, info, [], hls, "hls.js"));
|
||||
});
|
||||
} else
|
||||
rej("You can't watch livestreams / premieres because hls.js is not supported in your browser.")
|
||||
})
|
||||
}
|
292
core/LightTube/wwwroot/js/lt-video/player-mobile.js
Normal file
292
core/LightTube/wwwroot/js/lt-video/player-mobile.js
Normal file
|
@ -0,0 +1,292 @@
|
|||
class Player {
|
||||
constructor(query, info, sources, externalPlayer, externalPlayerType) {
|
||||
// vars
|
||||
this.externalPlayerType = externalPlayerType ?? "html5";
|
||||
this.muted = false;
|
||||
this.info = info;
|
||||
this.sources = sources;
|
||||
this.__videoElement = document.querySelector(query);
|
||||
this.__videoElement.removeAttribute("controls");
|
||||
this.__externalPlayer = externalPlayer;
|
||||
|
||||
// container
|
||||
const container = document.createElement("div");
|
||||
container.classList.add("player");
|
||||
this.__videoElement.parentElement.appendChild(container);
|
||||
container.appendChild(this.__videoElement);
|
||||
this.container = container;
|
||||
if (info.embed) {
|
||||
this.container.classList.add("embed");
|
||||
this.__videoElement.classList.remove("embed");
|
||||
}
|
||||
|
||||
// default source
|
||||
switch (this.externalPlayerType) {
|
||||
case "html5":
|
||||
for (let source of sources) {
|
||||
if (source.height <= 720) {
|
||||
this.__videoElement.src = source.src;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "hls.js":
|
||||
for (let level = this.__externalPlayer.levels.length - 1; level >= 0; level--) {
|
||||
if (this.__externalPlayer.levels[level].height <= 720) {
|
||||
this.__externalPlayer.currentLevel = level;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "shaka":
|
||||
this.__externalPlayer.configure({abr: {enabled: false}})
|
||||
let variants = this.__externalPlayer.getVariantTracks();
|
||||
for (let variant = variants.length - 1; variant >= 0; variant--) {
|
||||
let v = variants[variant];
|
||||
if (v.height <= 720) {
|
||||
this.__externalPlayer.selectVariantTrack(v, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// controls
|
||||
const createButton = (tag, icon) => {
|
||||
const b = document.createElement(tag);
|
||||
b.classList.add("player-button");
|
||||
if (icon !== "")
|
||||
b.innerHTML = `<i class="bi bi-${icon}"></i>`;
|
||||
return b;
|
||||
}
|
||||
|
||||
this.controls = {
|
||||
container: document.createElement("div"),
|
||||
play: createButton("div", "play-fill"),
|
||||
fullscreen: createButton("div", "fullscreen"),
|
||||
time: document.createElement("span"),
|
||||
duration: document.createElement("span"),
|
||||
}
|
||||
|
||||
this.controls.container.classList.add("player-controls");
|
||||
this.controls.container.appendChild(this.controls.play);
|
||||
this.controls.fullscreen.classList.replace("player-button", "player-tiny-button")
|
||||
container.appendChild(this.controls.container);
|
||||
|
||||
this.controls.play.onclick = () => this.togglePlayPause();
|
||||
this.controls.fullscreen.onclick = () => this.fullscreen();
|
||||
this.setVolume({target: {value: 1}});
|
||||
|
||||
// playback bar
|
||||
this.playbackBar = {
|
||||
bg: document.createElement("div"),
|
||||
played: document.createElement("div"),
|
||||
buffered: document.createElement("div")
|
||||
}
|
||||
this.playbackBar.bg.classList.add("player-playback-bar");
|
||||
this.playbackBar.bg.classList.add("player-playback-bar-bg");
|
||||
this.playbackBar.played.classList.add("player-playback-bar");
|
||||
this.playbackBar.played.classList.add("player-playback-bar-fg");
|
||||
this.playbackBar.buffered.classList.add("player-playback-bar");
|
||||
this.playbackBar.buffered.classList.add("player-playback-bar-buffer");
|
||||
this.playbackBar.bg.appendChild(this.playbackBar.buffered);
|
||||
this.playbackBar.bg.appendChild(this.playbackBar.played);
|
||||
|
||||
let playbackBarContainer = document.createElement("div");
|
||||
playbackBarContainer.classList.add("player-playback-bar-container")
|
||||
this.playbackBar.bg.onclick = e => {
|
||||
this.playbackBarSeek(e)
|
||||
}
|
||||
playbackBarContainer.appendChild(this.controls.time);
|
||||
playbackBarContainer.appendChild(this.playbackBar.bg);
|
||||
playbackBarContainer.appendChild(this.controls.duration);
|
||||
playbackBarContainer.appendChild(this.controls.fullscreen);
|
||||
container.appendChild(playbackBarContainer);
|
||||
|
||||
|
||||
// events
|
||||
container.onfullscreenchange = () => {
|
||||
if (!document.fullscreenElement) {
|
||||
this.controls.fullscreen.querySelector("i").setAttribute("class", "bi bi-fullscreen");
|
||||
} else {
|
||||
this.controls.fullscreen.querySelector("i").setAttribute("class", "bi bi-fullscreen-exit");
|
||||
}
|
||||
}
|
||||
const updatePlayButtons = () => {
|
||||
if (this.__videoElement.paused) {
|
||||
this.controls.play.querySelector("i").classList.replace("bi-pause-fill", "bi-play-fill");
|
||||
} else {
|
||||
this.controls.play.querySelector("i").classList.replace("bi-play-fill", "bi-pause-fill");
|
||||
}
|
||||
}
|
||||
this.__videoElement.onplay = () => updatePlayButtons();
|
||||
this.__videoElement.onpause = () => updatePlayButtons();
|
||||
updatePlayButtons();
|
||||
this.__videoElement.onclick = e => this.toggleControls(e);
|
||||
this.controls.container.onclick = e => this.toggleControls(e);
|
||||
this.__videoElement.onclick = e => this.toggleControls(e);
|
||||
|
||||
switch (this.externalPlayerType) {
|
||||
case "shaka":
|
||||
externalPlayer.addEventListener("variantchanged", () => {
|
||||
this.updateMenu();
|
||||
});
|
||||
externalPlayer.addEventListener('error', this.fallbackFromShaka);
|
||||
break;
|
||||
case "hls.js":
|
||||
// uhhhhhh...
|
||||
break;
|
||||
}
|
||||
|
||||
// buffering
|
||||
this.bufferingScreen = document.createElement("div");
|
||||
this.bufferingScreen.classList.add("player-buffering");
|
||||
this.container.appendChild(this.bufferingScreen);
|
||||
|
||||
let bufferingSpinner = document.createElement("img");
|
||||
bufferingSpinner.classList.add("player-buffering-spinner");
|
||||
bufferingSpinner.src = "/img/spinner.gif";
|
||||
this.bufferingScreen.appendChild(bufferingSpinner);
|
||||
|
||||
setInterval(() => this.update(), 100);
|
||||
}
|
||||
|
||||
togglePlayPause(e) {
|
||||
if (this.__videoElement.paused)
|
||||
this.__videoElement.play();
|
||||
else
|
||||
this.__videoElement.pause();
|
||||
}
|
||||
|
||||
updateMenu() {
|
||||
// todo: mobile resolution switching
|
||||
}
|
||||
|
||||
fullscreen() {
|
||||
if (!document.fullscreenElement) {
|
||||
this.container.requestFullscreen();
|
||||
} else {
|
||||
document.exitFullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
timeUpdate() {
|
||||
if (this.info.live) {
|
||||
let timeBack = this.__videoElement.duration - this.__videoElement.currentTime;
|
||||
this.controls.time.innerHTML = timeBack > 10 ? this.getTimeString(timeBack) : "LIVE";
|
||||
} else {
|
||||
this.controls.time.innerHTML = this.getTimeString(this.__videoElement.currentTime);
|
||||
this.controls.duration.innerHTML = this.getTimeString(this.__videoElement.duration);
|
||||
}
|
||||
this.playbackBar.played.style.width = ((this.__videoElement.currentTime / this.__videoElement.duration) * 100) + "%";
|
||||
this.playbackBar.buffered.style.width = ((this.getLoadEnd() / this.__videoElement.duration) * 100) + "%";
|
||||
}
|
||||
|
||||
setVolume(e) {
|
||||
this.__videoElement.volume = 1;
|
||||
localStorage.setItem("ltvideo.volume", 1);
|
||||
}
|
||||
|
||||
getLoadEnd() {
|
||||
let longest = -1;
|
||||
for (let i = 0; i < this.__videoElement.buffered.length; i++) {
|
||||
const end = this.__videoElement.buffered.end(i);
|
||||
if (end > longest) longest = end;
|
||||
}
|
||||
return longest;
|
||||
}
|
||||
|
||||
playbackBarSeek(e) {
|
||||
let percentage = (e.offsetX / (this.playbackBar.bg.clientLeft + this.playbackBar.bg.clientWidth));
|
||||
this.playbackBar.played.style.width = (percentage * 100) + "%";
|
||||
this.__videoElement.currentTime = this.__videoElement.duration * percentage;
|
||||
}
|
||||
|
||||
getTimeString(s) {
|
||||
let res = s < 3600 ? new Date(s * 1000).toISOString().substr(14, 5) : new Date(s * 1000).toISOString().substr(11, 8);
|
||||
if (res.startsWith("0"))
|
||||
res = res.substr(1);
|
||||
return res;
|
||||
}
|
||||
|
||||
update() {
|
||||
this.timeUpdate();
|
||||
|
||||
if (this.info.live) {
|
||||
let timeBack = Math.abs(this.__videoElement.currentTime - this.__videoElement.buffered.end(this.__videoElement.buffered.length - 1));
|
||||
this.bufferingScreen.style.display = timeBack < .1 ? "flex" : "none";
|
||||
} else {
|
||||
switch (this.__videoElement.readyState) {
|
||||
case 1:
|
||||
this.bufferingScreen.style.display = "flex";
|
||||
break;
|
||||
default:
|
||||
this.bufferingScreen.style.display = "none";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fallbackFromShaka() {
|
||||
if (this.externalPlayerType !== "shaka") return;
|
||||
this.externalPlayerType = "html5";
|
||||
console.log("Shaka player crashed, falling back");
|
||||
let cTime = this.__videoElement.currentTime;
|
||||
await this.__externalPlayer.detach();
|
||||
await this.__externalPlayer.destroy();
|
||||
this.__videoElement.src = this.sources[0].src;
|
||||
this.__externalPlayer = undefined;
|
||||
this.__videoElement.currentTime = cTime;
|
||||
this.updateMenu();
|
||||
console.log("Fallback complete!");
|
||||
}
|
||||
|
||||
toggleControls(e) {
|
||||
if (["DIV", "VIDEO"].includes(e.target.tagName))
|
||||
if (this.container.classList.contains("hide-controls")) {
|
||||
this.container.classList.remove("hide-controls")
|
||||
} else {
|
||||
this.container.classList.add("hide-controls")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const loadPlayerWithShaka = async (query, info, sources, manifestUri) => {
|
||||
let player;
|
||||
if (manifestUri !== undefined) {
|
||||
shaka.polyfill.installAll();
|
||||
let shakaUsable = shaka.Player.isBrowserSupported();
|
||||
|
||||
if (shakaUsable) {
|
||||
const video = document.querySelector(query);
|
||||
player = new shaka.Player(video);
|
||||
|
||||
try {
|
||||
await player.load(manifestUri);
|
||||
} catch (e) {
|
||||
await player.destroy();
|
||||
return new Player(query, info, sources, undefined, "html5");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Player(query, info, sources, await player, "shaka");
|
||||
}
|
||||
|
||||
const loadPlayerWithHls = (query, info, manifestUri) => {
|
||||
return new Promise((res, rej) => {
|
||||
let hls;
|
||||
|
||||
const video = document.querySelector(query);
|
||||
|
||||
if (Hls.isSupported()) {
|
||||
hls = new Hls();
|
||||
hls.loadSource(manifestUri);
|
||||
hls.attachMedia(video);
|
||||
hls.on(Hls.Events.MANIFEST_PARSED, function (event, data) {
|
||||
res(new Player(query, info, [], hls, "hls.js"));
|
||||
});
|
||||
} else
|
||||
rej("You can't watch livestreams / premieres because hls.js is not supported in your browser.")
|
||||
})
|
||||
}
|
32
core/LightTube/wwwroot/js/shaka-player/shaka-player.compiled.min.js
vendored
Normal file
32
core/LightTube/wwwroot/js/shaka-player/shaka-player.compiled.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
43
core/LightTube/wwwroot/js/site.js
Normal file
43
core/LightTube/wwwroot/js/site.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
const subscribeButtons = document.querySelectorAll("button.subscribe-button");
|
||||
const subscribeToChannel = (e) => {
|
||||
const channelId = e.target.attributes["data-cid"].value;
|
||||
e.target.disabled = true;
|
||||
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", "/Account/Subscribe?channel=" + channelId, false)
|
||||
xhr.send()
|
||||
|
||||
e.target.disabled = false;
|
||||
if (xhr.status !== 200)
|
||||
alert("You need to login to subscribe to a channel")
|
||||
|
||||
if (xhr.responseText === "true") {
|
||||
e.target.innerText = "Subscribed";
|
||||
e.target.classList.add("subscribed")
|
||||
} else {
|
||||
e.target.innerText = "Subscribe";
|
||||
e.target.classList.remove("subscribed")
|
||||
}
|
||||
}
|
||||
|
||||
if (subscribeButtons.length > 0) {
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", "/Account/SubscriptionsJson", false)
|
||||
xhr.send()
|
||||
|
||||
let subscribedChannels = JSON.parse(xhr.responseText);
|
||||
|
||||
for (let i = 0; i < subscribeButtons.length; i++) {
|
||||
let button = subscribeButtons[i];
|
||||
if (subscribedChannels.includes(button.attributes["data-cid"].value)) {
|
||||
button.innerText = "Subscribed";
|
||||
button.classList.add("subscribed")
|
||||
} else {
|
||||
button.innerText = "Subscribe";
|
||||
button.classList.remove("subscribed")
|
||||
}
|
||||
|
||||
button.onclick = subscribeToChannel;
|
||||
button.style.display = ""
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue