Skip to content

Instantly share code, notes, and snippets.

@jfoster
Last active November 27, 2019 02:32
Show Gist options
  • Save jfoster/a53cc0bc469f7c838ad4b4e02674d744 to your computer and use it in GitHub Desktop.
Save jfoster/a53cc0bc469f7c838ad4b4e02674d744 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @id twitchdvr
// @name Twitch DVR
// @namespace https://github.com/jfoster
//
// @description Twitch DVR
// @author Jacob Foster
// @homepage https://gist.github.com/jfoster/a53cc0bc469f7c838ad4b4e02674d744
// @icon https://www.twitch.tv/favicon.ico
//
// @version 0.1.1
// @updateURL https://gist.github.com/jfoster/a53cc0bc469f7c838ad4b4e02674d744/raw/twitchdvr.user.js
//
// @match https://*twitch.tv/*
//
// @require https://code.jquery.com/jquery-latest.min.js
// @grant none
// ==/UserScript==
const html = `<div class="tw-align-items-stretch tw-flex tw-flex-column twitch-dvr">
<div data-test-selector="seekbar-interaction-area__interactionArea" data-a-target="player-seekbar" class="seekbar-interaction-area tw-pd-y-1">
<div class="seekbar-bar tw-align-items-center tw-border-radius-medium tw-flex tw-full-width tw-relative">
<span data-test-selector="seekbar-segment__segment" class="seekbar-segment tw-absolute" style="background-color: rgba(255, 255, 255, 0.85); left: 11.5162%; width: 0.00591686%;"></span>
<span data-test-selector="seekbar-segment__segment" class="seekbar-segment tw-absolute" style="background-color: rgb(169, 112, 255); left: 0%; width: 11.5162%;"></span>
<span class="tw-absolute" style="left: 11.5162%;">
<div class="tw-transition tw-transition--duration-medium tw-transition--enter-done tw-transition__fade tw-transition__fade--enter-done">
<div class="seekbar-thumb"></div>
</div>
</span>
</div>
</div>
</div>`
$(document).ready(function() {
setInterval(() => {
const elements = document.getElementsByClassName("player-controls")[0].getElementsByClassName("tw-pd-x-2");
if (elements.length > 0 && !$(elements[0]).children('.twitch-dvr').length) {
injectSeek();
}
}, 1000);
});
function injectSeek() {
const element = document.getElementsByClassName("player-controls")[0].getElementsByClassName("tw-pd-x-2")[0];
let mouseDown = false;
const template = document.createElement("template");
template.innerHTML = html.trim();
element.insertBefore(template.content.firstChild, element.firstChild);
const twitchDvr = document.querySelector(".twitch-dvr");
const playerSlider = document.querySelector(".twitch-dvr .seekbar-bar");
const temp = document.querySelectorAll(".twitch-dvr .tw-absolute");
const sliderProg = temp[1];
const sliderThumb = temp[2];
const popupTop = document.querySelector(".twitch-dvr .player-slider__popup-container");
const popupBottom = document.querySelector(".twitch-dvr .popup-arrow");
const popupTimestamp = document.querySelector(".twitch-dvr .popup-timestamp");
let video = document.querySelector("video");
function onMouseMove(event) {
let offsetX = event.offsetX;
if (event.target === sliderThumb) {
offsetX += event.target.offsetLeft;
}
const frac = offsetX / playerSlider.offsetWidth;
popupTop.style.left = Math.min(Math.max(offsetX - 10, 0), playerSlider.offsetWidth - 20) + "px";
popupBottom.style.left = Math.min(Math.max(offsetX - 10, 0), playerSlider.offsetWidth - 20) + "px";
if (mouseDown) {
sliderProg.style.width = (frac * 100) + "%";
sliderThumb.style.left = (frac * 100) + "%";
}
if (!video || video.buffered.length === 0) {
return;
}
const distance = video.buffered.end(0) - video.buffered.start(0);
popupTimestamp.innerHTML = "-" + Math.floor((1 - frac) * distance);
}
playerSlider.addEventListener("mousemove", onMouseMove);
function onClick(event) {
mouseDown = false;
let offsetX = event.offsetX;
if (event.target === sliderThumb) {
offsetX += event.target.offsetLeft;
}
const frac = offsetX / playerSlider.offsetWidth;
if (!video || video.buffered.length === 0) {
return;
}
const distance = video.buffered.end(0) - video.buffered.start(0);
video.currentTime = video.buffered.end(0) - ((1 - frac) * distance);
}
playerSlider.addEventListener("click", onClick);
function onMouseEnter() {
popupTop.style.opacity = 1;
popupBottom.style.opacity = 1;
sliderThumb.style.opacity = 1;
}
playerSlider.addEventListener("mouseenter", onMouseEnter);
function onMouseLeave() {
popupTop.style.opacity = 0;
popupBottom.style.opacity = 0;
sliderThumb.style.opacity = 0;
}
playerSlider.addEventListener("mouseleave", onMouseLeave);
function onMouseDown() {
mouseDown = true;
}
playerSlider.addEventListener("mousedown", onMouseDown);
function onMouseUp() {
mouseDown = false;
}
playerSlider.addEventListener("mouseup", onMouseUp);
const updateTask = setInterval(() => {
if (mouseDown) {
return;
}
if (!video || video.buffered.length === 0) {
return;
}
const distance = video.buffered.end(0) - video.buffered.start(0);
const pct = (1 - ((video.buffered.end(0) - video.currentTime) / distance));
sliderProg.style.width = (pct * 100) + "%";
sliderThumb.style.left = (pct * 100) + "%";
}, 250);
const manageTask = setInterval(() => {
if (!document.body.contains(twitchDvr)) {
shutdown();
return;
}
if (!video) {
video = document.querySelector("video");
}
}, 1000);
function shutdown() {
clearInterval(updateTask);
clearInterval(manageTask);
playerSlider.removeEventListener("mousemove", onMouseMove);
playerSlider.removeEventListener("click", onClick);
playerSlider.removeEventListener("mouseenter", onMouseEnter);
playerSlider.removeEventListener("mouseleave", onMouseLeave);
playerSlider.removeEventListener("mousedown", onMouseDown);
playerSlider.removeEventListener("mouseup", onMouseUp);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment