diff options
author | Minteck <contact@minteck.org> | 2022-10-10 20:51:39 +0200 |
---|---|---|
committer | Minteck <contact@minteck.org> | 2022-10-10 20:51:39 +0200 |
commit | 108525534c28013cfe1897c30e4565f9893f3766 (patch) | |
tree | dd3e5132971f96ab5f05e7f3f8f6dbbf379a19bd /pages/together.inc | |
parent | 2162eaa06f7e4764eb3dcfe130ec2c711d0c62ab (diff) | |
download | pluralconnect-108525534c28013cfe1897c30e4565f9893f3766.tar.gz pluralconnect-108525534c28013cfe1897c30e4565f9893f3766.tar.bz2 pluralconnect-108525534c28013cfe1897c30e4565f9893f3766.zip |
Update
Diffstat (limited to 'pages/together.inc')
-rw-r--r-- | pages/together.inc | 939 |
1 files changed, 939 insertions, 0 deletions
diff --git a/pages/together.inc b/pages/together.inc new file mode 100644 index 0000000..87faf6a --- /dev/null +++ b/pages/together.inc @@ -0,0 +1,939 @@ +<?php + +require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/init.inc"; global $title; global $isLoggedIn; +require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.inc'; + +global $WebSocketAddress; + +if (!isset($WebSocketAddress)) { + $WebSocketAddress = "wss://ponies.equestria.horse/_WatchTogether-WebSocket-EntryPoint/socket"; +} + +?> + +<style> + .list-group-item { + color: #fff; + background-color: #222; + border: 1px solid rgba(255, 255, 255, .125); + } + + .list-group-item.disabled { + color: #fff; + background-color: #222; + border-color: rgba(255, 255, 255, .125); + opacity: .75; + } + + .list-group-item-action:hover { + background-color: #252525; + color: #ddd; + } + + .list-group-item-action:active, .list-group-item-action:focus { + background-color: #272727; + color: #bbb; + } + + .video-queue-item { + display: grid; + grid-template-columns: 1fr max-content; + grid-gap: 10px; + cursor: pointer; + } + + .video-queue-item-status { + filter: invert(1); + display: block; + height: 32px; + width: 32px; + } + + .video-queue-item-part { + display: flex; + align-items: center; + } + + .video-queue-item-metadata { + width: 100%; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + + .video-queue-item-title-outer, .video-queue-item-author-outer { + display: flex; + align-items: center; + } + + .video-queue-item-title, .video-queue-item-title-outer, .video-queue-item-author, .video-queue-item-author-outer { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + width: 100%; + } + + .modal-header { + border-bottom: 1px solid #353738; + } + + .modal-content { + border: 1px solid rgba(255, 255, 255, .2); + background-color: #111; + } + + .btn-close { + filter: invert(1); + } + + .user-ping { + float: right; + display: inline-block; + width: 16px; + height: 16px; + border-radius: 999px; + position: relative; + top: 5px; + } + + .control-item { + display: inline-block; + cursor: pointer; + border-radius: 999px; + background-color: rgba(0, 0, 0, 0); + border: 1px solid rgba(255, 255, 255, 0); + transition: background-color 200ms, border-color 200ms; + } + + .control-item:hover { + background-color: rgba(0, 0, 0, .25); + border-color: rgba(255, 255, 255, .1); + } + + .control-item:active { + background-color: rgba(0, 0, 0, .5); + border-color: rgba(255, 255, 255, .25); + } + + .control-icon { + filter: invert(1); + width: 24px; + height: 24px; + margin: 12px; + pointer-events: none; + } + + body.hide-controls * { + cursor: none; + } + + #controls-outer { + opacity: 1; + transition: opacity 500ms; + } + + .navbar, #sidebar { + opacity: 1; + transition: opacity 500ms; + } + + body.hide-controls #controls-outer { + opacity: 0; + } + + body.hide-controls .navbar, body.hide-controls #sidebar { + opacity: .25; + } + + body.fullscreen #app-container { + grid-template-columns: 1fr !important; + } + + body.fullscreen .navbar, body.fullscreen #sidebar { + display: none; + } + + body.fullscreen #app-container div, body.fullscreen #app-container video { + height: 100vh !important; + } + + body.fullscreen #controls-outer, body.fullscreen #notification { + inset: 0 !important; + } + + #notification { + opacity: 0; + pointer-events: none; + transition: opacity 200ms; + } + + #video-title { + opacity: 1; + transition: opacity 200ms; + } + + body.notification #notification { + opacity: 1; + } + + body.notification #video-title { + opacity: 0; + } + + body.skipper #skipper { + opacity: 1 !important; + pointer-events: initial !important; + } + + #skipper { + opacity: 0; + pointer-events: none; + transition: opacity 200ms; + position: fixed; + bottom: 79px; + z-index: 999; + background: rgba(0, 0, 0, .5); + border-radius: 10px; + padding: 10px 20px; + left: 20px; + border: 1px solid rgba(255, 255, 255, .25); + cursor: pointer; + } + + #skipper:hover { + background: rgba(0, 0, 0, .75) !important; + } + + #skipper:active { + background: rgba(0, 0, 0, 1) !important; + } +</style> + +<script src="/assets/editor/thing.js"></script> + +<div class="modal fade" id="error" data-bs-backdrop="static" data-bs-keyboard="false" style="z-index: 99999;"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <h4 class="modal-title">An error occurred</h4> + </div> + + <div class="modal-body"> + <div class="alert alert-danger" id="error-message"></div> + </div> + </div> + </div> +</div> + +<div class="modal fade" id="add" data-bs-backdrop="static" data-bs-keyboard="false"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <h4 class="modal-title">Add new video</h4> + </div> + + <div class="modal-body"> + <p>Enter a YouTube video ID to add to the queue:</p> + <input type="text" class="form-control" id="add-id" placeholder="e.g. K9tUKbOFots" style="color:white;background:#111;border-color:#222;"> + <p style="margin-top:10px;margin-bottom: 0;"> + <div class="btn-group"> + <span onclick="doAdd();" id="add-btn-yes" class="btn btn-primary">Add</span> + <span onclick="closeAdd();" id="add-btn-no" class="btn btn-secondary">Cancel</span> + </div> + <a href="#" onclick="document.getElementById('add-id').value = 'K9tUKbOFots';">test</a> + </p> + </div> + </div> + </div> +</div> + +<div class="modal fade" id="welcome" data-bs-backdrop="static" data-bs-keyboard="false"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <h4 class="modal-title">Welcome to Watch Together!</h4> + </div> + + <div class="modal-body"> + <h5>Host a new session</h5> + <p>You will be given an invite code to share with your partner·s.</p> + <span class="btn btn-primary" id="host-start" onclick="hostSession();">Start</span> + + <hr> + + <h5>Join an existing session</h5> + <p>Enter the invite code your partner has given you:</p> + <div style="display:grid;grid-template-columns: repeat(8, 1fr);grid-gap:10px;"> + <input type="text" class="form-control" id="invite-input-1" style="text-align: center;color:white;background:#111;border-color:#222;" onchange="processInviteCode(event);" onkeydown="processInviteCode(event);" onkeyup="processInviteCode(event);" maxlength="1"> + <input type="text" class="form-control" id="invite-input-2" style="text-align: center;color:white;background:#111;border-color:#222;" onchange="processInviteCode(event);" onkeydown="processInviteCode(event);" onkeyup="processInviteCode(event);" maxlength="1"> + <input type="text" class="form-control" id="invite-input-3" style="text-align: center;color:white;background:#111;border-color:#222;" onchange="processInviteCode(event);" onkeydown="processInviteCode(event);" onkeyup="processInviteCode(event);" maxlength="1"> + <input type="text" class="form-control" id="invite-input-4" style="text-align: center;color:white;background:#111;border-color:#222;" onchange="processInviteCode(event);" onkeydown="processInviteCode(event);" onkeyup="processInviteCode(event);" maxlength="1"> + <input type="text" class="form-control" id="invite-input-5" style="text-align: center;color:white;background:#111;border-color:#222;" onchange="processInviteCode(event);" onkeydown="processInviteCode(event);" onkeyup="processInviteCode(event);" maxlength="1"> + <input type="text" class="form-control" id="invite-input-6" style="text-align: center;color:white;background:#111;border-color:#222;" onchange="processInviteCode(event);" onkeydown="processInviteCode(event);" onkeyup="processInviteCode(event);" maxlength="1"> + <input type="text" class="form-control" id="invite-input-7" style="text-align: center;color:white;background:#111;border-color:#222;" onchange="processInviteCode(event);" onkeydown="processInviteCode(event);" onkeyup="processInviteCode(event);" maxlength="1"> + <input type="text" class="form-control" id="invite-input-8" style="text-align: center;color:white;background:#111;border-color:#222;" onchange="processInviteCode(event);" onkeydown="processInviteCode(event);" onkeyup="processInviteCode(event);" maxlength="1"> + </div> + </div> + </div> + </div> +</div> +<script> + document.getElementById("invite-input-1").disabled = true; + document.getElementById("invite-input-2").disabled = true; + document.getElementById("invite-input-3").disabled = true; + document.getElementById("invite-input-4").disabled = true; + document.getElementById("invite-input-5").disabled = true; + document.getElementById("invite-input-6").disabled = true; + document.getElementById("invite-input-7").disabled = true; + document.getElementById("invite-input-8").disabled = true; + document.getElementById("host-start").classList.add("disabled"); + + let modal = new bootstrap.Modal(document.getElementById('welcome')); + modal.show(); + document.getElementById("invite-input-1").focus(); + + window.modalAdd = new bootstrap.Modal(document.getElementById('add')); + + function openAdd() { + modalAdd.show(); + } + + function closeAdd() { + document.getElementById("add-id").value = ""; + modalAdd.hide(); + } + + function doAdd() { + document.getElementById("add-id").disabled = true; + document.getElementById("add-btn-yes").classList.add("disabled"); + document.getElementById("add-btn-no").classList.add("disabled"); + + socket.send(JSON.stringify({ + task: "UPDATE_QUEUE", + payload: { + operation: "+", + video: document.getElementById("add-id").value + } + })) + } + + function toPrettyTime(seconds) { + let parts = new Date(seconds * 1000).toUTCString().split(" ")[4].split(":"); + parts[0] = parseInt(parts[0]).toString(); + + if (parts[0] === "0") { + parts[1] = parseInt(parts[1]).toString(); + parts.shift(); + } + + return parts.join(":"); + } + + function processInviteCode(event) { + let i1 = document.getElementById("invite-input-1").value.trim().toLowerCase().replace(/[^\da-z]+/gm, ""); + let i2 = document.getElementById("invite-input-2").value.trim().toLowerCase().replace(/[^\da-z]+/gm, ""); + let i3 = document.getElementById("invite-input-3").value.trim().toLowerCase().replace(/[^\da-z]+/gm, ""); + let i4 = document.getElementById("invite-input-4").value.trim().toLowerCase().replace(/[^\da-z]+/gm, ""); + let i5 = document.getElementById("invite-input-5").value.trim().toLowerCase().replace(/[^\da-z]+/gm, ""); + let i6 = document.getElementById("invite-input-6").value.trim().toLowerCase().replace(/[^\da-z]+/gm, ""); + let i7 = document.getElementById("invite-input-7").value.trim().toLowerCase().replace(/[^\da-z]+/gm, ""); + let i8 = document.getElementById("invite-input-8").value.trim().toLowerCase().replace(/[^\da-z]+/gm, ""); + + document.getElementById("invite-input-1").value = i1; + document.getElementById("invite-input-2").value = i2; + document.getElementById("invite-input-3").value = i3; + document.getElementById("invite-input-4").value = i4; + document.getElementById("invite-input-5").value = i5; + document.getElementById("invite-input-6").value = i6; + document.getElementById("invite-input-7").value = i7; + document.getElementById("invite-input-8").value = i8; + + if (i8 === "") { + document.getElementById("invite-input-8").focus(); + } + if (i7 === "") { + document.getElementById("invite-input-7").focus(); + } + if (i6 === "") { + document.getElementById("invite-input-6").focus(); + } + if (i5 === "") { + document.getElementById("invite-input-5").focus(); + } + if (i4 === "") { + document.getElementById("invite-input-4").focus(); + } + if (i3 === "") { + document.getElementById("invite-input-3").focus(); + } + if (i2 === "") { + document.getElementById("invite-input-2").focus(); + } + if (i1 === "") { + document.getElementById("invite-input-1").focus(); + } + + if (event instanceof KeyboardEvent) { + if (event.code === "Backspace" && event.type === "keydown") { + let el = event.target; + let ep = document.getElementById("invite-input-" + (el['id'].split("-")[2] - 1)); + + if (ep !== null) { + if (el.value.trim() === "") { + ep.value = ""; + ep.focus(); + } else { + el.value = ""; + ep.focus(); + } + } + } + } + + if (i1.length === 1 && i2.length === 1 && i3.length === 1 && i4.length === 1 && i5.length === 1 && i6.length === 1 && i7.length === 1 && i8.length === 1 && ((event.type && event.type === "keyup") || !event.type)) { + joinSession(i1 + i2 + i3 + i4 + i5 + i6 + i7 + i8); + } + } + + async function joinSession(code) { + document.getElementById("invite-input-1").disabled = true; + document.getElementById("invite-input-2").disabled = true; + document.getElementById("invite-input-3").disabled = true; + document.getElementById("invite-input-4").disabled = true; + document.getElementById("invite-input-5").disabled = true; + document.getElementById("invite-input-6").disabled = true; + document.getElementById("invite-input-7").disabled = true; + document.getElementById("invite-input-8").disabled = true; + document.getElementById("host-start").classList.add("disabled"); + + window.inviteCode = code; + + startSession(); + } + + async function hostSession() { + document.getElementById("invite-input-1").disabled = true; + document.getElementById("invite-input-2").disabled = true; + document.getElementById("invite-input-3").disabled = true; + document.getElementById("invite-input-4").disabled = true; + document.getElementById("invite-input-5").disabled = true; + document.getElementById("invite-input-6").disabled = true; + document.getElementById("invite-input-7").disabled = true; + document.getElementById("invite-input-8").disabled = true; + document.getElementById("host-start").classList.add("disabled"); + + window.inviteCode = null; + + startSession(); + } + + window.terminated = false; + window.users = null; + window.queue = null; + window.segments = []; + window.originalTitle = document.title; + + async function connect() { + window.identity = JSON.parse(await (await window.fetch("/api/me")).text()); + window.sessionToken = await (await window.fetch("/api/token")).text(); + + window.socket = new WebSocket("<?= $WebSocketAddress ?>"); + + socket.onclose = (event) => { + console.log("[ws:close] ",event); + + if (window.terminated) { + modal.hide(); + return; + } + + if (event.wasClean) { + document.getElementById('error-message').innerText = "Connection closed"; + } else { + if (event.reason) { + document.getElementById('error-message').innerText = "Connection closed unexpectedly with code " + event.code + ": " + event.reason; + } else { + document.getElementById('error-message').innerText = "Connection closed unexpectedly with code " + event.code; + } + } + + let errorModal = new bootstrap.Modal(document.getElementById('error')); + errorModal.show(); + modal.hide(); + } + + socket.onmessage = async (event) => { + let data = JSON.parse(event.data); + if (data.task !== "HEARTBEAT_ACK") console.log("[ws:message]", data); + + if (data.task === "CONFIG") { + document.getElementById("invite-input-1").disabled = false; + document.getElementById("invite-input-2").disabled = false; + document.getElementById("invite-input-3").disabled = false; + document.getElementById("invite-input-4").disabled = false; + document.getElementById("invite-input-5").disabled = false; + document.getElementById("invite-input-6").disabled = false; + document.getElementById("invite-input-7").disabled = false; + document.getElementById("invite-input-8").disabled = false; + document.getElementById("host-start").classList.remove("disabled"); + + setInterval(() => { + socket.send(JSON.stringify({ + task: "HEARTBEAT", + payload: { + videoPositon: document.getElementById("video").currentTime + } + })); + }, data.payload.heartbeatInterval); + + return; + } + + if (data.task === "SESSION") { + window.users = data.payload.users; + window.queue = data.payload.queue; + + updateQueue(); + + document.getElementById("participants").innerHTML = ""; + for (let user of data.payload.users) { + document.getElementById("participants").innerHTML += "<li class='list-group-item'>" + user.id + "<span id='user-" + user.id + "-ping' class='bg-primary user-ping'></span></li>"; + } + + document.getElementById("invite-code").innerText = data.payload.code; + } + + if (data.task === "VIDEO_UPDATE") { + if (data.payload.id !== null) { + try { + window.segments = JSON.parse(await (await window.fetch("https://sponsor.ajay.app/api/skipSegments/" + data.payload.sha.substring(0, 32) + '?categories=["sponsor","intro","outro","music_offtopic"]')).text()).filter(i => i['videoID'] === data.payload.id)[0].segments; + notification("This video is enhanced by smart playback", "success"); + } catch (e) { + notification("This video is not compatible with smart playback", "warning"); + window.segments = []; + } + } + + if (data.payload.url) document.getElementById("video").src = data.payload.url; + + if (data.payload.state === 1) { + document.getElementById("video").play(); + } else if (!!data.payload.state) { + document.getElementById("video").pause(); + } + + if (data.payload.title && data.payload.title.trim() !== "") { + document.getElementById("video-title").innerText = data.payload.title; + document.title = data.payload.title + " · " + window.originalTitle; + } else { + if (data.payload.title) { + document.getElementById("video-title").innerText = ""; + document.title = window.originalTitle; + } + } + + document.getElementById("video").currentTime = data.payload.position; + } + + if (data.task === "UPDATE_USERS") { + window.users = data.payload.users; + + document.getElementById("participants").innerHTML = ""; + for (let user of data.payload.users) { + document.getElementById("participants").innerHTML += "<li class='list-group-item'>" + user.id + "<span id='user-" + user.id + "-ping' class='bg-primary user-ping'></span></li>"; + } + } + + if (data.task === "UPDATE_QUEUE") { + window.queue = data.payload.queue; + + if (data.payload.poster === identity.id) { + document.getElementById("add-id").disabled = false; + document.getElementById("add-id").value = ""; + document.getElementById("add-btn-yes").classList.remove("disabled"); + document.getElementById("add-btn-no").classList.remove("disabled"); + + modalAdd.hide(); + } + + updateQueue(); + } + + if (data.task === "HEARTBEAT_ACK") { + for (let user of Object.keys(data.payload.delays)) { + let abs = Math.abs(data.payload.delays[user]); + let color = "primary"; + + if (abs >= 500) color = "warning"; + if (abs >= 1000) color = "danger"; + if (abs < 500) color = "success"; + + let sign = "±"; + if (data.payload.delays[user] < 0) sign = "-"; + if (data.payload.delays[user] > 0) sign = "+"; + + document.getElementById("user-" + user + "-ping").title = sign + abs + " ms"; + document.getElementById("user-" + user + "-ping").classList.remove("bg-primary"); + document.getElementById("user-" + user + "-ping").classList.remove("bg-danger"); + document.getElementById("user-" + user + "-ping").classList.remove("bg-warning"); + document.getElementById("user-" + user + "-ping").classList.remove("bg-green"); + document.getElementById("user-" + user + "-ping").classList.add("bg-" + color); + } + } + + if (data.task === "TERMINATE" || data.task === "FAILURE") { + window.terminated = true; + + if (data.payload.code) { + if (data.payload.reason) { + document.getElementById('error-message').innerText = data.payload.code + ": " + data.payload.reason; + let errorModal = new bootstrap.Modal(document.getElementById('error')); + errorModal.show(); + } else { + document.getElementById('error-message').innerText = data.payload.code; + let errorModal = new bootstrap.Modal(document.getElementById('error')); + errorModal.show(); + } + } else { + document.getElementById('error-message').innerText = "Error"; + let errorModal = new bootstrap.Modal(document.getElementById('error')); + errorModal.show(); + } + + if (data.task === "FAILURE") socket.close(); + } + } + + socket.onopen = (event) => { + console.log("[ws:open] ", event); + + socket.send(JSON.stringify({ + task: "IDENTIFY", + payload: { + token: window.sessionToken + } + })) + } + } + + function updateQueue() { + document.getElementById("queue").innerHTML = ""; + + for (let video of window.queue) { + document.getElementById("queue").innerHTML += ` +<li class="list-group-item list-group-item-action video-queue-item"> + <div class="video-queue-item-part video-queue-item-metadata"> + <div> + <div class="video-queue-item-title-outer"> + <span class="video-queue-item-title">${video.title}</span> + </div> + <div class="video-queue-item-author-outer"> + <span class="video-queue-item-author text-muted">${video.author}</span> + </div> + </div> + </div> + <div class="video-queue-item-part text-muted">${video['duration_pretty'] ?? toPrettyTime(video.duration)}</div> +</li> +`; + } + } + + function startSession() { + socket.send(JSON.stringify({ + task: "SESSION", + payload: { + id: window.inviteCode + } + })) + + modal.hide(); + } + + connect(); +</script> + +<div style="height:calc(100vh - 60px);display:grid;grid-template-columns: 3fr 1.5fr;" id="app-container"> + <div style="height:calc(100vh - 60px);"> + <video id="video" style="width:100%;height:calc(100vh - 60px);"></video> + </div> + <script> + + document.getElementById("video").onplay = () => { + console.log("play"); + + if (socket) socket.send(JSON.stringify({ + task: "VIDEO_UPDATE", + payload: { + state: 1, + position: document.getElementById("video").currentTime + } + })); + } + + document.getElementById("video").onpause = () => { + console.log("pause"); + + if (socket) socket.send(JSON.stringify({ + task: "VIDEO_UPDATE", + payload: { + state: 0, + position: document.getElementById("video").currentTime + } + })); + } + + let controlsFadeInterval; + document.body.classList.remove("hide-controls"); + + document.body.onmousemove = document.body.onmouseup = () => { + document.body.classList.remove("hide-controls"); + try { clearTimeout(controlsFadeInterval) } catch (e) {} + controlsFadeInterval = setTimeout(() => { + if (!document.getElementById("video").paused) document.body.classList.add("hide-controls"); + }, 5000); + } + + </script> + <div class="container" id="sidebar" style="border-left:1px solid rgba(255, 255, 255, .25);"> + <br> + <h4>Participants</h4> + <ul class="list-group" id="participants"></ul> + <p style="margin-top:10px;margin-bottom:0;">Invite new participants with this invite code: <code id="invite-code">--------</code></p> + + <br> + <h4>Queue</h4> + <ul class="list-group" id="queue"></ul> + <p style="margin-top:10px;margin-bottom:0;"> + <span class="btn btn-primary" onclick="openAdd()">Add video</span> + </p> + </div> +</div> + +<div id="notification" style="z-index:999;padding: 15px 20px;font-size: 20px;position: fixed;top: 60px;left:0;text-shadow: 0 0 10px black;"> + Notification. +</div> +<a onclick="controls_skipIntroOutro();" id="skipper">Skip</a> +<div id="controls-outer" style="position:fixed;background: linear-gradient(180deg, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0) 35%, rgba(0,0,0,0) 65%, rgba(0,0,0,0.5) 100%);top: 60px;left: 0;right: 0;bottom: 0;"> + <div id="video-title" style="padding: 15px 20px;font-size: 20px;"></div> + <div id="controls" style="position: fixed;bottom: 20px;height: 50px;left: 15px;display:grid;grid-template-columns: max-content 1fr max-content;"> + <div> + <a id="control-play" onclick="controls_playPause();" class="control-item"> + <img alt="" src="/assets/icons/together/play.svg" id="control-play-icon" class="control-icon"> + </a> + <a id="control-next" onclick="controls_next();" class="control-item"> + <img alt="" src="/assets/icons/together/next.svg" id="control-next-icon" class="control-icon"> + </a> + </div> + <div style="display: flex;align-items: center;padding-left:15px;padding-right:15px;"> + <div style="padding: 22px 0;width:100%;" id="seek-bar"> + <div style="height:6px;width:100%;background:rgba(255, 255, 255, .1);border-radius:999px;pointer-events: none;"> + <div style="height:6px;width:0;background:rgba(255, 255, 255, .75);border-radius:999px;" id="control-progress"></div> + </div> + </div> + </div> + <div> + <div style="display: inline-flex;align-items: center;"> + <span id="time-remaining" style="font-family:monospace;">-0:00</span> + </div> + <a id="control-full" onclick="controls_fullscreen();" class="control-item"> + <img alt="" src="/assets/icons/together/full-on.svg" id="control-full-icon" class="control-icon"> + </a> + </div> + </div> + <div id="seek-bar-dot" style="position: fixed;bottom: 38px;width: 12px;height: 12px;background: white;border-radius: 999px;pointer-events: none;display:none;"></div> +</div> +<script> + document.getElementById("controls").style.width = (document.getElementById("video").clientWidth - 30) + "px"; + document.getElementById("controls-outer").style.right = (window.innerWidth - document.getElementById("video").clientWidth) + "px"; + document.getElementById("notification").style.right = (window.innerWidth - document.getElementById("video").clientWidth) + "px"; + + let notificationDecayTimeout; + + function notification(text, color) { + document.getElementById("notification").innerText = text; + document.getElementById("notification").classList.add("text-" + color); + document.body.classList.add("notification"); + + try { clearTimeout(notificationDecayTimeout) } catch (e) {} + + notificationDecayTimeout = setTimeout(() => { + document.body.classList.remove("notification"); + setTimeout(() => { + document.getElementById("notification").innerText = "Notification."; + document.getElementById("notification").classList.remove("text-" + color); + }, 200); + }, 5000) + } + + window.onresize = () => { + document.getElementById("controls").style.width = (document.getElementById("video").clientWidth - 30) + "px"; + document.getElementById("controls-outer").style.right = (window.innerWidth - document.getElementById("video").clientWidth) + "px"; + document.getElementById("notification").style.right = (window.innerWidth - document.getElementById("video").clientWidth) + "px"; + } + + function controls_playPause() { + if (document.getElementById("video").src.trim() === "") return; + + if (document.getElementById("video").paused) { + document.getElementById("video").play(); + } else { + document.getElementById("video").pause(); + } + } + + function controls_fullscreen() { + if (document.fullscreen) { + document.exitFullscreen(); + } else { + document.documentElement.requestFullscreen(); + } + } + + function controls_next() { + document.getElementById("video").play(); + document.getElementById("video").currentTime = document.getElementById("video").duration; + } + + window.seeking = false; + window.seekPosition = 0; + + document.getElementById("controls-outer").onclick = (event) => { + if (event.target === document.getElementById("controls-outer")) controls_playPause(); + } + + document.getElementById("controls-outer").ondblclick = (event) => { + if (event.target === document.getElementById("controls-outer")) controls_fullscreen(); + } + + document.getElementById("seek-bar").onmouseenter = () => { + document.getElementById("seek-bar-dot").style.display = ""; + console.log("> ENTER <"); + window.seeking = true; + } + + document.getElementById("seek-bar").onclick = (event) => { + let percentage = (event.offsetX / document.getElementById("seek-bar").clientWidth) * 100; + let multiplier = event.offsetX / document.getElementById("seek-bar").clientWidth; + + document.getElementById("seek-bar-dot").style.left = event.clientX + "px"; + + console.log(" CLICK: ", event.offsetX, "(" + percentage.toFixed(3) + "%, " + toPrettyTime(document.getElementById("video").duration * multiplier) + ")"); + window.seekPosition = document.getElementById("video").duration * multiplier; + + document.getElementById("video").pause(); + document.getElementById("video").currentTime = window.seekPosition; + document.getElementById("video").play(); + } + + document.getElementById("seek-bar").onmouseleave = () => { + document.getElementById("seek-bar-dot").style.display = "none"; + console.log("< LEAVE >"); + window.seeking = false; + } + + function controls_skipIntroOutro() { + let skipper = null; + + for (let segment of window.segments.filter(i => i['category'] === "intro")) { + if (segment.segment) { + if (document.getElementById("video").currentTime >= segment.segment[0] && document.getElementById("video").currentTime < segment.segment[1]) { + skipper = segment.segment[1]; + } + } + } + for (let segment of window.segments.filter(i => i['category'] === "outro")) { + if (segment.segment) { + if (document.getElementById("video").currentTime >= segment.segment[0] && document.getElementById("video").currentTime < segment.segment[1]) { + skipper = segment.segment[1]; + } + } + } + + if (skipper) { + document.getElementById("video").pause(); + document.getElementById("video").currentTime = skipper; + document.getElementById("video").play(); + } + } + + document.getElementById("seek-bar").onmousemove = (event) => { + let percentage = (event.offsetX / document.getElementById("seek-bar").clientWidth) * 100; + let multiplier = event.offsetX / document.getElementById("seek-bar").clientWidth; + + document.getElementById("seek-bar-dot").style.left = event.clientX + "px"; + + console.log(" POS: ", event.offsetX, "(" + percentage.toFixed(3) + "%, " + toPrettyTime(document.getElementById("video").duration * multiplier) + ")"); + window.seekPosition = document.getElementById("video").duration * multiplier; + } + + setInterval(() => { + if (document.getElementById("video").src.trim() === "") { + document.getElementById("controls").style.display = "none"; + } else { + document.getElementById("controls").style.display = "grid"; + } + + document.getElementById("control-play-icon").src = document.getElementById("video").paused ? "/assets/icons/together/play.svg" : "/assets/icons/together/pause.svg"; + document.getElementById("control-full-icon").src = document.fullscreen ? "/assets/icons/together/full-off.svg" : "/assets/icons/together/full-on.svg"; + document.getElementById("control-progress").style.width = ((document.getElementById("video").currentTime / document.getElementById("video").duration) * 100) + "%"; + + if (document.fullscreen) { + document.body.classList.add("fullscreen"); + } else { + document.body.classList.remove("fullscreen"); + } + + if (window.seeking) { + document.getElementById("time-remaining").classList.add("text-warning"); + document.getElementById("time-remaining").innerText = toPrettyTime(window.seekPosition); + } else { + document.getElementById("time-remaining").classList.remove("text-warning"); + + if (!isNaN(document.getElementById("video").duration)) { + document.getElementById("time-remaining").innerText = "-" + toPrettyTime(Math.round(document.getElementById("video").duration) - Math.round(document.getElementById("video").currentTime)); + } else { + document.getElementById("time-remaining").innerText = "-0:00"; + } + } + }) + + setInterval(() => { + if (!document.getElementById("video").paused) { + for (let segment of window.segments.filter(i => i['category'] === "sponsor")) { + if (segment.segment) { + if (document.getElementById("video").currentTime >= segment.segment[0] && document.getElementById("video").currentTime < segment.segment[1]) { + document.getElementById("video").pause(); + document.getElementById("video").currentTime = segment.segment[1]; + document.getElementById("video").play(); + notification("Advert skipped by smart playback", "primary"); + } + } + } + + let skipper = false; + + for (let segment of window.segments.filter(i => i['category'] === "intro")) { + if (segment.segment) { + if (document.getElementById("video").currentTime >= segment.segment[0] && document.getElementById("video").currentTime < segment.segment[1]) { + skipper = true; + document.getElementById("skipper").innerText = "Skip intro"; + document.body.classList.add("skipper"); + } + } + } + for (let segment of window.segments.filter(i => i['category'] === "outro")) { + if (segment.segment) { + if (document.getElementById("video").currentTime >= segment.segment[0] && document.getElementById("video").currentTime < segment.segment[1]) { + skipper = true; + document.getElementById("skipper").innerText = "Skip to the end"; + document.body.classList.add("skipper"); + } + } + } + + if (!skipper) { + document.body.classList.remove("skipper"); + } + } + }) +</script>
\ No newline at end of file |