From 2826849ee0973d691b76908174e21eeb47beee19 Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Sat, 27 Jul 2019 20:56:47 +0000 Subject: [PATCH 01/35] assets/scripts.js: Fix JS warnings --- assets/scripts.js | 51 +++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/assets/scripts.js b/assets/scripts.js index 01826f8..d59085b 100644 --- a/assets/scripts.js +++ b/assets/scripts.js @@ -25,14 +25,14 @@ function generateJs(cond) { } else if (cond[0] == 'not') { return '!(' + generateJs(cond[1]) + ')'; } else if (cond[0] == 'and') { - var conds = []; - for (var i = 1; i < cond.length; i++) { + let conds = []; + for (let i = 1; i < cond.length; i++) { conds.push('(' + generateJs(cond[i]) + ')'); } return '(' + conds.join(' && ') + ')'; } else if (cond[0] == 'or') { - var conds = []; - for (var i = 1; i < cond.length; i++) { + let conds = []; + for (let i = 1; i < cond.length; i++) { conds.push('(' + generateJs(cond[i]) + ')'); } return '(' + conds.join(' || ') + ')'; @@ -61,13 +61,13 @@ function findSegment(id) { if (id.startsWith('nsg-')) { id = id.substr(4); } - if (SegmentMap.segments[id]) { + if (segmentMap.segments[id]) { // check precondition return id; } if (segmentGroups[id]) { - for (v of segmentGroups[id]) { + for (let v of segmentGroups[id]) { if (v.segmentGroup) { return findSegment(v.segmentGroup); } else if (v.segment) { @@ -88,7 +88,7 @@ function getChoiceMs(choiceId) { } function getSegmentId(ms) { - for (const [k, v] of Object.entries(SegmentMap.segments)) { + for (const [k, v] of Object.entries(segmentMap.segments)) { if (ms >= v.startTimeMs && ms < v.endTimeMs) { return k; } @@ -102,7 +102,7 @@ function getSegmentMs(segmentId) { function getMoment(ms) { for (const [k, v] of Object.entries(momentsBySegment)) { - for (r of v) + for (let r of v) if (r.type == 'scene:cs_bs') { if (ms >= r.startMs && ms < r.endMs) { return r; @@ -140,24 +140,24 @@ function setNextSegment(segmentId, comment) { function addZones(segmentId) { var ul = newList("interactionZones"); - var caption = 'currentSegment(' + segmentId + ')'; + let caption = 'currentSegment(' + segmentId + ')'; addItem(ul, caption, 'javascript:playSegment("' + segmentId + '")'); var v = segmentMap.segments[segmentId]; if (v && v.ui && v.ui.interactionZones) { var index = 0; - for (z of v.ui.interactionZones) { + for (var z of v.ui.interactionZones) { var startMs = z[0]; var stopMs = z[1]; - var caption = segmentId + ' interactionZone ' + index; + let caption = segmentId + ' interactionZone ' + index; addItem(ul, caption, 'javascript:seek(' + startMs + ')'); index++; } } - var ul = newList("nextSegments"); + ul = newList("nextSegments"); for (const [k, v] of Object.entries(segmentMap.segments[segmentId].next)) { - var caption = captions[k] ? captions[k] : k; + let caption = captions[k] ? captions[k] : k; if (segmentMap.segments[segmentId].defaultNext == k) { caption = '[' + caption + ']'; setNextSegment(k); @@ -170,9 +170,9 @@ function addChoices(r) { var ul = newList("choices"); document.getElementById("choiceCaption").innerHTML = ''; if (!r) return; - index = 0; + let index = 0; - for (x of r.choices) { + for (let x of r.choices) { console.log(x.id, 'choice saved'); globalChoices[x.id] = x; @@ -247,14 +247,14 @@ function ontimeupdate(evt) { } } -function jumpForward(ms) { +function jumpForward() { var ms = getCurrentMs(); var segmentId = getSegmentId(ms); var v = segmentMap.segments[segmentId]; var interactionMs = 0; if (v && v.ui && v.ui.interactionZones) { - for (z of v.ui.interactionZones) { + for (var z of v.ui.interactionZones) { var startMs = z[0]; var stopMs = z[1]; if (ms < startMs) @@ -315,13 +315,13 @@ function onload() { var video_source_selector = document.getElementById("video-source"); var file_selector = document.getElementById("file-selector"); if (video_source_selector.getAttribute("src") == '') { - console.log('no video') + console.log('no video'); file_selector.style.display = 'table'; document.getElementById("wrapper-video").style.display = 'none'; } document.getElementById('fileinput').addEventListener('change', function () { var file = this.files[0]; - var fileUrl = URL.createObjectURL(file) + var fileUrl = URL.createObjectURL(file); video_selector.src = fileUrl; video_selector.play(); file_selector.style.display = 'none'; @@ -347,7 +347,7 @@ function onload() { playSegment(0); if (e.code == 'Space') togglePlayPause(); - } + }; document.onkeydown = function (evt) { var v = document.getElementById("video"); @@ -359,14 +359,13 @@ function onload() { if (evt.key == 'ArrowRight') { jumpForward(); } - } + }; if (location.hash) { var segmentId = location.hash.slice(1); playSegment(segmentId); } - -} +}; function seek(ms) { clearTimeout(timerId); @@ -389,7 +388,7 @@ function applyImpression(obj) { return; } - impressionData = obj.impressionData; + var impressionData = obj.impressionData; if (impressionData && impressionData.type == 'userState') { for (const [variable, value] of Object.entries(impressionData.data.persistent)) { @@ -407,7 +406,7 @@ function applyPlaybackImpression(segmentId) { return; } - for (moment of moments) { + for (let moment of moments) { if (moment.type != 'notification:playbackImpression') { continue; } @@ -426,4 +425,4 @@ function playSegment(segmentId) { document.title = 'Bandersnatch - Chapter ' + segmentId; var ms = getSegmentMs(segmentId); seek(ms); -} \ No newline at end of file +} From 19c8003e62dc39be39d84cc44dc79df41c62291f Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Sat, 27 Jul 2019 21:26:34 +0000 Subject: [PATCH 02/35] assets/scripts.js: Delete unused function --- assets/scripts.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/assets/scripts.js b/assets/scripts.js index d59085b..d9a8193 100644 --- a/assets/scripts.js +++ b/assets/scripts.js @@ -82,11 +82,6 @@ function findSegment(id) { return id; } -function getChoiceMs(choiceId) { - var segmentId = findSegment(choiceId); - return getSegmentMs(segmentId); -} - function getSegmentId(ms) { for (const [k, v] of Object.entries(segmentMap.segments)) { if (ms >= v.startTimeMs && ms < v.endTimeMs) { From afc3abb81c8b3480a357576110670ac64c777dfa Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Sat, 27 Jul 2019 22:04:21 +0000 Subject: [PATCH 03/35] Fix onload JS error --- assets/scripts.js | 2 +- index.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/scripts.js b/assets/scripts.js index d9a8193..72d9a8f 100644 --- a/assets/scripts.js +++ b/assets/scripts.js @@ -305,7 +305,7 @@ function togglePlayPause() { else v.pause(); } -function onload() { +window.onload = function() { var video_selector = document.getElementById("video"); var video_source_selector = document.getElementById("video-source"); var file_selector = document.getElementById("file-selector"); diff --git a/index.html b/index.html index 7b83fcd..d676070 100644 --- a/index.html +++ b/index.html @@ -24,7 +24,7 @@ - +
From 64bad02a3be632b6e8cb19f14e2f94d91a786250 Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Sat, 27 Jul 2019 22:09:35 +0000 Subject: [PATCH 04/35] assets/scripts.js: Do not trigger key events with modifier keys These key combinations usually mean something else and are not intended for the web page. --- assets/scripts.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/assets/scripts.js b/assets/scripts.js index 72d9a8f..31487c9 100644 --- a/assets/scripts.js +++ b/assets/scripts.js @@ -336,6 +336,8 @@ window.onload = function() { }; document.onkeypress = function (e) { + if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) + return; if (e.code == 'KeyF') toggleFullScreen(); if (e.code == 'KeyR') @@ -344,14 +346,13 @@ window.onload = function() { togglePlayPause(); }; - document.onkeydown = function (evt) { - var v = document.getElementById("video"); - - if (evt.key == 'ArrowLeft') { + document.onkeydown = function (e) { + if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) + return; + if (e.key == 'ArrowLeft') { jumpBack(); } - - if (evt.key == 'ArrowRight') { + if (e.key == 'ArrowRight') { jumpForward(); } }; From 68ec8bb628054a00f74630afd8c37afbec326bf1 Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Sat, 27 Jul 2019 22:34:51 +0000 Subject: [PATCH 05/35] assets/scripts.js: Fix check for undefined --- assets/scripts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/scripts.js b/assets/scripts.js index 31487c9..43c18e5 100644 --- a/assets/scripts.js +++ b/assets/scripts.js @@ -413,7 +413,7 @@ function applyPlaybackImpression(segmentId) { function playSegment(segmentId) { clearTimeout(timerId); - if (!segmentId || segmentId == "undefined") + if (!segmentId || typeof segmentId === "undefined") segmentId = '1A'; console.log('playSegment', segmentId); applyPlaybackImpression(segmentId); From 7e26ff3ddb1abbfa684d6830bbfd5227259daa33 Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Sat, 27 Jul 2019 22:35:20 +0000 Subject: [PATCH 06/35] assets/scripts.js: Don't hardcode initial segment --- assets/scripts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/scripts.js b/assets/scripts.js index 43c18e5..eede1b0 100644 --- a/assets/scripts.js +++ b/assets/scripts.js @@ -414,7 +414,7 @@ function applyPlaybackImpression(segmentId) { function playSegment(segmentId) { clearTimeout(timerId); if (!segmentId || typeof segmentId === "undefined") - segmentId = '1A'; + segmentId = segmentMap.initialSegment; console.log('playSegment', segmentId); applyPlaybackImpression(segmentId); location.hash = segmentId; From 2c987cd81e6aeb02b33e88971b77a6f7236c98d2 Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Sat, 27 Jul 2019 23:33:31 +0000 Subject: [PATCH 07/35] assets/scripts.js: Improve handling of pre-specified video src Fixes behavior if HTML is edited to indicate the video source. --- assets/scripts.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/assets/scripts.js b/assets/scripts.js index eede1b0..0ababa7 100644 --- a/assets/scripts.js +++ b/assets/scripts.js @@ -309,18 +309,24 @@ window.onload = function() { var video_selector = document.getElementById("video"); var video_source_selector = document.getElementById("video-source"); var file_selector = document.getElementById("file-selector"); + function startPlayback() { + file_selector.style.display = 'none'; + playSegment(null); + video_selector.play(); + } if (video_source_selector.getAttribute("src") == '') { console.log('no video'); file_selector.style.display = 'table'; document.getElementById("wrapper-video").style.display = 'none'; + } else { + startPlayback(); } document.getElementById('fileinput').addEventListener('change', function () { var file = this.files[0]; var fileUrl = URL.createObjectURL(file); video_selector.src = fileUrl; - video_selector.play(); - file_selector.style.display = 'none'; document.getElementById("wrapper-video").style.display = 'block'; + startPlayback(); }, false); video_selector.ontimeupdate = ontimeupdate; From 4195108b0d6986d850bed8a911d4f24c06b6c947 Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Sat, 27 Jul 2019 23:57:15 +0000 Subject: [PATCH 08/35] assets/scripts.js: Track time and moments properly WIP. - Fixes a family of bugs such as pausing the video during a choice resulting in the video resuming at the default choice (while still paused) after a while. - Unifies tracking of moments and cleans up redundant code. - Fixes unnecessary stutter when transitioning between two segments that are adjacent in the video file. --- assets/scripts.js | 162 ++++++++++++++++++++++------------------------ 1 file changed, 77 insertions(+), 85 deletions(-) diff --git a/assets/scripts.js b/assets/scripts.js index 0ababa7..7a0cffb 100644 --- a/assets/scripts.js +++ b/assets/scripts.js @@ -1,13 +1,15 @@ +// Data var segmentMap = SegmentMap; var bv = bandersnatch.videos['80988062'].interactiveVideoMoments.value; var choicePoints = bv.choicePointNavigatorMetadata.choicePointsMetadata.choicePoints; var momentsBySegment = bv.momentsBySegment; var segmentGroups = bv.segmentGroups; + +// Global mutable state var captions = {}; var currentSegment; -var currentMoment; var nextSegment = null; -var momentSelected = null; +var currentMoments = []; var persistentState = bv.stateHistory; var globalChoices = {}; @@ -82,6 +84,8 @@ function findSegment(id) { return id; } +/// Returns the segment ID at the given timestamp. +/// There will be exactly one segment for any timestamp within the video file. function getSegmentId(ms) { for (const [k, v] of Object.entries(segmentMap.segments)) { if (ms >= v.startTimeMs && ms < v.endTimeMs) { @@ -95,16 +99,16 @@ function getSegmentMs(segmentId) { return segmentMap.segments[segmentId].startTimeMs; } -function getMoment(ms) { - for (const [k, v] of Object.entries(momentsBySegment)) { - for (let r of v) - if (r.type == 'scene:cs_bs') { - if (ms >= r.startMs && ms < r.endMs) { - return r; - } - } +function getMoments(segmentId, ms) { + let result = {}; + let moments = momentsBySegment[segmentId] || []; + for (let i = 0; i < moments.length; i++) { + let m = moments[i]; + if (ms >= m.startMs && ms < m.endMs) { + result[segmentId + '-' + i] = m; + } } - return null; + return result; } function newList(id) { @@ -180,66 +184,75 @@ function addChoices(r) { document.getElementById("choiceCaption").innerHTML = choicePoints[r.id].description; } - -function updateProgressBar(ms, r) { - var p = 0; - - if (r && ms > r.startMs && ms < r.endMs) { - p = 100 - Math.floor((ms - r.startMs) * 100 / (r.endMs - r.startMs)); +function momentStart(m) { + console.log('momentStart', m); + if (m.type == 'scene:cs_bs') { + addZones(currentSegment); + addChoices(m); } + applyImpression(m.impressionData); +} - document.getElementById("progress").style.width = p + '%'; +function momentUpdate(m, ms) { + //console.log('momentUpdate', m); + if (m.type == 'scene:cs_bs') { + var p = 100 - Math.floor((ms - m.startMs) * 100 / (m.endMs - m.startMs)); + document.getElementById("progress").style.width = p + '%'; + } +} + +function momentEnd(m) { + console.log('momentEnd', m); + if (m.type == 'scene:cs_bs') { + setNextSegment(null); + addZones(currentSegment); + addChoices(0); + document.getElementById("progress").style.width = 0; + } } var timerId = 0; -var switchFrom = null; -var switchTo = null; - -function ontimeout(nextSegment) { - console.log('ontimeout', nextSegment); - - if (switchFrom != currentSegment || switchTo != nextSegment) { - playSegment(nextSegment); - } - - switchFrom = currentSegment; - switchTo = nextSegment; -} - function ontimeupdate(evt) { var ms = getCurrentMs(); var segmentId = getSegmentId(ms); - // ontimeupdate resolution is about a second, better use timer - clearTimeout(timerId); + // ontimeupdate resolution is about a second. Augment it using timer. + if (timerId) { + clearTimeout(timerId); + timerId = 0; + } if (segmentId && nextSegment && nextSegment != segmentId) { - var timeLeft = SegmentMap.segments[segmentId].endTimeMs - ms; - timerId = setTimeout(ontimeout, timeLeft, nextSegment); + var timeLeft = segmentMap.segments[segmentId].endTimeMs - ms; + timerId = setTimeout(ontimeupdate, timeLeft); } if (currentSegment != segmentId) { console.log('ontimeupdate', currentSegment, segmentId, ms, msToString(ms)); - currentSegment = segmentId; - addZones(segmentId); - currentMoment = null; - addChoices(0); + // Distinguish between the user seeking manually with
-