Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open interactive area if chapters or references are present in video #644

Merged
merged 1 commit into from
May 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 65 additions & 50 deletions app/assets/javascripts/thyme/chapter_manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,31 @@ class ChapterManager {
this.iaBackButton = iaBackButton;
}

load() {
/**
* Loads chapters from the video element and displays them in the interactive area.
* @param {function} onLoad - Callback function that is called when chapters have been loaded.
* It receives a boolean value that indicates whether chapters are present.
*/
load(onLoad) {
let initialChapters = true;
const videoId = thymeAttributes.video.id;
const chaptersElement = $("#" + videoId + ' track[kind="chapters"]').get(0);
const chapters = this.#getChapters();
const chapterManager = this;

/* after video metadata have been loaded, display chapters in the interactive area
Originally (and more appropriately, according to the standards),
only the 'loadedmetadata' event was used. However, Firefox triggers this event too soon,
i.e. when the readyStates for chapters and elements are 1 (loading) instead of 2 (loaded)
for the events, see https://www.w3schools.com/jsref/event_oncanplay.asp */
video.addEventListener("loadedmetadata", function () {
if (initialChapters && chaptersElement.readyState === 2) {
if (initialChapters && chapters.readyState === 2) {
chapterManager.#displayChapters();
initialChapters = false;
if (onLoad) {
onLoad(chapters.track ? (chapters.track.cues.length > 0) : false);
}
}
});
video.addEventListener("canplay", function () {
if (initialChapters && chaptersElement.readyState === 2) {
if (initialChapters && chapters.readyState === 2) {
chapterManager.#displayChapters();
initialChapters = false;
}
Expand Down Expand Up @@ -66,56 +72,65 @@ class ChapterManager {
}
}

#displayChapters() {
#getChapters() {
const videoId = thymeAttributes.video.id;
return $("#" + videoId + ' track[kind="chapters"]').get(0);
}

#displayChapters() {
const chapterListId = this.chapterListId;
const iaBackButton = this.iaBackButton;
const chapterList = $("#" + chapterListId);
const chaptersElement = $("#" + videoId + ' track[kind="chapters"]').get(0);

let chaptersTrack;
if (chaptersElement.readyState === 2 && (chaptersTrack = chaptersElement.track)) {
chaptersTrack.mode = "hidden";
let times = [];
// read out the chapter track cues and generate html elements for chapters,
// run katex on them
for (let i = 0; i < chaptersTrack.cues.length; i++) {
const cue = chaptersTrack.cues[i];
const chapterName = cue.text;
const start = cue.startTime;
times.push(start);
const $listItem = $("<li/>");
const $link = $("<a/>", {
id: "c-" + start,
text: chapterName,
});
chapterList.append($listItem.append($link));
const chapterElement = $link.get(0);
thymeUtility.renderLatex(chapterElement);
$link.data("text", chapterName);
// if a chapter element is clicked, transport to chapter start time
$link.on("click", function () {
iaBackButton.update();
video.currentTime = this.id.replace("c-", "");
});
}
// store start times as data attribute
chapterList.get(0).dataset.times = JSON.stringify(times);
chapterList.show();
// if the chapters cue changes (i.e. a switch between chapters), highlight
// current chapter elment and scroll it into view, remove highlighting from
// old chapter
$(chaptersTrack).on("cuechange", function () {
$("#" + chapterListId + " li a").removeClass("current");
if (this.activeCues.length > 0) {
const activeStart = this.activeCues[0].startTime;
const chapter = document.getElementById("c-" + activeStart);
if (chapter) {
$(chapter).addClass("current");
chapter.scrollIntoView();
}
}
const chapters = this.#getChapters();
if (chapters.readyState != 2) {
return;
}
const track = chapters.track;
if (!track) {
return;
}

track.mode = "hidden";
let times = [];
// read out the chapter track cues and generate html elements for chapters,
// run katex on them
for (let i = 0; i < track.cues.length; i++) {
const cue = track.cues[i];
const chapterName = cue.text;
const start = cue.startTime;
times.push(start);
const $listItem = $("<li/>");
const $link = $("<a/>", {
id: "c-" + start,
text: chapterName,
});
chapterList.append($listItem.append($link));
const chapterElement = $link.get(0);
thymeUtility.renderLatex(chapterElement);
$link.data("text", chapterName);
// if a chapter element is clicked, transport to chapter start time
$link.on("click", function () {
iaBackButton.update();
video.currentTime = this.id.replace("c-", "");
});
}
// store start times as data attribute
chapterList.get(0).dataset.times = JSON.stringify(times);
chapterList.show();
// if the chapters cue changes (i.e. a switch between chapters), highlight
// current chapter elment and scroll it into view, remove highlighting from
// old chapter
$(track).on("cuechange", function () {
$("#" + chapterListId + " li a").removeClass("current");
if (this.activeCues.length > 0) {
const activeStart = this.activeCues[0].startTime;
const chapter = document.getElementById("c-" + activeStart);
if (chapter) {
$(chapter).addClass("current");
chapter.scrollIntoView();
}
}
});
}
}
Loading