406 lines
9.9 KiB
JavaScript
406 lines
9.9 KiB
JavaScript
class Slide {
|
|
constructor(slide, idx) {
|
|
this.isAudioLoaded = this.isVideoLoaded = false
|
|
this.el = slide
|
|
this.idx = idx
|
|
this.duration = slide.dataset.duration
|
|
this.durationMs = parseInt(this.duration) * 1000
|
|
const video = slide.querySelector('.video')
|
|
if (video) {
|
|
this.video = load_urls(video.dataset)
|
|
if (this.video.length > 1 || this.video[0].includes('document')) {
|
|
this.isDocument = true
|
|
}
|
|
this.videoVolume = video.dataset.volume ? parseFloat(video.dataset.volume) : 1
|
|
this.videoContainer = video
|
|
if (this.video.length > 1) {
|
|
this.zooms = this.video.map(url => url.split('/').pop().split('#')[0].split(',').map(a => Math.round(a)))
|
|
}
|
|
}
|
|
const audio = slide.querySelector('.audio')
|
|
if (audio) {
|
|
this.audio = audio.dataset.url // audio does not need to be an array
|
|
this.audioVolume = audio.dataset.volume ? parseFloat(audio.dataset.volume) : 1
|
|
this.audioContainer = audio
|
|
this.audioContinue = !!audio.dataset.continue
|
|
}
|
|
this.initEmbeds()
|
|
return this
|
|
}
|
|
|
|
initEmbeds() {
|
|
if (this.video) {
|
|
const videoEmbed = this.videoEmbed = new PandoraEmbed({
|
|
id: 'slide-' + this.idx,
|
|
url: this.video[0],
|
|
container: this.videoContainer
|
|
})
|
|
|
|
videoEmbed.on('init', () => {
|
|
if (this.isDocument) {
|
|
this.isVideoLoaded = true
|
|
}
|
|
})
|
|
|
|
videoEmbed.on('loaded', () => {
|
|
console.log('loaded called on embed')
|
|
this.isVideoLoaded = true
|
|
})
|
|
|
|
videoEmbed.on('playing', (positionData) => {
|
|
if (!this.videoStart) {
|
|
this.videoStart = positionData.position
|
|
}
|
|
})
|
|
}
|
|
|
|
if (this.audio) {
|
|
const audioEmbed = this.audioEmbed = new PandoraEmbed({
|
|
id: 'audio-' + this.idx,
|
|
url: this.audio,
|
|
container: this.audioContainer
|
|
})
|
|
|
|
audioEmbed.on('loaded', () => {
|
|
console.log('loaded called on audio embed')
|
|
this.isAudioLoaded = true
|
|
})
|
|
|
|
audioEmbed.on('playing', (positionData) => {
|
|
if (!this.audioStart) {
|
|
this.audioStart = positionData.position
|
|
}
|
|
})
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
isReady() {
|
|
console.log('isReady called');
|
|
if (this.video && this.audio) {
|
|
return this.isVideoLoaded && this.isAudioLoaded
|
|
} else if (this.video) {
|
|
return this.isVideoLoaded
|
|
} else if (this.audio) {
|
|
return this.isAudioLoaded
|
|
} else {
|
|
return true
|
|
}
|
|
}
|
|
|
|
sendToBack() {
|
|
this.el.style.zIndex = 0
|
|
return this
|
|
}
|
|
|
|
bringToFront() {
|
|
this.el.style.zIndex = 10
|
|
return this
|
|
}
|
|
|
|
start() {
|
|
console.log('called start', this.isReady())
|
|
this.timeout = setTimeout(next, this.durationMs)
|
|
this.startTime = new Date()
|
|
if (this.zooms) {
|
|
this.startZoom()
|
|
} else if (this.video && this.video.length) {
|
|
this.startVideo()
|
|
}
|
|
if (this.audioContainer) { // if there is an audio container, we always stop current audio
|
|
reset_active_audio()
|
|
}
|
|
if (this.audio) {
|
|
this.startAudio()
|
|
}
|
|
return this
|
|
}
|
|
|
|
stop() {
|
|
if (this.zooms) {
|
|
this.resetZoom()
|
|
} else if (this.video && this.video.length) {
|
|
this.resetVideo()
|
|
}
|
|
if (this.audio && !this.audioContinue) {
|
|
this.resetAudio()
|
|
}
|
|
return this
|
|
}
|
|
|
|
pause() {
|
|
clearTimeout(this.timeout)
|
|
this.pauseTime = new Date()
|
|
if (this.zooms) {
|
|
this.pauseZoom()
|
|
} else if (this.video && this.video.length) {
|
|
this.pauseVideo()
|
|
}
|
|
if (this.audio) {
|
|
this.pauseAudio()
|
|
}
|
|
}
|
|
|
|
resume() {
|
|
|
|
const offset = this.pauseTime - this.startTime
|
|
const timeout = this.durationMs - offset
|
|
|
|
this.timeout = setTimeout(next, timeout)
|
|
|
|
// "Fake" the start time in case the user pauses and resumes again.
|
|
// NOTE: This seems like a bit of a hack, but the other way seemed convoluted
|
|
this.startTime = new Date() - offset
|
|
|
|
if (this.zooms) {
|
|
this.resumeZoom()
|
|
} else if (this.video && this.video.length) {
|
|
this.resumeVideo()
|
|
}
|
|
if (this.audio) {
|
|
this.resumeAudio()
|
|
}
|
|
}
|
|
|
|
startZoom(offset) {
|
|
offset = offset || 0
|
|
for (var i=1; i<this.zooms.length; i++) {
|
|
this.zoomTimeouts = [];
|
|
((j) => {
|
|
const timeout = Math.round(1000 * (this.duration / this.zooms.length) * j) - offset
|
|
if (timeout < 0) return
|
|
this.zoomTimeouts.push(setTimeout(() => {
|
|
this.videoEmbed.postMessage('options', {
|
|
'area': this.zooms[j]
|
|
})
|
|
}, timeout))
|
|
})(i);
|
|
}
|
|
return this
|
|
}
|
|
|
|
startVideo() {
|
|
this.videoEmbed.postMessage('options', {
|
|
'paused': false,
|
|
'volume': this.videoVolume
|
|
})
|
|
return this
|
|
}
|
|
|
|
startAudio() {
|
|
this.audioEmbed.postMessage('options', {
|
|
'paused': false,
|
|
'volume': this.audioVolume
|
|
})
|
|
activeAudio = this
|
|
return this
|
|
}
|
|
|
|
pauseZoom() {
|
|
this.zoomTimeouts.forEach(clearTimeout)
|
|
return this
|
|
}
|
|
|
|
pauseVideo() {
|
|
this.videoEmbed.postMessage('options', {
|
|
'paused': true
|
|
})
|
|
return this
|
|
}
|
|
|
|
pauseAudio() {
|
|
this.audioEmbed.postMessage('options', {
|
|
'paused': true
|
|
})
|
|
return this
|
|
}
|
|
|
|
resumeZoom() {
|
|
const offset = this.pauseTime - this.startTime
|
|
this.startZoom(offset)
|
|
return this
|
|
}
|
|
|
|
resumeVideo() {
|
|
this.videoEmbed.postMessage('options', {
|
|
'paused': false
|
|
})
|
|
return this
|
|
}
|
|
|
|
resumeAudio() {
|
|
this.audioEmbed.postMessage('options', {
|
|
'paused': false
|
|
})
|
|
return this
|
|
}
|
|
|
|
resetZoom() {
|
|
this.zoomTimeouts.forEach(clearTimeout)
|
|
this.videoEmbed.postMessage('options', {
|
|
'area': this.zooms[0]
|
|
})
|
|
return this
|
|
}
|
|
|
|
resetVideo() {
|
|
this.videoEmbed.postMessage('options', {
|
|
'paused': true,
|
|
'position': this.start || 0
|
|
})
|
|
return this
|
|
}
|
|
|
|
resetAudio() {
|
|
this.audioEmbed.postMessage('options', {
|
|
'paused': true,
|
|
'position': 0 //FIXME: figure correct audio reset
|
|
})
|
|
activeAudio = null;
|
|
return this
|
|
}
|
|
}
|
|
|
|
var slides = [],
|
|
current = 0,
|
|
activeAudio,
|
|
timeout;
|
|
|
|
let globalIsPaused = false
|
|
|
|
let textbUrl = new URLSearchParams(window.location.search).get('textb') || 'https://textb.org/r/housingplaylist2/'
|
|
|
|
// use raw version of page
|
|
textbUrl = textbUrl.replace('/t/', '/r/')
|
|
if (!textbUrl.endsWith('/')) textbUrl += '/'
|
|
|
|
fetch(textbUrl)
|
|
.then(response => response.text())
|
|
.then(loadYaml)
|
|
.then(init)
|
|
.catch(err => {
|
|
console.log('error', err)
|
|
alert('error loading YAML')
|
|
})
|
|
|
|
|
|
function loadYaml(txt) {
|
|
const html = txt.split('\n\n')
|
|
.filter(chunk => chunk.trim())
|
|
.map(chunk => {
|
|
const obj = jsyaml.load(chunk)
|
|
return obj
|
|
})
|
|
.map(jsonToHTML)
|
|
.join('')
|
|
document.body.innerHTML = `
|
|
<div class="base"></div>
|
|
<div id="overlay"></div>
|
|
${html}
|
|
`
|
|
return true
|
|
}
|
|
|
|
|
|
function globalPause() {
|
|
if (globalIsPaused) return;
|
|
slides[current].pause()
|
|
globalIsPaused = true
|
|
}
|
|
|
|
function globalResume() {
|
|
if (!globalIsPaused) return;
|
|
slides[current].resume()
|
|
globalIsPaused = false
|
|
}
|
|
|
|
function togglePause() {
|
|
if (globalIsPaused) {
|
|
globalResume()
|
|
} else {
|
|
globalPause()
|
|
}
|
|
}
|
|
|
|
function init() {
|
|
document.querySelectorAll('.slide').forEach(function(slide, idx) {
|
|
slides.push(new Slide(slide, idx))
|
|
})
|
|
go(0)
|
|
}
|
|
|
|
function go(idx) {
|
|
if (!slides[idx].isReady()) {
|
|
console.log('slide not ready');
|
|
return setTimeout(() => {
|
|
go(idx)
|
|
}, 250)
|
|
}
|
|
var old = current
|
|
|
|
slides[current].sendToBack()
|
|
slides[idx].bringToFront()
|
|
current = idx
|
|
if (timeout) {
|
|
clearTimeout(timeout)
|
|
timeout = null
|
|
}
|
|
// timeout = setTimeout(next, Math.round(slides[current].duration) * 1000)
|
|
|
|
slides[old].stop()
|
|
slides[current].start()
|
|
}
|
|
|
|
function next() {
|
|
var idx = (current + 1) % slides.length
|
|
console.log(current, idx)
|
|
go(idx)
|
|
}
|
|
|
|
function previous() {
|
|
var idx = (current - 1) % slides.length
|
|
if (idx < 0) {
|
|
idx += slides.length
|
|
}
|
|
go(idx)
|
|
}
|
|
|
|
|
|
function load_urls(dataset) {
|
|
var urls = [], idx = 0
|
|
while (dataset['url_' + idx]) {
|
|
urls.push(dataset['url_' + idx])
|
|
idx += 1
|
|
}
|
|
return urls
|
|
}
|
|
|
|
|
|
window.addEventListener('blur', function(){
|
|
setTimeout(function(){
|
|
// using the 'setTimout' to let the event pass the run loop
|
|
if (document.activeElement instanceof HTMLIFrameElement) {
|
|
window.focus();
|
|
}
|
|
},0);
|
|
}, false);
|
|
|
|
|
|
function reset_active_audio() {
|
|
if (activeAudio) {
|
|
activeAudio.resetAudio()
|
|
}
|
|
}
|
|
|
|
document.addEventListener('keydown', function(event) {
|
|
if (event.key == 'ArrowRight') {
|
|
next()
|
|
event.preventDefault()
|
|
} else if (event.key == 'ArrowLeft') {
|
|
previous()
|
|
event.preventDefault()
|
|
} else if (event.keyCode === 32) { // spacebar
|
|
togglePause()
|
|
}
|
|
}, false) |