diff --git a/assets/scripts.js b/assets/scripts.js index 01826f8..1c0585c 100644 --- a/assets/scripts.js +++ b/assets/scripts.js @@ -1,15 +1,12 @@ +// 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; -var captions = {}; -var currentSegment; -var currentMoment; -var nextSegment = null; -var momentSelected = null; -var persistentState = bv.stateHistory; -var globalChoices = {}; + +// Persistent state +var ls = window.localStorage || {}; function msToString(ms) { return new Date(ms).toUTCString().split(' ')[4]; @@ -19,76 +16,73 @@ function getCurrentMs() { return Math.round(document.getElementById("video").currentTime * 1000.0); } -function generateJs(cond) { +function preconditionToJS(cond) { if (cond[0] == 'persistentState') { - return '!!persistentState["' + cond[1] + '"]'; + return 'JSON.parse(ls["persistentState_' + cond[1] + '"])'; } else if (cond[0] == 'not') { - return '!(' + generateJs(cond[1]) + ')'; + return '!(' + preconditionToJS(cond[1]) + ')'; } else if (cond[0] == 'and') { - var conds = []; - for (var i = 1; i < cond.length; i++) { - conds.push('(' + generateJs(cond[i]) + ')'); - } - return '(' + conds.join(' && ') + ')'; + return '(' + cond.slice(1).map(preconditionToJS).join(' && ') + ')'; } else if (cond[0] == 'or') { - var conds = []; - for (var i = 1; i < cond.length; i++) { - conds.push('(' + generateJs(cond[i]) + ')'); - } - return '(' + conds.join(' || ') + ')'; + return '(' + cond.slice(1).map(preconditionToJS).join(' || ') + ')'; + } else if (cond[0] == 'eql' && cond.length == 3) { + return '(' + cond.slice(1).map(preconditionToJS).join(' == ') + ')'; + } else if (cond === false) { + return false; + } else if (cond === true) { + return true; + } else if (typeof cond === 'string') { + return cond; } else { console.log('unsupported condition!', cond); return 'true'; } } -function checkPrecondition(segmentId) { - let precondition = bv.preconditions[segmentId]; - +function evalPrecondition(precondition, text) { if (precondition) { - let cond = generateJs(precondition); + let cond = preconditionToJS(precondition); let match = eval(cond); - - console.log(cond, '==', match); - + console.log('precondition', text, ':', cond, '==', match); return match; } return true; } -function findSegment(id) { - if (id.startsWith('nsg-')) { - id = id.substr(4); - } - if (SegmentMap.segments[id]) { - // check precondition - return id; - } +function checkPrecondition(preconditionId) { + return evalPrecondition(bv.preconditions[preconditionId], preconditionId); +} - if (segmentGroups[id]) { - for (v of segmentGroups[id]) { - if (v.segmentGroup) { - return findSegment(v.segmentGroup); - } else if (v.segment) { - // check precondition - return v.segment; - } else { - if (checkPrecondition(v)) - return v; - } +function resolveSegmentGroup(sg) { + let results = []; + for (let v of segmentGroups[sg]) { + if (v.precondition) { + if (!checkPrecondition(v.precondition)) + continue; + } + if (v.segmentGroup) { + results.push(resolveSegmentGroup(v.segmentGroup)); + } else if (v.segment) { + // TODO: does the included precondition override or + // complement the segment precondition? + if (!checkPrecondition(v.segment)) + continue; + results.push(v.segment); + } else { + if (!checkPrecondition(v)) + continue; + results.push(v); } } - return id; -} - -function getChoiceMs(choiceId) { - var segmentId = findSegment(choiceId); - return getSegmentMs(segmentId); + console.log('segment group', sg, '=>', results); + return results[0]; } +/// 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)) { + for (const [k, v] of Object.entries(segmentMap.segments)) { if (ms >= v.startTimeMs && ms < v.endTimeMs) { return k; } @@ -100,16 +94,17 @@ function getSegmentMs(segmentId) { return segmentMap.segments[segmentId].startTimeMs; } -function getMoment(ms) { - for (const [k, v] of Object.entries(momentsBySegment)) { - for (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]; + let momentId = segmentId + '/' + i; + if (ms >= m.startMs && ms < m.endMs && evalPrecondition(m.precondition, 'moment ' + momentId)) { + result[momentId] = m; + } } - return null; + return result; } function newList(id) { @@ -129,9 +124,13 @@ function addItem(ul, text, url) { ul.appendChild(li); } +var nextChoice = -1; +var nextSegment = null; + function setNextSegment(segmentId, comment) { console.log('setNextSegment', segmentId, comment); nextSegment = segmentId; + nextChoice = -1; var ul = newList("nextSegment"); var caption = 'nextSegment: ' + segmentId; addItem(ul, comment ? caption + ' (' + comment + ')' : caption, @@ -140,142 +139,267 @@ 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"); + let defaultSegmentId = null; for (const [k, v] of Object.entries(segmentMap.segments[segmentId].next)) { - var caption = captions[k] ? captions[k] : k; + let caption = k; if (segmentMap.segments[segmentId].defaultNext == k) { caption = '[' + caption + ']'; - setNextSegment(k); + defaultSegmentId = k; } addItem(ul, caption, 'javascript:playSegment("' + k + '")'); } + setNextSegment(defaultSegmentId); } +var currentChoiceMoment = null; + function addChoices(r) { + currentChoiceMoment = r; + nextChoice = -1; var ul = newList("choices"); document.getElementById("choiceCaption").innerHTML = ''; if (!r) return; - index = 0; - for (x of r.choices) { - console.log(x.id, 'choice saved'); - globalChoices[x.id] = x; + nextChoice = r.defaultChoiceIndex; + let index = 0; + for (let x of r.choices) { var caption = r.defaultChoiceIndex == index ? '[' + x.text + ']' : x.text; - addItem(ul, caption, 'javascript:choice("' + - (x.segmentId ? x.segmentId : (x.sg ? x.sg : x.id)) + '", "' + x.text + '", "' + x.id + '")'); + addItem(ul, caption, 'javascript:choice(' + index + ')'); index++; } - document.getElementById("choiceCaption").innerHTML = choicePoints[r.id].description; + if (r.id in choicePoints) + 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, seeked) { + console.log('momentStart', m, seeked); + if (m.choices) { + addChoices(m); } + if (!seeked) + applyImpression(m.impressionData); +} - document.getElementById("progress").style.width = p + '%'; +function momentUpdate(m, ms) { + //console.log('momentUpdate', m); + if (m.choices) { + var p = 100 - ((ms - m.startMs) * 100.0 / (m.endMs - m.startMs)); + document.getElementById("progress").style.width = p + '%'; + } +} + +function momentEnd(m, seeked) { + console.log('momentEnd', m, seeked); + if (m.choices) { + addChoices(null); + 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; -} +var lastMs = 0; +var currentSegment; +var lastSegment = null; +var segmentTransition = false; +var lastMoments = []; function ontimeupdate(evt) { var ms = getCurrentMs(); + currentSegment = getSegmentId(ms); - var segmentId = getSegmentId(ms); - - // ontimeupdate resolution is about a second, better use timer - clearTimeout(timerId); - if (segmentId && nextSegment && nextSegment != segmentId) { - var timeLeft = SegmentMap.segments[segmentId].endTimeMs - ms; - timerId = setTimeout(ontimeout, timeLeft, nextSegment); + if (timerId) { + clearTimeout(timerId); + timerId = 0; } - 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