var layer = 'keywords' var imageResolution = 480 function resize() { var video = document.querySelector('video') if (video && video._frame) { var top, rect = video._frame.getBoundingClientRect(); if (video._root) { var root_rect = video._root.getBoundingClientRect(); top = rect.top - root_rect.top } else { top = rect.top + window.scrollY } video.style.top = top + 'px' } } function updatePlayerRect(video, frame, config) { var top, rect = frame.getBoundingClientRect(); if (config.root && config.mode != "single") { var root_rect = config.root.getBoundingClientRect(); //console.log('rect.top', rect.top, 'window.scrollY', window.scrollY, 'root_rect.top', root_rect.top) top = rect.top - root_rect.top video.style.left = '' } else { top = rect.top + window.scrollY video.style.left = rect.x + 'px' } video.style.top = top + 'px' video.style.display = 'block'; video.style.maxWidth = rect.width + 'px' } function updatePlayer(video, frame, currentTime, out, src, config) { video.style.opacity = 0 //console.log('update player', currentTime, out, src) updatePlayerRect(video, frame, config) if (src && config.loaded) { setVideoSrc(video, src) } //video.poster = frame.querySelector('img').src var muted = video.muted // video.muted = true video.currentTime = currentTime video.dataset.in = currentTime video.dataset.out = out video._root = config.root video._frame = frame if (config.mode == "single") { video._text = frame.parentElement.querySelector('.text') } else { video._text = null } const show = event => { video.style.opacity = 1 video.muted = muted video.removeEventListener('seeked', show) } video.addEventListener('seeked', show) video.play() } function timeupdate(event) { if (event.target.dataset.out && event.target.dataset.in) { var in_= parseFloat(event.target.dataset.in) var out_= parseFloat(event.target.dataset.out) var currentTime = event.target.currentTime if (event.target._text) { event.target._text.querySelectorAll('.annotation').forEach(annot => { var ain = parseFloat(annot.dataset.in) var aout = parseFloat(annot.dataset.out) if (ain <= currentTime && aout > currentTime) { annot.style.display = '' annot.classList.add('active') annot.parentElement.classList.add('active') } else { annot.style.display = 'none' annot.classList.remove('active') if (!annot.parentElement.querySelector('.active')) { annot.parentElement.classList.remove('active') } } }) } if (event.target.currentTime >= out_) { /* var next if (event.target._frame) { next = event.target._frame.parentElement.nextSibling if (next) { next = next.querySelector('.frame') } } if (next) { scrollTo(next) } else { event.target.pause() } */ if (config.pause) { event.target.pause() } else { event.target.currentTime = in_ } } } } function formatInfo(config, ascroll) { var info = document.createElement('div') if (config.title) { var h1 = document.createElement('h1') h1.innerHTML = config.title info.appendChild(h1) } if (config.byline) { var h2 = document.createElement('h2') h2.innerHTML = config.byline info.appendChild(h2) } if (config.annotations.length && config.body) { var div = document.createElement('div') div.classList.add('intro') div.innerHTML = config.body info.appendChild(div) } ascroll.appendChild(info) if (!config.annotations.length && config.body) { var div = document.createElement('div') div.innerHTML = config.body var activate var part = document.location.hash.slice(1) || 'part1' div.querySelectorAll("h3.toggle").forEach(title => { var section = title.nextElementSibling if (part == title.id && section.style.display == "none") { activate = title } title.onclick = event => { section.style.display = section.style.display == "none" ? "block" : "none" div.querySelectorAll("h3.toggle").forEach(other => { if (other != title) { var osection = other.nextElementSibling osection.style.display = "none" } }) } }) if (activate) { activate.click() } ascroll.appendChild(div) } return info } function renderAnnotation(config, video, root, clip) { var div = document.createElement('div') div.classList.add('annotation') var annotation = clip.annotations[0] var color1 = `hsl(${clip.color1.hue}, 60%, 15%)` var color2 = `hsl(${clip.color2.hue}, 60%, 15%)` if (!config.first) { config.first = annotation config.info.style.background = color1 } div.style.background = `linear-gradient(to bottom, ${color1}, ${color2})`; var figcaption = '' if (annotation.title) { var title = annotation.title var txt = `${title}` figcaption = `
${txt}
` } var values = [] clip.annotations.forEach(a => { values.push(a.value.replace(/src="\//g, `src="${streamPrefix}/`).replace(/href="\//g, `href="${pandoraURL}/`)) }) values = values.join('

') div.innerHTML = `
${figcaption}
${values}
` root.appendChild(div) var frame = div.querySelector('.frame') document.addEventListener('scroll', onVisibilityChange(div.querySelector('.frame'), function(visible) { var src if (clip.src) { src = clip.src } else if (config.edit) { src = `${streamPrefix}/${annotation.id.split('/')[0]}/480p.webm` } if (config.loaded && visible) { updatePlayer(video, frame, annotation['in'], annotation['out'], src, config) } })) window.addEventListener('resize', () => { var visible = video._frame == frame; if (config.loaded && visible) { updatePlayerRect(video, frame, config) } }, false); } function renderAnnotations(config) { //console.log('renderAnnotations', config.item, config.annotations.length) var ascroll = config.root ? config.root : document.querySelector('#ascroll') config.loaded = false var video = document.createElement('video') video.classList.add('player') video.playsinline = true video.setAttribute('playsinline', 'playsinline') video.setAttribute('webkit-playsinline', 'webkit-playsinline') video.WebKitPlaysInline = true video.muted = true if (config.item) { setVideoSrc(video, `${streamPrefix}/${config.item}/480p.webm`) } video.addEventListener('timeupdate', timeupdate) video.addEventListener('touchstart', showOverlay) video.addEventListener('mouseover', showOverlay) var box = document.createElement('div') box.classList.add('vbox') box.appendChild(video) ascroll.appendChild(box) config.info = formatInfo(config, ascroll) config.annotations.forEach(annotation => { renderAnnotation(config, video, ascroll, annotation) }) config.loaded = true if (config.first) { let frame = ascroll.querySelector('.annotation .frame') if (frame) { var src if (config.edit) { src = `${streamPrefix}/${config.first.id.split('/')[0]}/480p.webm` } updatePlayer(video, frame, config.first['in'], config.first['out'], src, config) } } } async function loadClips(annotations) { var items = annotations.map(annotation => annotation.id.split('/')[0]) items = [...new Set(items)] return pandoraAPI('findClips', {itemsQuery: { conditions: [{key: 'id', operator: '&', value: items}] }, range: [0, 10000], keys: [ 'id', 'hue', 'saturation', 'lightness' ]}).then(response => { var colors = {} var clips = {} response.data.items.forEach(clip => { colors[clip.id] = clip }) var previous annotations.forEach(annotation => { var clipId = annotation.clipId || annotation.id.split('/')[0] + '/' + annotation.in.toFixed(3) + '-'+ annotation.out.toFixed(3) if (annotation.colorId && !colors[clipId]) { colors[clipId] = colors[annotation.colorId] } if (!colors[clipId]) { console.log('no match for clip', clipId) return } clips[clipId] = clips[clipId] || { id: clipId, color1: colors[clipId], annotations: [] } clips[clipId].annotations.push(annotation) annotation.color1 = colors[clipId] if(previous && previous.id != clipId) { previous.color2 = clips[clipId].color1 } if (!previous || previous.id != clipId) { previous = clips[clipId] } }) var clips = Object.values(clips) if (clips.length) { clips[clips.length - 1].color2 = clips[0].color1 } //return annotations return clips }) } async function loadAnnotations(config) { var layers = config.layer || config.layers || [] if (!Array.isArray(layers)) { layers = [layers] } if (config.item && !layers.length) { // load player only view config.annotations = [ { "in": config["in"], "out": config["out"], "id": config["item"] + "/" + config["in"].toFixed(3) + '-'+ config.out.toFixed(3), "annotations": [{ "id": config["item"] + "/" + config["in"].toFixed(3) + '-'+ config.out.toFixed(3), "value": "", "in": config["in"], "out": config["out"], }], "color1": "", "color2": "", } ] return config } else if (config.item) { return pandoraAPI('get', {id: config.item, keys: [ 'layers' ]}).then(response => { var annotations = [] layers.forEach(layer => { if (!response.data.layers[layer]) { console.log("ERROR", config.item, layer, "missing", config.root) } response.data.layers[layer] && response.data.layers[layer].forEach(annotation => { annotation.clipId = annotation.id.split('/')[0] + '/' + annotation.in.toFixed(3) + '-'+ annotation.out.toFixed(3) if (!(config.user && annotation.user != config.user) && isInside(config, annotation)) { if (config.split && config.split.indexOf(layer) > -1) { var parts = annotation.value.replace(//g, '
').replace(//g, '
').split('
') var duration = (annotation.out - annotation['in']) / parts.length if (parts.length > 1 && duration > 2) { var position = annotation['in'] var part while (parts.length) { part = { ...annotation } part['in'] = position part['out'] = position + duration part.clipId = part.id.split('/')[0] + '/' + part.in.toFixed(3) + '-'+ part.out.toFixed(3) part.colorId = annotation.clipId part.value = parts.shift() if (parts.length) { position = part['out'] } else { position = annotation.out } annotations.push(part) } } } else { annotations.push(annotation) } } }) }) return loadClips(annotations).then(annotations => { config.annotations = annotations.filter(annotation => { if (config.only_e) { if (annotation.value.slice(0, 2) == 'E:') { annotation.value = annotation.value.slice(2).trim() return true } else { return false } } else { return annotation } }) return config }) }) } else { var cited = {} if (config.edit) { return pandoraAPI('getEdit', {id: config.edit, keys: []}).then(response => { var annotations = [] response.data.clips.forEach(clip => { cited[clip.item] = { title: clip.title, director: clip.director, id: clip.item } layers.forEach(layer => { clip.layers[layer].forEach(annotation => { if (config.user && annotation.user != config.user) { return } ;['title', 'director', 'date'].forEach(key => { annotation[key] = clip[key] }) if (config.only_e) { if (annotation.value.slice(0, 2) == 'E:') { annotation.value = annotation.value.slice(2).trim() annotations.push(annotation) } } else { //if (annotation.value.slice(0, 2) != 'E:') { annotations.push(annotation) //} } }) }) }) return loadClips(annotations).then(annotations => { config.annotations = annotations config.cited = Object.values(cited) return config }) }) } else { if (!config.annotations) { config.annotations = [] } return config } } } function renderSingleMode(config) { var src config.annotations.forEach(annotation => { src = annotation.src = `${streamPrefix}/${annotation.id.split('/')[0]}/480p.webm` }) var div = document.createElement('div') var annotation = config.annotations[0].annotations[0] var figcaption = '' div.classList.add('single-node') div.classList.add('annotation') div.innerHTML = `
${figcaption}
` config.root.appendChild(div) var frame = div.querySelector('.frame') var text = div.querySelector('.text') var first = config.annotations[0] if (text && first) { var color1 = `hsl(${first.color1.hue}, 60%, 15%)` div.style.background = `${color1}`; } document.addEventListener('scroll', onVisibilityChange(div.querySelector('.frame'), function(visible) { if (config.loaded && visible) { updatePlayer(config.video, frame, config['in'], config['out'], src, config) } })) config.layers.forEach(layer => { var text_layer = document.createElement('div') text_layer.classList.add('layer') text_layer.classList.add(layer) var title = document.createElement('div') title.classList.add('title') title.innerText = layer.charAt(0).toUpperCase() + layer.slice(1); text_layer.appendChild(title) config.annotations.forEach(clip => { var values = [] clip.annotations.forEach(a => { if (a.layer == layer) { values.push(a.value.replace(/src="\//g, `src="${streamPrefix}/`).replace(/href="\//g, `href="${pandoraURL}/`)) } }) if (values.length) { values = values.join('

') var annot = document.createElement('div') annot.classList.add('annotation') annot.innerHTML = values //console.log(clip) annot.dataset['in'] = clip.annotations[0]['in'] annot.dataset['out'] = clip.annotations[0]['out'] annot.style.display = 'none' text_layer.appendChild(annot) } }) if (text_layer.querySelector('.annotation')) { text.appendChild(text_layer) } }) if (frame) { updatePlayer(config.video, frame, config['in'], config['out'], src, config) } } config.layer = config.layer || layer if (false && config.annotations) { renderAnnotations(config) } else { loadAnnotations(config).then(renderAnnotations) } window.addEventListener('resize', resize, false);