Skip to content

Instantly share code, notes, and snippets.

@kubarium
Last active July 17, 2020 18:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kubarium/9a8243fd4fba8ca8a915b7be0e4084bb to your computer and use it in GitHub Desktop.
Save kubarium/9a8243fd4fba8ca8a915b7be0e4084bb to your computer and use it in GitHub Desktop.
var course_id = RegExp(/\d+/g).exec(location.href)[0];
var styles = {
pagewrapid: {
width: "1270px",
margin: "0 auto"
},
p: {
"font-size": "16px !important",
"line-height": "1.3rem !important"
}
};
function rearrange(){
return new Promise((resolve, reject) => {
pagewrapid = document.querySelector("#pagewrapid");
pagewrapid.setAttribute("style", stylize("pagewrapid"));
p = document.querySelectorAll(".htmlcontent p");
p.forEach(p=>p.setAttribute("style", stylize("p")));
document.querySelector("#readerleft").style.height = document.querySelector("#readerright").style.height;
resolve();
});
}
function saveBookmark() {
new Promise((resolve, reject) => {
localStorage.setItem(
course_id,
document.querySelector(".current").dataset.xmlid
);
resolve();
});
}
function loadBookmark() {
var video_id = localStorage.getItem(course_id);
if (video_id) {
Array.from(document.querySelectorAll("#lefttoc a"))
.filter(a => a.dataset.xmlid === video_id)[0]
.click();
}
}
function stylize(id) {
return Object.keys(styles[id])
.reduce((cssText, key) => cssText.concat(`${key}:${styles[id][key]};`), [])
.join("");
}
$(document).ajaxSuccess((event, xhr, settings) => {
if (/_ajax_htmlview/.test(settings.url)) {
rearrange()
.then(saveBookmark);
}
});
rearrange().then(loadBookmark);
var kdp;
var scrubInSeconds = 10;
var altModifier = 2;
var shiftModifier = 3;
var paused = false;
var tocWrapperScrollTop = 0;
var tocWrapperScrollEventAttached = false;
var course_id = RegExp(/\d+/g).exec(location.href)[0];
var styles = {
percentage: {
position: "absolute",
right: "25px",
top: "25px",
"font-size": "larger",
"font-weight": "bold"
},
tocWrapper: {
overflow: "auto",
height: "550px"
},
pagewrapid: {
width: "1570px",
display: "flex",
justifyContent: "space-around",
border: "1px solid #7990A2",
"border-radius": "5px"
},
metadata_flashactive: {
alignSelf: "flex-start"
},
shadowBox1: {
"border-radius": 0,
padding: 0,
margin: 0,
border: 0
},
catalog_container: {
height: "606px",
margin: 0,
padding: "25px",
border: 0,
"border-radius": 0,
position: "relative"
},
bcv_next: {
top: "-12%"
},
bcv_previous: {
top: "-12%",
right: "20px",
left: "inherit"
}
};
var percentage = document.createElement("div");
percentage.setAttribute("style", stylize("percentage"));
document.querySelector(".catalog_container").appendChild(percentage);
function rearrange() {
return new Promise((resolve, reject) => {
Array.from(document.querySelector("#pagewrapid").childNodes)
.filter(
child =>
child.nodeName !== "INPUT" &&
child.id !== "metadata_flashactive" &&
child.className !== "catalog_container"
)
.forEach(selector => selector.remove());
pagewrapid = document.querySelector("#pagewrapid");
toc = document.querySelector(".videotoc");
tocWrapper = document.createElement("div");
tocWrapper.id = "tocWrapper";
toc.parentNode.appendChild(tocWrapper);
if (tocWrapperScrollEventAttached === false) attachTocWrapperScrollEvent();
tocWrapper.appendChild(toc);
tocWrapper.setAttribute("style", stylize("tocWrapper"));
tocWrapper.scrollTop = tocWrapperScrollTop;
pagewrapid.setAttribute("style", stylize("pagewrapid"));
document
.querySelector("#metadata_flashactive")
.setAttribute("style", stylize("metadata_flashactive"));
document
.querySelector(".shadowBox1")
.setAttribute("style", stylize("shadowBox1"));
document
.querySelector(".catalog_container")
.setAttribute("style", stylize("catalog_container"));
document
.getElementById("bcv_next")
.setAttribute("style", stylize("bcv_next"));
document
.getElementById("bcv_previous")
.setAttribute("style", stylize("bcv_previous"));
resolve();
});
}
function attachTocWrapperScrollEvent() {
tocWrapperScrollEventAttached = true;
tocWrapper.onscroll = event => (tocWrapperScrollTop = event.target.scrollTop);
}
function keyEvents(event) {
switch (event.key) {
case ".":
document.getElementById("bcv_next").click();
break;
case ",":
document.getElementById("bcv_previous").click();
break;
case "ArrowUp":
event.altKey
? sr
.increment()
.then(speed =>
kdp.sendNotification("playbackRateChangeSpeed", speed)
)
: vr
.increment()
.then(volume => kdp.sendNotification("changeVolume", volume));
break;
case "ArrowDown":
event.altKey
? sr
.decrement()
.then(speed =>
kdp.sendNotification("playbackRateChangeSpeed", speed)
)
: vr
.decrement()
.then(volume => kdp.sendNotification("changeVolume", volume));
break;
case "ArrowRight":
adjustTime("forward", event);
break;
case "ArrowLeft":
adjustTime("backward", event);
break;
case " ":
kdp.sendNotification(paused ? "doPlay" : "doPause");
paused = !paused;
break;
case "0":
case "1":
case "2":
case "3":
case "4":
case "5":
case "6":
case "7":
case "8":
case "9":
adjustTime("headshot", event);
break;
default:
break;
}
}
class Range {
constructor(value, minimum, maximum, interval) {
this.value = value;
this.minimum = minimum;
this.maximum = maximum;
this.interval = interval;
}
increment() {
return new Promise((resolve, reject) => {
var newValue = this.value + this.interval;
this.value =
newValue >= this.maximum ? this.maximum : parseFloat(newValue);
resolve(this.value);
});
}
decrement() {
return new Promise((resolve, reject) => {
var newValue = this.value - this.interval;
this.value =
newValue <= this.minimum ? this.minimum : parseFloat(newValue);
resolve(this.value);
});
}
}
class VolumeRange extends Range {
constructor() {
super(1, 0.1, 1, 0.1);
}
}
class SpeedRange extends Range {
constructor() {
super(1.25, 0.25, 2, 0.25);
}
}
function stylize(id) {
return Object.keys(styles[id])
.reduce((cssText, key) => cssText.concat(`${key}:${styles[id][key]};`), [])
.join("");
}
function adjustTime(transition, event) {
var currentTime = kdp.evaluate("{video.player.currentTime}");
var timeAdjustment =
scrubInSeconds *
(event.altKey ? altModifier : 1) *
(event.shiftKey ? shiftModifier : 1);
if (
transition === "forward" &&
currentTime + timeAdjustment < kdp.evaluate("{duration}")
) {
kdp.sendNotification("doSeek", currentTime + timeAdjustment);
}
if (transition === "backward" && currentTime - timeAdjustment > 0) {
kdp.sendNotification("doSeek", currentTime - timeAdjustment);
}
if (transition === "headshot") {
kdp.sendNotification(
"doSeek",
parseInt(event.key) / 10 * kdp.evaluate("{duration}")
);
}
}
function saveBookmark() {
new Promise((resolve, reject) => {
localStorage.setItem(
course_id,
document.querySelector(".clipselected").dataset.clipRef
);
resolve();
});
}
function loadBookmark() {
var video_id = localStorage.getItem(course_id);
if (video_id) {
Array.from(document.querySelectorAll(".videotoc a"))
.filter(a => a.dataset.clipRef === video_id)[0]
.click();
document.getElementById("tocWrapper").scrollTop = document.querySelector(
".clipselected"
).offsetParent.offsetTop;
}
}
function calculateWatchedTime() {
var totalTime = 0,
watchedTime = 0;
var selectedClipIndex = Array.from(document.querySelectorAll(".videotoc a"))
.filter(a => a.dataset.clipRef)
.map(a => a.className)
.indexOf("clipselected");
var spans = Array.from(document.querySelectorAll(".videotoc p span")).filter(
span => {
return span.parentElement.classList[1] === "time" && span.innerText;
}
);
totalTime = collectTime(spans);
spans.splice(selectedClipIndex);
watchedTime = collectTime(spans);
return `${Math.round(watchedTime / totalTime * 100)}%`;
}
function collectTime(spans) {
var hour = 0,
min = 0,
second = 0;
spans.forEach(span => {
var chunks = span.innerText.split(":");
hour += parseInt(chunks[0]);
min += parseInt(chunks[1]);
second += parseInt(chunks[2]);
});
return hour * 60 * 60 + min * 60 + second;
}
function setPercentage() {
percentage.innerText = calculateWatchedTime();
}
kWidget.addReadyCallback(function(playerId) {
kdp = document.getElementById(playerId);
kdp.kBind("playerPlayEnd", function(data, id) {
document.getElementById("bcv_next").click();
});
});
let vr = new VolumeRange();
let sr = new SpeedRange();
document.addEventListener("keydown", keyEvents);
$(document).ajaxSuccess((event, xhr, settings) => {
if (/ajaxtoc/.test(settings.url)) {
rearrange()
.then(saveBookmark)
.then(setPercentage);
}
});
$(document).ajaxSend(
() => (tocWrapperScrollTop = document.getElementById("tocWrapper").scrollTop)
);
rearrange().then(loadBookmark);
@kubarium
Copy link
Author

kubarium commented Apr 24, 2018

The current version allows you to:

  • Bookmark, in the localStorage, the last video you watched and loads it next time you initialize this snippet
  • Trigger the next video once the current one reaches the end of the playback
  • Use numpad or standard number keys to jump to a part of the video like pressing 3 will take you 30% into the video
  • Alter styles as a variable for easy editing. They were initially too nested and kind of lost in lines of Javascript
  • Change volume using up and down arrows
  • Change speed using up and down arrows holding the ALT key
  • Change the playback head using left and right arrows with options of:
    • 10 seconds backward and forward with simple key pressings
    • Double the amount if the ALT key is pressed
    • Triple the amount if the SHIFT key is pressed
    • Sextuple the amount if ALT and SHIFT keys are both pressed
  • Stop and resume using the SPACE key
  • Moves to the next or previous video using the PERIOD (>) and the COMMA (<) keys respectively

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment