player
This commit is contained in:
parent
09ff5728cb
commit
14ae8ef1d5
3 changed files with 96 additions and 160 deletions
40
app/static/css/partials/_player.scss
Normal file
40
app/static/css/partials/_player.scss
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
#player {
|
||||||
|
font-family: "noto_sans";
|
||||||
|
width: 100%;
|
||||||
|
color: #ddd;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
padding-left: var(--spacing-2);
|
||||||
|
padding-right: var(--spacing-2);
|
||||||
|
padding-top: 32px;
|
||||||
|
font-size: 20px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-weight: bold;
|
||||||
|
max-width: var(--container-width);
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
padding: 4px var(--spacing-2);
|
||||||
|
max-width: var(--container-width);
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
.info {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
.vbox {
|
||||||
|
max-width: var(--container-width);
|
||||||
|
margin: auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.player {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
max-width: var(--container-width);
|
||||||
|
max-height: 80vh;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
.annotation {
|
||||||
|
display: none;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,5 +7,6 @@
|
||||||
@import "partials/film";
|
@import "partials/film";
|
||||||
@import "partials/text";
|
@import "partials/text";
|
||||||
@import "partials/ascroll";
|
@import "partials/ascroll";
|
||||||
|
@import "partials/player";
|
||||||
@import "partials/about";
|
@import "partials/about";
|
||||||
@import "partials/contact";
|
@import "partials/contact";
|
||||||
|
|
|
@ -119,6 +119,21 @@ function scrollTo(element) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function timeupdate(event) {
|
function timeupdate(event) {
|
||||||
|
var video = event.target
|
||||||
|
var now = video.currentTime
|
||||||
|
document.querySelectorAll('#player .annotation').forEach(annotation => {
|
||||||
|
var display
|
||||||
|
if (
|
||||||
|
parseFloat(annotation.dataset['in']) < now && parseFloat(annotation.dataset['out']) > now
|
||||||
|
) {
|
||||||
|
display = 'block'
|
||||||
|
} else {
|
||||||
|
display = 'none'
|
||||||
|
}
|
||||||
|
if (annotation.style.display != display) {
|
||||||
|
annotation.style.display = display
|
||||||
|
}
|
||||||
|
})
|
||||||
if (event.target.dataset.out && event.target.dataset.in) {
|
if (event.target.dataset.out && event.target.dataset.in) {
|
||||||
var in_= parseFloat(event.target.dataset.in)
|
var in_= parseFloat(event.target.dataset.in)
|
||||||
var out_= parseFloat(event.target.dataset.out)
|
var out_= parseFloat(event.target.dataset.out)
|
||||||
|
@ -146,120 +161,41 @@ function timeupdate(event) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatInfo(config, ascroll) {
|
function formatInfo(config, player) {
|
||||||
var info = document.createElement('div')
|
var info = document.createElement('div')
|
||||||
|
info.classList.add('info')
|
||||||
|
var folder = document.createElement('a')
|
||||||
|
folder.href = '..'
|
||||||
|
folder.innerText = config.folder
|
||||||
var h1 = document.createElement('h1')
|
var h1 = document.createElement('h1')
|
||||||
h1.innerHTML = config.title
|
h1.appendChild(folder)
|
||||||
|
var title = document.createElement('span')
|
||||||
|
title.innerHTML = ': ' + config.title
|
||||||
|
h1.appendChild(title)
|
||||||
info.appendChild(h1)
|
info.appendChild(h1)
|
||||||
var h2 = document.createElement('h2')
|
|
||||||
h2.innerHTML = config.byline
|
player.appendChild(info)
|
||||||
info.appendChild(h2)
|
|
||||||
var div = document.createElement('div')
|
|
||||||
div.classList.add('intro')
|
|
||||||
div.innerHTML = config.body
|
|
||||||
info.appendChild(div)
|
|
||||||
ascroll.appendChild(info)
|
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
function showOverlay(event) {
|
|
||||||
event.stopPropagation()
|
|
||||||
event.preventDefault()
|
|
||||||
document.querySelectorAll('#video-overlay').forEach(element => element.remove())
|
|
||||||
var video = event.target
|
|
||||||
var rect = video.getBoundingClientRect();
|
|
||||||
var overlay = document.createElement('div')
|
|
||||||
overlay.id = 'video-overlay'
|
|
||||||
overlay.style.top = video.style.top
|
|
||||||
overlay.style.width = rect.width + 'px'
|
|
||||||
overlay.style.height = rect.height + 'px'
|
|
||||||
overlay.style.position = 'absolute'
|
|
||||||
overlay.style.display = 'flex'
|
|
||||||
overlay.style.alignItems = 'center'
|
|
||||||
overlay.style.justifyContent = 'center'
|
|
||||||
//overlay.style.fontSize = '45px'
|
|
||||||
video.controls = false
|
|
||||||
|
|
||||||
var off = `<span class="annotation-icon-wrapper animated"><span class="text f-icon-volume_off annotation-icon"></span>`
|
function renderAnnotation(config, video, player, annotation) {
|
||||||
var on = `<span class="annotation-icon-wrapper"><span class="f-icon-volume_down annotation-icon"></span></span>`
|
|
||||||
|
|
||||||
if (video.muted) {
|
|
||||||
overlay.innerHTML = off
|
|
||||||
} else {
|
|
||||||
overlay.innerHTML = on
|
|
||||||
}
|
|
||||||
overlay.addEventListener('click', event=> {
|
|
||||||
video.muted = !video.muted
|
|
||||||
if (video.muted) {
|
|
||||||
overlay.innerHTML = off
|
|
||||||
} else {
|
|
||||||
overlay.innerHTML = on
|
|
||||||
}
|
|
||||||
})
|
|
||||||
var timeout = setTimeout(() => {
|
|
||||||
video.controls = false
|
|
||||||
overlay.remove()
|
|
||||||
}, 3000)
|
|
||||||
overlay.addEventListener('mousemove', event=> {
|
|
||||||
clearTimeout(timeout)
|
|
||||||
timeout = setTimeout(() => {
|
|
||||||
video.controls = false
|
|
||||||
overlay.remove()
|
|
||||||
}, 500)
|
|
||||||
})
|
|
||||||
video.parentElement.appendChild(overlay)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderAnnotation(config, video, ascroll, annotation) {
|
|
||||||
var div = document.createElement('div')
|
var div = document.createElement('div')
|
||||||
div.classList.add('annotation')
|
div.classList.add('annotation')
|
||||||
|
|
||||||
var color1 = `hsl(${annotation.color1.hue}, 70%, 75%)`
|
|
||||||
var color2 = `hsl(${annotation.color2.hue}, 70%, 75%)`
|
|
||||||
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) {
|
|
||||||
if (config.language == 'zh') {
|
|
||||||
var title = annotation.title.split('/')[1] || annotation.title
|
|
||||||
var director = annotation.director[1]
|
|
||||||
} else {
|
|
||||||
var title = annotation.title.split('/')[0]
|
|
||||||
var director = annotation.director[0]
|
|
||||||
}
|
|
||||||
var txt = `${title} (${director})`
|
|
||||||
figcaption = `<figcaption><a href="${pandoraURL}/${annotation.id}" target="_blank">${txt}</a></figcaption>`
|
|
||||||
}
|
|
||||||
annotation.value = annotation.value.replace(/src="\//g, `src="${pandoraURL}/`).replace(/href="\//g, `href="${pandoraURL}/`)
|
annotation.value = annotation.value.replace(/src="\//g, `src="${pandoraURL}/`).replace(/href="\//g, `href="${pandoraURL}/`)
|
||||||
|
div.dataset['in'] = annotation['in']
|
||||||
|
div.dataset['out'] = annotation['out']
|
||||||
div.innerHTML = `
|
div.innerHTML = `
|
||||||
<div class="frame">
|
|
||||||
<figure>
|
|
||||||
<img src="${pandoraURL}/${annotation.id.split('/')[0]}/${imageResolution}p${annotation.in}.jpg">
|
|
||||||
${figcaption}
|
|
||||||
</figure>
|
|
||||||
</div>
|
|
||||||
<div class="text">${annotation.value}</div>
|
<div class="text">${annotation.value}</div>
|
||||||
`
|
`
|
||||||
ascroll.appendChild(div)
|
player.appendChild(div)
|
||||||
var frame = div.querySelector('.frame')
|
|
||||||
document.addEventListener('scroll', onVisibilityChange(div.querySelector('.frame'), function(visible) {
|
|
||||||
var src
|
|
||||||
if (config.edit) {
|
|
||||||
src = `${pandoraURL}/${annotation.id.split('/')[0]}/480p.webm`
|
|
||||||
}
|
|
||||||
if (config.loaded && visible) {
|
|
||||||
updatePlayer(video, frame, annotation['in'], annotation['out'], src)
|
|
||||||
}
|
|
||||||
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderAnnotations(config) {
|
function renderPlayer(config) {
|
||||||
var ascroll = document.querySelector('#ascroll')
|
var player = document.querySelector('#ascroll')
|
||||||
|
player.id = 'player'
|
||||||
|
|
||||||
config.loaded = false
|
config.loaded = false
|
||||||
var video = document.createElement('video')
|
var video = document.createElement('video')
|
||||||
video.classList.add('player')
|
video.classList.add('player')
|
||||||
|
@ -267,73 +203,24 @@ function renderAnnotations(config) {
|
||||||
video.setAttribute('playsinline', 'playsinline')
|
video.setAttribute('playsinline', 'playsinline')
|
||||||
video.setAttribute('webkit-playsinline', 'webkit-playsinline')
|
video.setAttribute('webkit-playsinline', 'webkit-playsinline')
|
||||||
video.WebKitPlaysInline = true
|
video.WebKitPlaysInline = true
|
||||||
video.muted = true
|
video.muted = false
|
||||||
|
video.controls = true
|
||||||
if (config.item) {
|
if (config.item) {
|
||||||
setVideoSrc(video, `${pandoraURL}/${config.item}/480p.webm`)
|
setVideoSrc(video, `${pandoraURL}/${config.item}/480p.webm`)
|
||||||
|
video.poster = `${pandoraURL}/${config.item}/480p.jpg`
|
||||||
}
|
}
|
||||||
video.addEventListener('timeupdate', timeupdate)
|
video.addEventListener('timeupdate', timeupdate)
|
||||||
video.addEventListener('touchstart', showOverlay)
|
|
||||||
video.addEventListener('mouseover', showOverlay)
|
|
||||||
var box = document.createElement('div')
|
var box = document.createElement('div')
|
||||||
box.classList.add('vbox')
|
box.classList.add('vbox')
|
||||||
box.appendChild(video)
|
box.appendChild(video)
|
||||||
ascroll.appendChild(box)
|
config.info = formatInfo(config, player)
|
||||||
|
player.appendChild(box)
|
||||||
|
|
||||||
|
|
||||||
config.info = formatInfo(config, ascroll)
|
|
||||||
config.annotations.forEach(annotation => {
|
config.annotations.forEach(annotation => {
|
||||||
renderAnnotation(config, video, ascroll, annotation)
|
renderAnnotation(config, video, player, annotation)
|
||||||
})
|
})
|
||||||
config.loaded = true
|
config.loaded = true
|
||||||
if (config.first) {
|
|
||||||
let frame = ascroll.querySelector('.annotation .frame')
|
|
||||||
if (frame) {
|
|
||||||
var src
|
|
||||||
if (config.edit) {
|
|
||||||
src = `${pandoraURL}/${config.first.id.split('/')[0]}/480p.webm`
|
|
||||||
}
|
|
||||||
updatePlayer(video, frame, config.first['in'], config.first['out'], src)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (config.item_url || config.cited) {
|
|
||||||
var box = document.createElement('div')
|
|
||||||
var color = config.annotations[config.annotations.length - 1].color2
|
|
||||||
box.style.background = `hsl(${color.hue}, 70%, 75%)`
|
|
||||||
var div = document.createElement('div')
|
|
||||||
div.classList.add('related-film')
|
|
||||||
if (config.item_url) {
|
|
||||||
if (config.language == 'zh') {
|
|
||||||
div.innerHTML = `
|
|
||||||
引用影片: <a href="${config.item_url}">${config.item_title_zh} (${config.item_director[1]})</a>
|
|
||||||
`
|
|
||||||
} else {
|
|
||||||
div.innerHTML = `
|
|
||||||
Film cited: <a href="${config.item_url}">${config.item_title} (${config.item_director[0]})</a><br>
|
|
||||||
`
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var html = []
|
|
||||||
config.cited.forEach(film => {
|
|
||||||
var title_en = film.title.split(' / ')[0]
|
|
||||||
var title_zh = film.title.split(' / ')[1] || title_en
|
|
||||||
if (config.language == 'en') {
|
|
||||||
var director = film.director ? ` (${film.director[0]})` : ''
|
|
||||||
html.push(`<a href="/video/${film.id}/">${title_en}${director}</a>`)
|
|
||||||
} else {
|
|
||||||
var director = film.director ? ` (${film.director[1]})` : ''
|
|
||||||
html.push(`<a href="/video/${film.id}/">${title_zh}${director}</a>`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
var films = html.length == 1 ? 'Film' : 'Films'
|
|
||||||
html= html.join(', ')
|
|
||||||
if (config.language == 'zh') {
|
|
||||||
div.innerHTML = ` 引用影片: ${html}`
|
|
||||||
} else {
|
|
||||||
div.innerHTML = `${films} cited: ${html}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
box.appendChild(div)
|
|
||||||
ascroll.appendChild(box)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadClips(annotations) {
|
async function loadClips(annotations) {
|
||||||
|
@ -367,10 +254,18 @@ async function loadClips(annotations) {
|
||||||
function loadAnnotations(config) {
|
function loadAnnotations(config) {
|
||||||
if (config.item) {
|
if (config.item) {
|
||||||
pandoraAPI('get', {id: config.item, keys: [
|
pandoraAPI('get', {id: config.item, keys: [
|
||||||
'layers'
|
'title', 'date', 'country', 'featuring', 'layers', 'folder'
|
||||||
]}).then(response => {
|
]}).then(response => {
|
||||||
var annotations = response.data.layers[config.layer].filter(annotation => {
|
['title', 'date', 'country', 'featuring', 'folder'].forEach(key => {
|
||||||
return !(config.user && annotation.user != config.user)
|
config[key] = response.data[key]
|
||||||
|
})
|
||||||
|
var annotations = []
|
||||||
|
Object.keys(response.data.layers).forEach(layer => {
|
||||||
|
response.data.layers[layer].forEach(annotation => {
|
||||||
|
if (!(config.user && annotation.user != config.user)) {
|
||||||
|
annotations.push(annotation)
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
loadClips(annotations).then(annotations => {
|
loadClips(annotations).then(annotations => {
|
||||||
config.annotations = annotations.filter(annotation => {
|
config.annotations = annotations.filter(annotation => {
|
||||||
|
@ -385,7 +280,7 @@ function loadAnnotations(config) {
|
||||||
return annotation.value.slice(0, 2) != 'E:'
|
return annotation.value.slice(0, 2) != 'E:'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
renderAnnotations(config)
|
renderPlayer(config)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@ -420,7 +315,7 @@ function loadAnnotations(config) {
|
||||||
loadClips(annotations).then(annotations => {
|
loadClips(annotations).then(annotations => {
|
||||||
config.annotations = annotations
|
config.annotations = annotations
|
||||||
config.cited = Object.values(cited)
|
config.cited = Object.values(cited)
|
||||||
renderAnnotations(config)
|
renderPlayer(config)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -429,7 +324,7 @@ function loadAnnotations(config) {
|
||||||
config.layer = config.layer || layer
|
config.layer = config.layer || layer
|
||||||
|
|
||||||
if (config.annotations) {
|
if (config.annotations) {
|
||||||
renderAnnotations(config)
|
renderPlayer(config)
|
||||||
} else {
|
} else {
|
||||||
loadAnnotations(config)
|
loadAnnotations(config)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue