// 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; // Persistent state var ls = window.localStorage || {}; function msToString(ms) { return new Date(ms).toUTCString().split(' ')[4]; } function getCurrentMs() { return Math.round(document.getElementById("video").currentTime * 1000.0); } function preconditionToJS(cond) { if (cond[0] == 'persistentState') { return 'JSON.parse(ls["persistentState_' + cond[1] + '"])'; } else if (cond[0] == 'not') { return '!(' + preconditionToJS(cond[1]) + ')'; } else if (cond[0] == 'and') { return '(' + cond.slice(1).map(preconditionToJS).join(' && ') + ')'; } else if (cond[0] == 'or') { 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 evalPrecondition(precondition, text) { if (precondition) { let cond = preconditionToJS(precondition); let match = eval(cond); console.log('precondition', text, ':', cond, '==', match); return match; } return true; } function checkPrecondition(preconditionId) { return evalPrecondition(bv.preconditions[preconditionId], preconditionId); } 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); } } 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)) { if (ms >= v.startTimeMs && ms < v.endTimeMs) { return k; } } return null; } function getSegmentMs(segmentId) { return segmentMap.segments[segmentId].startTimeMs; } 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 result; } function newList(id) { var ul = document.getElementById(id); while (ul.firstChild) { ul.removeChild(ul.firstChild); } return ul; } function addItem(ul, text, url) { var li = document.createElement("li"); var a = document.createElement("a"); a.textContent = text; a.setAttribute('href', url); li.appendChild(a); 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, 'javascript:playSegment("' + segmentId + '")'); } function addZones(segmentId) { var ul = newList("interactionZones"); 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 (var z of v.ui.interactionZones) { var startMs = z[0]; var stopMs = z[1]; let caption = segmentId + ' interactionZone ' + index; addItem(ul, caption, 'javascript:seek(' + startMs + ')'); index++; } } ul = newList("nextSegments"); let defaultSegmentId = null; for (const [k, v] of Object.entries(segmentMap.segments[segmentId].next)) { let caption = k; if (segmentMap.segments[segmentId].defaultNext == k) { caption = '[' + caption + ']'; 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; 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(' + index + ')'); index++; } if (r.id in choicePoints) document.getElementById("choiceCaption").innerHTML = choicePoints[r.id].description; } function momentStart(m, seeked) { console.log('momentStart', m, seeked); if (m.choices) { addChoices(m); } if (!seeked) applyImpression(m.impressionData); } 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 lastMs = 0; var currentSegment; var lastSegment = null; var segmentTransition = false; var lastMoments = []; function ontimeupdate(evt) { var ms = getCurrentMs(); currentSegment = getSegmentId(ms); if (timerId) { clearTimeout(timerId); timerId = 0; } // Distinguish between the user seeking manually with