-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathpresentation-viewer.js
211 lines (171 loc) · 7.78 KB
/
presentation-viewer.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
(function() {
"use strict";
const caption_url = "https://www.w3.org/2020/06/machine-learning-workshop/talks/captions/" + shortname + ".vtt";
let captions; // For each language one array of subtitles
let cuelang = "en";
let cue_elt = document.getElementById("cue");
let slidenr_elt = document.getElementById("slidenr");
let firstslide_elt = document.getElementById("firstslide");
let prevslide_elt = document.getElementById("prevslide");
let nextslide_elt = document.getElementById("nextslide");
// "syncelts" includes both slides and incremental display items.
let syncelts = document.querySelectorAll(".slide, .next");
let current = 0; // Active element (index in syncelts)
let curslide = 0; // Currently visible slide (index in syncelts)
let curcue = -1; // Current caption (index in captions)
let video = document.getElementById("video"); // Only one of video and
let nexttalk_elt = document.getElementById("nexttalk");
// Load the English captions
let parser = new WebVTTParser();
let timecodes = [];
fetch(caption_url).then(r => r.text())
.then(text => {
let tree = parser.parse(text, "captions");
if (tree.errors.length)
cue_elt.textContent = "Error: line " + tree.errors[0].line +
" of " + caption_url + ": " + tree.errors[0].message;
for (let cue of tree.cues) {
if (cue.id && cue.id.startsWith("slide-")) {
timecodes.push(cue.startTime);
}
cue.text = cue.text.replace(/<\/?v[^>]*>/g,"");
}
captions = tree.cues;
});
// Round the timecodes to whole seconds, because the StreamFizz
// player can only seek to whole seconds.
// Also work around a bug in the player: seeking to 0 does not
// work, but seeking to 0.01 does.
if (video) {
for (let i = 0; i < timecodes.length; i++)
timecodes[i] = Math.round(timecodes[i]);
if (timecodes[0] == 0) timecodes[0] = 0.01;
}
// Activate and announce the first slide. (Assumes there is at least one.)
syncelts[0].classList.add("active");
announce(0);
// announce -- show the slide label in the output element
function announce(n)
{
if (!slidenr_elt) return; // No output element
let label = syncelts[n].getAttribute("aria-label");
let target = syncelts[n].id;
if (!target) {
slidenr_elt.textContent = label;
} else {
while (slidenr_elt.firstChild)
slidenr_elt.removeChild(slidenr_elt.firstChild);
let e = document.createElement("A");
e.textContent = label;
e.href = "#" + target;
slidenr_elt.appendChild(e);
}
}
// activate -- deactivate the current element and activate the new one
function activate(new_index)
{
if (!syncelts[current]) {
announce(new_index);
return;
}
if (new_index < current) {
// Deactivate the old current element and activate the new.
syncelts[current].classList.remove("active");
current = new_index;
syncelts[current].classList.add("active");
// Find containing slide i.
let i = current;
while (i > 0 && !syncelts[i].classList.contains("slide")) i--;
// If it is a new slide, deactivate the old one and activate the new.
if (i != curslide) {
// Deactivate the old slide and elements inside it.
do syncelts[curslide++].classList.remove("active");
while (curslide < syncelts.length &&
! syncelts[curslide].classList.contains("slide"));
// Activate the new slide.
syncelts[i].classList.add("active");
// Announce the new slide number.
announce(i);
curslide = i;
}
} else if (new_index > current) {
current = new_index;
// If this is a slide, deactivate the previous slide.
if (syncelts[current] && syncelts[current].classList.contains("slide")) {
syncelts[curslide].classList.remove("active");
syncelts[curslide].classList.add("visited");
curslide = current;
// Announce the new slide number.
announce(current);
}
// Activate the new current element (slide or other element).
syncelts[current].classList.add("active");
}
}
// seek_video -- set the video to the start time of the current slide
function seek_video()
{
if (current in timecodes) {
if (video)
video.contentWindow.postMessage(["seek" ,timecodes[current]], "*");
}
}
// Event handler for the "first slide" button.
if (firstslide_elt) firstslide_elt.addEventListener("click",
function(ev) {
ev.preventDefault();
activate(0); // Move the "active" class
seek_video();
});
// Event handler for the "previous slide" button.
if (prevslide_elt) prevslide_elt.addEventListener("click",
function(ev) {
ev.preventDefault();
if (current == 0) return; // Already at first element
activate(current - 1); // Move the "active" class
seek_video();
});
// Event handler for the "next slide" button.
if (nextslide_elt) nextslide_elt.addEventListener("click",
function(ev) {
ev.preventDefault();
if (current == syncelts.length - 1) return; // No next element
activate(current + 1); // Move the "active" class
seek_video();
});
// search -- return index of x in array a, or -1 if not found
function search(x, a, cmp)
{
let lo = 0, hi = a.length - 1;
while (lo <= hi) {
let m = Math.floor((lo + hi)/2), r = cmp(x, a[m]);
if (r < 0) hi = m - 1;
else if (r > 0) lo = m + 1;
else return m;
}
return -1;
}
// As the video plays, announce the relevant slide and make it active.
window.addEventListener("message",
function(ev) {
const type = ev.data[0];
const t = ev.data[1];
if (type !== "position") return;
// console.log("message: t = " + t);
// Find the caption corresponding to time t.
if (captions) {
let i = search(t, captions,
function(a,b) {return a<b.startTime ? -1 : a>b.endTime ? 1 : 0});
if (i == curcue) ; // Output already has the right cue
else if (i < 0) cue_elt.innerHTML = "";
else cue_elt.innerHTML = captions[i].text;
curcue = i;
}
// Find index i corresponding to time t. Search forward then backward.
let i = Math.min(current, timecodes.length - 1);
while (i < timecodes.length - 1 && t > timecodes[i]) i++;
while (i > 0 && t < timecodes[i]) i--;
// If i is not the current element, move the "active" class.
activate(i);
});
})();