initial commit

This commit is contained in:
Milarin
2024-09-15 14:16:45 +02:00
commit 18078d1d3b
13 changed files with 1048 additions and 0 deletions

48
static/js/element.js Normal file
View File

@ -0,0 +1,48 @@
Element.prototype.insertChildAtIndex = function(child, index) {
if (!index) index = 0;
if (index >= this.children.length) {
this.appendChild(child);
} else {
this.insertBefore(child, this.children[index]);
}
}
Element.prototype.clearChildren = function() {
while (this.firstChild) {
this.removeChild(this.lastChild);
}
}
Number.prototype.pad = function(size) {
var s = String(this);
while (s.length < (size || 2)) {s = "0" + s;}
return s;
}
window.addEventListener("load", init);
function init() {
const sliders = document.querySelectorAll('input[type="range"]');
for (let slider of sliders) {
let changeFunction = event => {
const progress = event.target.value;
const max = event.target.max;
const value = Math.round(progress / max * 100);
slider.style.background = `linear-gradient(90deg, rgba(200,55,55,1) ${value}%, #333 ${value}%)`;
};
slider.setValue = value => {
if (slider.mousedown) return;
slider.value = value;
changeFunction({target: slider});
}
slider.addEventListener("mousedown", event => slider.mousedown = true);
slider.addEventListener("mouseup", event => slider.mousedown = false);
slider.addEventListener("input", changeFunction);
slider.addEventListener("change", changeFunction);
changeFunction({target: slider});
}
}

270
static/js/main.js Normal file
View File

@ -0,0 +1,270 @@
window.addEventListener("load", main);
let displaySelectDialog;
let audioSinkSelectDialog;
let openNewVideoDialog;
let closeVideoDialog;
let closeVideoDialogID;
let videoTemplate;
let videoContainer;
let currentVideos = [];
let currentVideoData;
let seekbar;
let volumeBar;
let playPauseButton;
let playPauseButtonIcon;
let replayButton;
let forwardButton;
let currentTimestamp;
let videoLength;
async function main() {
displaySelectDialog = document.querySelector("#display-select-dialog");
audioSinkSelectDialog = document.querySelector("#audio-sink-select-dialog");
openNewVideoDialog = document.querySelector("#open-new-video-dialog");
closeVideoDialog = document.querySelector("#close-video-dialog");
videoTemplate = document.querySelector("#video-template");
videoContainer = document.querySelector("#video-container");
seekbar = document.querySelector("#seekbar");
volumeBar = document.querySelector("#volume-bar");
playPauseButton = document.querySelector("#play-pause");
playPauseButtonIcon = playPauseButton.querySelector(".material-symbols-outlined");
replayButton = document.querySelector("#replay");
forwardButton = document.querySelector("#forward");
currentTimestamp = document.querySelector("#current-timestamp");
videoLength = document.querySelector("#video-length");
playPauseButton.addEventListener("click", onPlayPauseButtonClicked);
replayButton.addEventListener("click", onReplayButtonClicked);
forwardButton.addEventListener("click", onForwardButtonClicked);
seekbar.addEventListener("change", onSeekbarChanged);
volumeBar.addEventListener("input", onVolumeBarChanged);
updateUI();
setInterval(updateUI, 1000);
}
async function updateUI() {
const videoDatas = await listVideos();
const currentDisplay = localStorage.getItem("display");
currentVideoData = videoDatas.find(videoData => videoData.display == currentDisplay);
if (currentVideoData !== undefined) {
console.log(currentVideoData);
seekbar.disabled = false;
volumeBar.disabled = false;
playPauseButton.disabled = false;
replayButton.disabled = false;
forwardButton.disabled = false;
const {progress, duration} = formatTimestamps(currentVideoData.progress, currentVideoData.duration);
currentTimestamp.textContent = progress;
videoLength.textContent = duration;
seekbar.max = currentVideoData.duration;
seekbar.setValue(currentVideoData.progress);
volumeBar.setValue(currentVideoData.volume);
playPauseButtonIcon.textContent = currentVideoData.paused ? "play_circle" : "pause_circle";
} else {
seekbar.disabled = true;
seekbar.setValue(0);
volumeBar.disabled = true;
playPauseButton.disabled = true;
playPauseButtonIcon.textContent = "play_circle";
replayButton.disabled = true;
forwardButton.disabled = true;
currentTimestamp.textContent = "";
videoLength.textContent = "";
}
await refreshVideoList(videoDatas);
}
async function refreshVideoList(videoDatas) {
const videoIDs = videoDatas.map(videoData => videoData.id);
const shownVideoIDs = currentVideos.map(video => video.videoID);
const missingVideoIDs = videoIDs.filter(videoID => !shownVideoIDs.includes(videoID));
const unneededVideoIDs = shownVideoIDs.filter(shownVideoID => !videoIDs.includes(shownVideoID));
for (let unneededVideoID of unneededVideoIDs) {
const index = currentVideos.findIndex(video => video.videoID == unneededVideoID);
const video = currentVideos[index];
video.hide();
currentVideos.splice(index, 1);
}
for (let index in videoDatas) {
const videoData = videoDatas[index];
if (missingVideoIDs.includes(videoData.id)) {
const video = new Video(videoData);
video.showAt(index);
currentVideos.splice(index, 0, video);
} else {
const video = currentVideos.find(currentVideo => currentVideo.videoID == videoData.id);
video.update(videoData);
}
}
}
async function onShowSelectDisplayDialogClicked() {
const displays = await listDisplays();
const currentDisplay = localStorage.getItem("display");
const displaySelectForm = document.querySelector("#display-select-dialog-form");
displaySelectForm.clearChildren();
for (let display of displays) {
const input = document.createElement("input");
input.setAttribute("type", "radio");
input.setAttribute("name", "display");
input.setAttribute("id", `display_${display}`);
input.setAttribute("value", display);
input.addEventListener("change", onCloseSelectDisplayDialogClicked);
if (display == currentDisplay) input.setAttribute("checked", "checked");
displaySelectForm.appendChild(input);
const label = document.createElement("label");
label.setAttribute("for", `display_${display}`);
label.textContent = display;
displaySelectForm.appendChild(label);
}
displaySelectDialog.showModal();
}
function onCloseSelectDisplayDialogClicked() {
const selectedDisplay = document.forms["display-select-dialog-form"].elements["display"].value;
localStorage.setItem("display", selectedDisplay);
setTimeout(() => displaySelectDialog.close(), 100);
}
async function onShowSelectAudioSinkDialogClicked() {
const audioSinks = await listAudioSinks();
const currentAudioSink = localStorage.getItem("audio-sink");
const audioSinkSelectForm = document.querySelector("#audio-sink-select-dialog-form");
audioSinkSelectForm.clearChildren();
for (let index in audioSinks) {
const audioSink = audioSinks[index];
const input = document.createElement("input");
input.setAttribute("type", "radio");
input.setAttribute("name", "audiosink");
input.setAttribute("id", `audiosink_${index}`);
input.setAttribute("value", audioSink.description);
input.addEventListener("change", onCloseSelectAudioSinkDialogClicked);
if (audioSink.name == currentAudioSink) input.setAttribute("checked", "checked");
audioSinkSelectForm.appendChild(input);
const label = document.createElement("label");
label.setAttribute("for", `audiosink_${index}`);
label.textContent = audioSink.description;
audioSinkSelectForm.appendChild(label);
}
audioSinkSelectDialog.showModal();
}
function onCloseSelectAudioSinkDialogClicked() {
const selectedAudioSink = document.forms["audio-sink-select-dialog-form"].elements["audiosink"].value;
localStorage.setItem("audio-sink", selectedAudioSink);
changeAudioSink(selectedAudioSink);
setTimeout(() => audioSinkSelectDialog.close(), 100);
}
function onShowOpenNewVideoDialogClicked() {
const link = document.querySelector("#open-new-video-dialog-link");
link.value = "";
openNewVideoDialog.showModal();
}
function onOpenVideoButtonClicked() {
const link = document.forms["open-new-video-dialog-form"].elements["link"].value;
openVideo(link);
openNewVideoDialog.close();
}
function onOpenVideoInBackgroundButtonClicked() {
const link = document.forms["open-new-video-dialog-form"].elements["link"].value;
openVideoInBackground(link);
openNewVideoDialog.close();
}
function onCloseOpenNewVideoDialogClicked() {
openNewVideoDialog.close();
}
function askForCloseVideoConfirmation(videoID, videoTitle) {
const videoTitleElement = document.querySelector("#close-video-dialog-title");
videoTitleElement.textContent = videoTitle;
closeVideoDialogID = videoID;
closeVideoDialog.showModal();
}
function onCloseCloseVideoDialogClicked() {
closeVideoDialog.close();
}
function onCloseVideoButtonClicked() {
closeVideo(closeVideoDialogID)
closeVideoDialog.close();
}
function onCancelCloseVideoButtonClicked() {
closeVideoDialog.close();
}
function formatTimestamps(progress, duration) {
const progressHours = Math.floor(progress / 3600);
const progressRemaining = progress % 3600;
const progressMinutes = Math.floor(progressRemaining / 60);
const progressSeconds = progressRemaining % 60;
const durationHours = Math.floor(duration / 3600);
const durationRemaining = duration % 3600;
const durationMinutes = Math.floor(durationRemaining / 60);
const durationSeconds = durationRemaining % 60;
if (durationHours > 0) {
return {
progress: `${progressHours.pad(2)}:${progressMinutes.pad(2)}:${progressSeconds.pad(2)}`,
duration: `${durationHours.pad(2)}:${durationMinutes.pad(2)}:${durationSeconds.pad(2)}`
};
} else {
return {
progress: `${progressMinutes.pad(2)}:${progressSeconds.pad(2)}`,
duration: `${durationMinutes.pad(2)}:${durationSeconds.pad(2)}`
};
}
}
function onPlayPauseButtonClicked() {
if (currentVideoData.paused) {
unpauseVideo(currentVideoData.id);
} else {
pauseVideo(currentVideoData.id);
}
}
function onReplayButtonClicked() {
seekVideoRelative(currentVideoData.id, -10);
}
function onForwardButtonClicked() {
seekVideoRelative(currentVideoData.id, 10);
}
function onSeekbarChanged() {
seekVideo(currentVideoData.id, parseInt(seekbar.value));
}
function onVolumeBarChanged() {
setVolume(parseFloat(volumeBar.value));
}

79
static/js/video.js Normal file
View File

@ -0,0 +1,79 @@
class Video {
constructor(videoData) {
this.videoID = videoData.id;
this.shown = false;
this.root = videoTemplate.content.cloneNode(true);
this.item = this.root.querySelector(".video-item");
this.closeButton = this.root.querySelector(".video-close");
this.img = this.root.querySelector(".video-item .video-image");
this.title = this.root.querySelector(".video-item .video-title");
this.videoProgressPrimary = this.root.querySelector(".video-item .video-progress .primary");
this.videoProgressSecondary = this.root.querySelector(".video-item .video-progress .secondary");
this.item.addEventListener("click", this.enable.bind(this));
this.closeButton.addEventListener("click", this.close.bind(this));
this.update(videoData);
}
enable() {
const currentDisplay = localStorage.getItem("display");
if (this.videoData.display != currentDisplay) {
showVideo(this.videoID, currentDisplay);
} else {
hideVideo(this.videoID);
}
}
update(videoData) {
if (videoData.id != this.videoID) {
throw `tried to update video instance ${this.videoID} with data from video ${videoData.id}`;
}
this.videoData = videoData;
const progressPercentage = videoData.progress / videoData.duration * 100;
const bufferPercentage = videoData.buffered / videoData.duration * 100;
this.img.src = videoData.metadata.thumbnail;
this.title.textContent = videoData.title;
this.videoProgressPrimary.style.width = `${progressPercentage}%`;
this.videoProgressSecondary.style.width = `${bufferPercentage}%`;
const currentDisplay = localStorage.getItem("display");
if (videoData.display == currentDisplay) {
this.img.classList.add("shown-on-display");
} else {
this.img.classList.remove("shown-on-display");
}
}
shownOnDisplay() {
const currentDisplay = localStorage.getItem("display");
return this.videoData.display == currentDisplay;
}
show() {
videoContainer.appendChild(this.item);
this.shown = true;
}
showAt(index) {
videoContainer.insertChildAtIndex(this.item, index);
this.shown = true;
}
hide() {
videoContainer.removeChild(this.item);
this.shown = false;
}
close(event) {
console.log(event);
event.stopPropagation();
askForCloseVideoConfirmation(this.videoID, this.videoData.title);
}
}

94
static/js/youtube.js Normal file
View File

@ -0,0 +1,94 @@
async function listVideos() {
const resp = await fetch("/api/video/list/");
return await resp.json();
}
async function listDisplays() {
const resp = await fetch("/api/display/list/");
return await resp.json();
}
async function listAudioSinks() {
const resp = await fetch("/api/audio/list/");
return await resp.json();
}
function changeAudioSink(sink) {
fetch("/api/audio/change/", {
method: "POST",
body: JSON.stringify({sink}),
});
}
function showVideo(videoID, display) {
fetch("/api/video/show/", {
method: "POST",
body: JSON.stringify({id: videoID, display: display}),
}).then(updateUI);
}
function hideVideo(videoID) {
fetch("/api/video/hide/", {
method: "POST",
body: JSON.stringify({id: videoID}),
}).then(updateUI);
}
function openVideo(link) {
const display = localStorage.getItem("display");
fetch("/api/video/open/", {
method: "POST",
body: JSON.stringify({link, display}),
}).then(updateUI);
}
function openVideoInBackground(link) {
fetch("/api/video/open/hide/", {
method: "POST",
body: JSON.stringify({link}),
}).then(updateUI);
}
function closeVideo(videoID) {
fetch("/api/video/close/", {
method: "POST",
body: JSON.stringify({id: videoID}),
}).then(updateUI);
}
function pauseVideo(videoID) {
fetch("/api/video/pause/", {
method: "POST",
body: JSON.stringify({id: videoID}),
}).then(updateUI);
}
function unpauseVideo(videoID) {
fetch("/api/video/play/", {
method: "POST",
body: JSON.stringify({id: videoID}),
}).then(updateUI);
}
function seekVideo(videoID, second) {
fetch("/api/video/seek/", {
method: "POST",
body: JSON.stringify({id: videoID, second: second}),
}).then(updateUI);
}
function seekVideoRelative(videoID, second) {
fetch("/api/video/seek/relative/", {
method: "POST",
body: JSON.stringify({id: videoID, second: second}),
}).then(updateUI);
}
function setVolume(volume) {
fetch("/api/video/volume/", {
method: "POST",
body: JSON.stringify({volume}),
}).then(updateUI);
}