<?php require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php"; global $isLoggedIn; if (!$isLoggedIn) header("Location: /login") and die(); $title = "Watch Together"; require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php'; 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>