mirror of
https://github.com/mehotkhan/BandersnatchInteractive.git
synced 2025-07-28 09:42:55 +00:00
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.
This commit is contained in:
parent
2c987cd81e
commit
4195108b0d
1 changed files with 77 additions and 85 deletions
|
@ -1,13 +1,15 @@
|
||||||
|
// Data
|
||||||
var segmentMap = SegmentMap;
|
var segmentMap = SegmentMap;
|
||||||
var bv = bandersnatch.videos['80988062'].interactiveVideoMoments.value;
|
var bv = bandersnatch.videos['80988062'].interactiveVideoMoments.value;
|
||||||
var choicePoints = bv.choicePointNavigatorMetadata.choicePointsMetadata.choicePoints;
|
var choicePoints = bv.choicePointNavigatorMetadata.choicePointsMetadata.choicePoints;
|
||||||
var momentsBySegment = bv.momentsBySegment;
|
var momentsBySegment = bv.momentsBySegment;
|
||||||
var segmentGroups = bv.segmentGroups;
|
var segmentGroups = bv.segmentGroups;
|
||||||
|
|
||||||
|
// Global mutable state
|
||||||
var captions = {};
|
var captions = {};
|
||||||
var currentSegment;
|
var currentSegment;
|
||||||
var currentMoment;
|
|
||||||
var nextSegment = null;
|
var nextSegment = null;
|
||||||
var momentSelected = null;
|
var currentMoments = [];
|
||||||
var persistentState = bv.stateHistory;
|
var persistentState = bv.stateHistory;
|
||||||
var globalChoices = {};
|
var globalChoices = {};
|
||||||
|
|
||||||
|
@ -82,6 +84,8 @@ function findSegment(id) {
|
||||||
return 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) {
|
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) {
|
if (ms >= v.startTimeMs && ms < v.endTimeMs) {
|
||||||
|
@ -95,16 +99,16 @@ function getSegmentMs(segmentId) {
|
||||||
return segmentMap.segments[segmentId].startTimeMs;
|
return segmentMap.segments[segmentId].startTimeMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMoment(ms) {
|
function getMoments(segmentId, ms) {
|
||||||
for (const [k, v] of Object.entries(momentsBySegment)) {
|
let result = {};
|
||||||
for (let r of v)
|
let moments = momentsBySegment[segmentId] || [];
|
||||||
if (r.type == 'scene:cs_bs') {
|
for (let i = 0; i < moments.length; i++) {
|
||||||
if (ms >= r.startMs && ms < r.endMs) {
|
let m = moments[i];
|
||||||
return r;
|
if (ms >= m.startMs && ms < m.endMs) {
|
||||||
}
|
result[segmentId + '-' + i] = m;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function newList(id) {
|
function newList(id) {
|
||||||
|
@ -180,66 +184,75 @@ function addChoices(r) {
|
||||||
document.getElementById("choiceCaption").innerHTML = choicePoints[r.id].description;
|
document.getElementById("choiceCaption").innerHTML = choicePoints[r.id].description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function momentStart(m) {
|
||||||
function updateProgressBar(ms, r) {
|
console.log('momentStart', m);
|
||||||
var p = 0;
|
if (m.type == 'scene:cs_bs') {
|
||||||
|
addZones(currentSegment);
|
||||||
if (r && ms > r.startMs && ms < r.endMs) {
|
addChoices(m);
|
||||||
p = 100 - Math.floor((ms - r.startMs) * 100 / (r.endMs - r.startMs));
|
|
||||||
}
|
}
|
||||||
|
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 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) {
|
function ontimeupdate(evt) {
|
||||||
var ms = getCurrentMs();
|
var ms = getCurrentMs();
|
||||||
|
|
||||||
var segmentId = getSegmentId(ms);
|
var segmentId = getSegmentId(ms);
|
||||||
|
|
||||||
// ontimeupdate resolution is about a second, better use timer
|
// ontimeupdate resolution is about a second. Augment it using timer.
|
||||||
clearTimeout(timerId);
|
if (timerId) {
|
||||||
|
clearTimeout(timerId);
|
||||||
|
timerId = 0;
|
||||||
|
}
|
||||||
if (segmentId && nextSegment && nextSegment != segmentId) {
|
if (segmentId && nextSegment && nextSegment != segmentId) {
|
||||||
var timeLeft = SegmentMap.segments[segmentId].endTimeMs - ms;
|
var timeLeft = segmentMap.segments[segmentId].endTimeMs - ms;
|
||||||
timerId = setTimeout(ontimeout, timeLeft, nextSegment);
|
timerId = setTimeout(ontimeupdate, timeLeft);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentSegment != segmentId) {
|
if (currentSegment != segmentId) {
|
||||||
console.log('ontimeupdate', currentSegment, segmentId, ms, msToString(ms));
|
console.log('ontimeupdate', currentSegment, segmentId, ms, msToString(ms));
|
||||||
currentSegment = segmentId;
|
// Distinguish between the user seeking manually with <video> controls,
|
||||||
addZones(segmentId);
|
// from the video playing past the current segment end.
|
||||||
currentMoment = null;
|
if (ms > segmentMap.segments[currentSegment].endTimeMs &&
|
||||||
addChoices(0);
|
ms < segmentMap.segments[currentSegment].endTimeMs + 2000) {
|
||||||
|
// TODO: activate and apply user choice (whether or not it
|
||||||
|
// was default) instead of just playing the next segment.
|
||||||
|
playSegment(nextSegment, true);
|
||||||
|
} else {
|
||||||
|
playSegment(segmentId, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var r = getMoment(ms);
|
var moments = getMoments(segmentId, ms);
|
||||||
if (r && momentSelected != r.id) {
|
for (let k in currentMoments)
|
||||||
updateProgressBar(ms, r);
|
if (!(k in moments))
|
||||||
if (currentMoment != r.id) {
|
momentEnd(currentMoments[k]);
|
||||||
currentMoment = r.id;
|
for (let k in currentMoments)
|
||||||
console.log('interaction', currentMoment);
|
if (k in moments)
|
||||||
addChoices(r);
|
momentUpdate(currentMoments[k], ms);
|
||||||
}
|
for (let k in moments)
|
||||||
} else {
|
if (!(k in currentMoments))
|
||||||
currentMoment = null;
|
momentStart(moments[k]);
|
||||||
addChoices(0);
|
currentMoments = moments;
|
||||||
updateProgressBar(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function jumpForward() {
|
function jumpForward() {
|
||||||
|
@ -379,19 +392,13 @@ function seek(ms) {
|
||||||
function choice(choiceId, text, id) {
|
function choice(choiceId, text, id) {
|
||||||
var segmentId = findSegment(choiceId);
|
var segmentId = findSegment(choiceId);
|
||||||
console.log('choice', choiceId, 'nextSegment', segmentId);
|
console.log('choice', choiceId, 'nextSegment', segmentId);
|
||||||
applyImpression(globalChoices[id]);
|
applyImpression(globalChoices[id].impressionData);
|
||||||
setNextSegment(segmentId, text);
|
setNextSegment(segmentId, text);
|
||||||
momentSelected = choiceId;
|
momentSelected = choiceId;
|
||||||
addChoices(0);
|
addChoices(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyImpression(obj) {
|
function applyImpression(impressionData) {
|
||||||
if (!obj) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var impressionData = obj.impressionData;
|
|
||||||
|
|
||||||
if (impressionData && impressionData.type == 'userState') {
|
if (impressionData && impressionData.type == 'userState') {
|
||||||
for (const [variable, value] of Object.entries(impressionData.data.persistent)) {
|
for (const [variable, value] of Object.entries(impressionData.data.persistent)) {
|
||||||
console.log('persistentState set', variable, '=', value);
|
console.log('persistentState set', variable, '=', value);
|
||||||
|
@ -400,31 +407,16 @@ function applyImpression(obj) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyPlaybackImpression(segmentId) {
|
function playSegment(segmentId, noSeek) {
|
||||||
let moments = momentsBySegment[segmentId];
|
|
||||||
|
|
||||||
if (!moments) {
|
|
||||||
console.log('warning - no moments');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let moment of moments) {
|
|
||||||
if (moment.type != 'notification:playbackImpression') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
applyImpression(moment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function playSegment(segmentId) {
|
|
||||||
clearTimeout(timerId);
|
|
||||||
if (!segmentId || typeof segmentId === "undefined")
|
if (!segmentId || typeof segmentId === "undefined")
|
||||||
segmentId = segmentMap.initialSegment;
|
segmentId = segmentMap.initialSegment;
|
||||||
console.log('playSegment', segmentId);
|
console.log('playSegment', currentSegment, '->', segmentId);
|
||||||
applyPlaybackImpression(segmentId);
|
|
||||||
location.hash = segmentId;
|
|
||||||
document.title = 'Bandersnatch - Chapter ' + segmentId;
|
document.title = 'Bandersnatch - Chapter ' + segmentId;
|
||||||
var ms = getSegmentMs(segmentId);
|
var oldSegment = getSegmentId(getCurrentMs());
|
||||||
seek(ms);
|
currentSegment = segmentId;
|
||||||
|
location.hash = segmentId;
|
||||||
|
if (!noSeek || oldSegment != segmentId) {
|
||||||
|
var ms = getSegmentMs(segmentId);
|
||||||
|
seek(ms);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue