Compare commits

..

25 Commits

Author SHA1 Message Date
sanj
32a9720e6e Merge branch 'fetch-textb' of sanj/padmaEmbeds into master 2018-04-02 07:32:50 +00:00
8794207e1d allow loading of yaml with url param textb=<textb_url> 2018-03-27 17:56:21 +05:30
0a4825e14b update slides from yaml 2018-03-18 16:47:21 +05:30
27e8d44db0 fix isReady() for documents 2018-03-17 18:21:39 +05:30
961678fbca Merge branch 'master' of code.with.camp:sanj/padmaEmbeds 2018-03-17 15:47:31 +05:30
78c13a285b wait for slide to load before starting, fixes #3 2018-03-17 15:47:13 +05:30
67cce94580 add border 0 to audio iframe 2018-03-17 15:20:24 +05:30
j
3284c5ca81 use border: 0 for iframe 2018-03-17 09:50:16 +00:00
j
2cc261b604 Merge branch 'refactor-class' of sanj/padmaEmbeds into master 2018-03-17 09:45:19 +00:00
be68a2429b remove unused slideData variable 2018-03-17 15:14:24 +05:30
16d7ffa19a refactor to a Slide class 2018-03-17 14:42:14 +05:30
sanj
ba2e8e4d75 Merge branch 'volume-control' of sanj/padmaEmbeds into master 2018-03-16 14:32:35 +00:00
18cdcee3c1 allow setting of volume via data-volume 2018-03-16 20:00:57 +05:30
j
79c00627a3 Merge branch 'add-audio' of sanj/padmaEmbeds into master 2018-03-16 14:10:54 +00:00
65378ee564 allows continuing and stopping of audio, with slightly strange semantics 2018-03-16 19:20:00 +05:30
8d4f7be7a5 basic audio working without continue 2018-03-16 18:25:14 +05:30
j
034832f71e Merge branch 'many-documents' of sanj/padmaEmbeds into master 2018-03-15 10:24:54 +00:00
3fd440a849 cleanup timeouts for zooms 2018-03-14 23:21:14 +05:30
9ee095b6db work for an arbitrary number of document zooms 2018-03-14 22:56:26 +05:30
9cb4300004 merge commit 2018-03-09 22:12:10 +05:30
bc374ef513 video auutoplay, reset video 2018-03-09 22:11:27 +05:30
j
440a78b691 enable timeout 2018-03-09 21:47:37 +05:30
j
54896ff1c3 some form on presentation demo 2018-03-09 21:46:02 +05:30
d7864c6d36 minor cleanups 2018-03-06 19:40:03 +05:30
sanj
c43759e8b2 Merge branch 'add-lib' of sanj/padmaEmbeds into master 2018-03-06 12:38:59 +00:00
7 changed files with 468 additions and 5 deletions

61
example/example.css Normal file
View File

@ -0,0 +1,61 @@
body {
background-color: black;
color: white;
font-family: sans-serif;
font-size: 18px;
height: 100%;
margin: 0;
overflow: hidden;
width: 100%;
}
.base {
z-index: 1;
background: black;
position: absolute;
width: 100%;
height: 100%;
}
.slide {
z-index: 0;
position: absolute;
width: 100%;
height: 100%;
text-shadow:
-2px -2px 0 #000,
2px -2px 0 #000,
-2px 2px 0 #000,
2px 2px 0 #000;
}
.title {
font-size: 48px;
margin-left: 16px;
margin-right: 16px;
margin-top: 30%;
text-align: center;
position: fixed;
z-index: 10;
height: 56px;
width: 100%;
}
.video {
position: fixed;
width: 100%;
height: 100%;
}
.video iframe {
width: 100%;
height: 100%;
border: 0;
margin: 0;
padding: 0;
}
.audio iframe {
height: 0;
border: 0;
}

302
example/example.js Normal file
View File

@ -0,0 +1,302 @@
class Slide {
constructor(slide, idx) {
this.isAudioLoaded = this.isVideoLoaded = false
this.el = slide
this.idx = idx
this.duration = slide.dataset.duration
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())
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
}
startZoom() {
for (var i=1; i<this.zooms.length; i++) {
this.zoomTimeouts = [];
((j) => {
this.zoomTimeouts.push(setTimeout(() => {
this.videoEmbed.postMessage('options', {
'area': this.zooms[j]
})
}, Math.round(1000 * (this.duration / this.zooms.length) * j)))
})(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
}
resetZoom() {
this.zoomTimeouts.forEach(timeout => clearTimeout(timeout))
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 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>
${html}
`
return true
}
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()
}
}, false)

9
example/index.html Normal file
View File

@ -0,0 +1,9 @@
<meta charset="utf-8">
<script src="../lib/PandoraEmbed.js"></script>
<link type="text/css" href="example.css" rel="stylesheet" />
<!-- YAML parser from: https://github.com/nodeca/js-yaml-->
<script src="../lib/js-yaml.min.js"></script>
<script src="../lib/json-to-html.js"></script>
<script src="example.js"></script>

View File

@ -4,7 +4,7 @@
<title>Subscribe to events from pad.ma iframe embed</title>
</head>
<body>
<iframe width="640" height="360" src="http://10.0.3.230/A/editor/C#embed" frameborder="0" allowfullscreen id="padembed"></iframe>
<iframe width="640" height="360" src="https://pad.ma/BVF/editor/F#embed" frameborder="0" allowfullscreen id="padembed"></iframe>
<button id="setUrl">
Set Iframe URL
</button>

View File

@ -16,7 +16,7 @@ var PandoraEmbed = function(options) {
this.boundFns = {};
var width = options.width || 640;
var height = options.height || 360;
var container = options.container || document;
var container = options.container || document.body;
if (!options.url) {
alert('no embed url specified');
return;
@ -40,7 +40,7 @@ var PandoraEmbed = function(options) {
init();
});
window.addEventListener('message', that.listener.bind(this), false);
window.addEventListener('message', this.listener.bind(this), false);
container.appendChild($iframe);
return this;
};

1
lib/js-yaml.min.js vendored Normal file

File diff suppressed because one or more lines are too long

90
lib/json-to-html.js Normal file
View File

@ -0,0 +1,90 @@
(function() {
window.jsonToHTML = getHTML
function getHTML(slideData) {
let video = audio = ''
if (!slideData.title) slideData.title = ''
title = `
<div class="title">
${slideData.title}
</div>
`
if (slideData.video) {
video = getVideoHtml(slideData.video)
}
if (slideData.audio) {
audio = getAudioHtml(slideData.audio)
}
const duration = `data-duration="${slideData.duration ? slideData.duration : 5}"`
return `
<div class="slide" ${duration}>
${title}
${video}
${audio}
</div>
`
}
function getVideoHtml(videoData) {
let urls = []
let volume = ''
if (typeof(videoData) === 'string') {
urls.push(videoData)
} else { // video is an object
if (videoData.hasOwnProperty('url')) {
urls.push(videoData.url)
} else if (videoData.hasOwnProperty('zoom')) {
urls = videoData.zoom
}
if (videoData.volume) {
volume = `data-volume="${videoData.volume}"`
}
}
const urlsHtml = urls.map((url, index) => {
return `data-url_${index}="${makeEmbed(url)}"`
}).join('\n')
return `
<div class="video" ${urlsHtml} ${volume}> </div>
`
}
function getAudioHtml(audioData) {
let url = continueStr = volume = ''
// short circuit to return an empty div if audioData is null or not an object
// this is useful for user to specify empty audio track to cause existing
// audio to pause
if (!audioData || !(typeof(audioData === 'object'))) {
return `<div class="audio></div>`
}
if (audioData.url) {
url = `data-url="${makeEmbed(audioData.url)}"`
}
if (audioData.volume) {
volume = `data-volume="${audioData.volume}"`
}
if (audioData['continue']) {
continueStr = `data-continue="true"`
}
return `
<div class="audio" ${url} ${volume} ${continueStr}></div>
`
}
function makeEmbed(url) {
if (!url.endsWith('#embed')) {
return `${url}#embed`
} else {
return url
}
}
})();