aboutsummaryrefslogtreecommitdiffhomepage
path: root/plugin/audio
diff options
context:
space:
mode:
authorMarvin Borner2019-04-01 20:16:24 +0200
committerMarvin Borner2019-04-01 20:16:24 +0200
commit83b56d1270cdb179b64a1be51f8c1e7fe586c35e (patch)
treeae40a84def05eb42c53305e0caa8c2a2440fce71 /plugin/audio
parent0b3e7839ebf4ed8b6c180aca0abafa28c67aee6d (diff)
Added content
Diffstat (limited to 'plugin/audio')
-rw-r--r--plugin/audio/audio-slideshow.js520
1 files changed, 520 insertions, 0 deletions
diff --git a/plugin/audio/audio-slideshow.js b/plugin/audio/audio-slideshow.js
new file mode 100644
index 0000000..ef70e21
--- /dev/null
+++ b/plugin/audio/audio-slideshow.js
@@ -0,0 +1,520 @@
+/*****************************************************************
+ ** Author: Asvin Goel, goel@telematique.eu
+ **
+ ** Audio slideshow is a plugin for reveal.js allowing to
+ ** automatically play audio files for a slide deck. After an audio
+ ** file has completed playing the next slide or fragment is
+ ** automatically shown and the respective audio file is played.
+ ** If no audio file is available, a blank audio file with default
+ ** duration is played instead.
+ **
+ ** Version: 0.6.1
+ **
+ ** License: MIT license (see LICENSE.md)
+ **
+ ******************************************************************/
+
+var RevealAudioSlideshow = window.RevealAudioSlideshow || (function () {
+
+ // default parameters
+ var prefix = "audio/";
+ var suffix = ".ogg";
+ var textToSpeechURL = null; // no text to speech converter
+// var textToSpeechURL = "http://api.voicerss.org/?key=[YOUR_KEY]&hl=en-gb&c=ogg&src="; // the text to speech converter
+ var defaultNotes = false; // use slide notes as default for the text to speech converter
+ var defaultText = false; // use slide text as default for the text to speech converter
+ var defaultDuration = 5; // value in seconds
+ var advance = 0; // advance to next slide after given time in milliseconds after audio has played, use negative value to not advance
+ var autoplay = false; // automatically start slideshow
+ var playerOpacity = .05; // opacity when the mouse is far from to the audioplayer
+ var startAtFragment = false; // when moving to a slide, start at the current fragment or at the start of the slide
+ var playerStyle = "position: fixed; bottom: 4px; left: 25%; width: 50%; height:75px; z-index: 33;"; // style used for container of audio controls
+ // ------------------
+
+ var silence;
+ var currentAudio = null;
+ var previousAudio = null;
+ var timer = null;
+
+ Reveal.addEventListener('fragmentshown', function (event) {
+ if (timer) {
+ clearTimeout(timer);
+ timer = null;
+ }
+//console.debug( "fragmentshown ");
+ selectAudio();
+ });
+
+ Reveal.addEventListener('fragmenthidden', function (event) {
+ if (timer) {
+ clearTimeout(timer);
+ timer = null;
+ }
+//console.debug( "fragmenthidden ");
+ selectAudio();
+ });
+
+ Reveal.addEventListener('ready', function (event) {
+ setup();
+//console.debug( "ready ");
+ selectAudio();
+ document.dispatchEvent(new CustomEvent('stopplayback'));
+
+ });
+
+ Reveal.addEventListener('slidechanged', function (event) {
+ if (timer) {
+ clearTimeout(timer);
+ timer = null;
+ }
+//console.debug( "slidechanged ");
+ var indices = Reveal.getIndices();
+ if (!startAtFragment && typeof indices.f !== 'undefined' && indices.f >= 0) {
+ // hide fragments when slide is shown
+ Reveal.slide(indices.h, indices.v, -1);
+ }
+
+ selectAudio();
+ });
+
+ Reveal.addEventListener('paused', function (event) {
+ if (timer) {
+ clearTimeout(timer);
+ timer = null;
+ }
+ currentAudio.pause();
+ });
+
+ Reveal.addEventListener('resumed', function (event) {
+ if (timer) {
+ clearTimeout(timer);
+ timer = null;
+ }
+ });
+
+ Reveal.addEventListener('overviewshown', function (event) {
+ if (timer) {
+ clearTimeout(timer);
+ timer = null;
+ }
+ currentAudio.pause();
+ document.querySelector(".audio-controls").style.visibility = "hidden";
+ });
+
+ Reveal.addEventListener('overviewhidden', function (event) {
+ if (timer) {
+ clearTimeout(timer);
+ timer = null;
+ }
+ document.querySelector(".audio-controls").style.visibility = "visible";
+ });
+
+ function selectAudio(previousAudio) {
+ if (currentAudio) {
+ currentAudio.pause();
+ currentAudio.style.display = "none";
+ }
+ var indices = Reveal.getIndices();
+ var id = "audioplayer-" + indices.h + '.' + indices.v;
+ if (indices.f != undefined && indices.f >= 0) id = id + '.' + indices.f;
+ currentAudio = document.getElementById(id);
+ if (currentAudio) {
+ currentAudio.style.display = "block";
+ if (previousAudio) {
+ if (currentAudio.id != previousAudio.id) {
+ currentAudio.volume = previousAudio.volume;
+ currentAudio.muted = previousAudio.muted;
+//console.debug( "Play " + currentAudio.id);
+ currentAudio.play();
+ }
+ } else if (autoplay) {
+ currentAudio.play();
+ }
+
+ }
+ }
+
+
+ function setup() {
+ // deprecated parameters
+ if (Reveal.getConfig().audioPrefix) {
+ prefix = Reveal.getConfig().audioPrefix;
+ console.warn('Setting parameter "audioPrefix" is deprecated!');
+ }
+ if (Reveal.getConfig().audioSuffix) {
+ suffix = Reveal.getConfig().audioSuffix;
+ console.warn('Setting parameter "audioSuffix" is deprecated!');
+ }
+ if (Reveal.getConfig().audioTextToSpeechURL) {
+ textToSpeechURL = Reveal.getConfig().audioTextToSpeechURL;
+ console.warn('Setting parameter "audioTextToSpeechURL" is deprecated!');
+ }
+ if (Reveal.getConfig().audioDefaultDuration) {
+ defaultDuration = Reveal.getConfig().audioDefaultDuration;
+ console.warn('Setting parameter "audioDefaultDuration" is deprecated!');
+ }
+ if (Reveal.getConfig().audioAutoplay) {
+ autoplay = Reveal.getConfig().audioAutoplay;
+ console.warn('Setting parameter "audioAutoplay" is deprecated!');
+ }
+ if (Reveal.getConfig().audioPlayerOpacity) {
+ playerOpacity = Reveal.getConfig().audioPlayerOpacity;
+ console.warn('Setting parameter "audioPlayerOpacity" is deprecated!');
+ }
+
+ // set parameters
+ var config = Reveal.getConfig().audio;
+ if (config) {
+ if (config.prefix != null) prefix = config.prefix;
+ if (config.suffix != null) suffix = config.suffix;
+ if (config.textToSpeechURL != null) textToSpeechURL = config.textToSpeechURL;
+ if (config.defaultNotes != null) defaultNotes = config.defaultNotes;
+ if (config.defaultText != null) defaultText = config.defaultText;
+ if (config.defaultDuration != null) defaultDuration = config.defaultDuration;
+ if (config.advance != null) advance = config.advance;
+ if (config.autoplay != null) autoplay = config.autoplay;
+ if (config.playerOpacity != null) playerOpacity = config.playerOpacity;
+ if (config.playerStyle != null) playerStyle = config.playerStyle;
+ }
+
+ if ('ontouchstart' in window || navigator.msMaxTouchPoints) {
+ opacity = 1;
+ }
+ if (Reveal.getConfig().audioStartAtFragment) startAtFragment = Reveal.getConfig().audioStartAtFragment;
+
+ // set style so that audio controls are shown on hover
+ var css = '.audio-controls>audio { opacity:' + playerOpacity + ';} .audio-controls:hover>audio { opacity:1;}';
+ style = document.createElement('style');
+ if (style.styleSheet) {
+ style.styleSheet.cssText = css;
+ } else {
+ style.appendChild(document.createTextNode(css));
+ }
+ document.getElementsByTagName('head')[0].appendChild(style);
+
+ silence = new SilentAudio(defaultDuration); // create the wave file
+
+ var divElement = document.createElement('div');
+ divElement.className = "audio-controls";
+ divElement.setAttribute('style', playerStyle);
+ document.querySelector(".reveal").appendChild(divElement);
+
+ // create audio players for all slides
+ var horizontalSlides = document.querySelectorAll('.reveal .slides>section');
+ for (var h = 0, len1 = horizontalSlides.length; h < len1; h++) {
+ var verticalSlides = horizontalSlides[h].querySelectorAll('section');
+ if (!verticalSlides.length) {
+ setupAllAudioElements(divElement, h, 0, horizontalSlides[h]);
+ } else {
+ for (var v = 0, len2 = verticalSlides.length; v < len2; v++) {
+ setupAllAudioElements(divElement, h, v, verticalSlides[v]);
+ }
+ }
+ }
+ }
+
+ function getText(textContainer) {
+ var elements = textContainer.querySelectorAll('[data-audio-text]');
+ for (var i = 0, len = elements.length; i < len; i++) {
+ // replace all elements with data-audio-text by specified text
+ textContainer.innerHTML = textContainer.innerHTML.replace(elements[i].outerHTML, elements[i].getAttribute('data-audio-text'));
+ }
+ return textContainer.textContent.trim().replace(/\s+/g, ' ');
+ }
+
+ function setupAllAudioElements(container, h, v, slide) {
+ var textContainer = document.createElement('div');
+ var text = null;
+ if (!slide.hasAttribute('data-audio-src')) {
+ // determine text for TTS
+ if (slide.hasAttribute('data-audio-text')) {
+ text = slide.getAttribute('data-audio-text');
+ } else if (defaultNotes && Reveal.getSlideNotes(slide)) {
+ // defaultNotes
+ var div = document.createElement("div");
+ div.innerHTML = Reveal.getSlideNotes(slide);
+ text = div.textContent || '';
+ } else if (defaultText) {
+ textContainer.innerHTML = slide.innerHTML;
+ // remove fragments
+ var fragments = textContainer.querySelectorAll('.fragment');
+ for (var f = 0, len = fragments.length; f < len; f++) {
+ textContainer.innerHTML = textContainer.innerHTML.replace(fragments[f].outerHTML, '');
+ }
+ text = getText(textContainer);
+ }
+// alert( h + '.' + v + ": " + text );
+// console.log( h + '.' + v + ": " + text );
+ }
+ setupAudioElement(container, h + '.' + v, slide.getAttribute('data-audio-src'), text, slide.querySelector(':not(.fragment) > video[data-audio-controls]'));
+ var i = 0;
+ var fragments;
+ while ((fragments = slide.querySelectorAll('.fragment[data-fragment-index="' + i + '"]')).length > 0) {
+ var audio = null;
+ var video = null;
+ var text = '';
+ for (var f = 0, len = fragments.length; f < len; f++) {
+ if (!audio) audio = fragments[f].getAttribute('data-audio-src');
+ if (!video) video = fragments[f].querySelector('video[data-audio-controls]');
+ // determine text for TTS
+ if (fragments[f].hasAttribute('data-audio-text')) {
+ text += fragments[f].getAttribute('data-audio-text') + ' ';
+ } else if (defaultText) {
+ textContainer.innerHTML = fragments[f].textContent;
+ text += getText(textContainer);
+ }
+ }
+//console.log( h + '.' + v + '.' + i + ": >" + text +"<")
+ setupAudioElement(container, h + '.' + v + '.' + i, audio, text, video);
+ i++;
+ }
+ }
+
+ function linkVideoToAudioControls(audioElement, videoElement) {
+ audioElement.addEventListener('playing', function (event) {
+ videoElement.currentTime = audioElement.currentTime;
+ });
+ audioElement.addEventListener('play', function (event) {
+ videoElement.currentTime = audioElement.currentTime;
+ if (videoElement.paused) videoElement.play();
+ });
+ audioElement.addEventListener('pause', function (event) {
+ videoElement.currentTime = audioElement.currentTime;
+ if (!videoElement.paused) videoElement.pause();
+ });
+ audioElement.addEventListener('volumechange', function (event) {
+ videoElement.volume = audioElement.volume;
+ videoElement.muted = audioElement.muted;
+ });
+ audioElement.addEventListener('seeked', function (event) {
+ videoElement.currentTime = audioElement.currentTime;
+ });
+
+ // add silent audio to video to be used as fallback
+ var audioSource = audioElement.querySelector('source[data-audio-silent]');
+ if (audioSource) audioElement.removeChild(audioSource);
+ audioSource = document.createElement('source');
+ var videoSilence = new SilentAudio(Math.round(videoElement.duration + .5)); // create the wave file
+ audioSource.src = videoSilence.dataURI;
+ audioSource.setAttribute("data-audio-silent", videoElement.duration);
+ audioElement.appendChild(audioSource, audioElement.firstChild);
+ }
+
+ function setupFallbackAudio(audioElement, text, videoElement) {
+ // default file cannot be read
+ if (textToSpeechURL != null && text != null && text != "") {
+ var audioSource = document.createElement('source');
+ audioSource.src = textToSpeechURL + encodeURIComponent(text);
+ audioSource.setAttribute('data-tts', audioElement.id.split('-').pop());
+ audioElement.appendChild(audioSource, audioElement.firstChild);
+ } else {
+ if (!audioElement.querySelector('source[data-audio-silent]')) {
+ // create silent source if not yet existent
+ var audioSource = document.createElement('source');
+ audioSource.src = silence.dataURI;
+ audioSource.setAttribute("data-audio-silent", defaultDuration);
+ audioElement.appendChild(audioSource, audioElement.firstChild);
+ }
+ }
+ }
+
+ function setupAudioElement(container, indices, audioFile, text, videoElement) {
+ var audioElement = document.createElement('audio');
+ audioElement.setAttribute('style', "position: relative; top: 20px; left: 10%; width: 80%;");
+ audioElement.id = "audioplayer-" + indices;
+ audioElement.style.display = "none";
+ audioElement.setAttribute('controls', '');
+ audioElement.setAttribute('preload', 'none');
+
+ if (videoElement) {
+ // connect play, pause, volumechange, mute, timeupdate events to video
+ if (videoElement.duration) {
+ linkVideoToAudioControls(audioElement, videoElement);
+ } else {
+ videoElement.onloadedmetadata = function () {
+ linkVideoToAudioControls(audioElement, videoElement);
+ };
+ }
+ }
+ audioElement.addEventListener('ended', function (event) {
+ if (typeof Recorder == 'undefined' || !Recorder.isRecording) {
+ // determine whether and when slideshow advances with next slide
+ var advanceNow = advance;
+ var slide = Reveal.getCurrentSlide();
+ // check current fragment
+ var indices = Reveal.getIndices();
+ if (typeof indices.f !== 'undefined' && indices.f >= 0) {
+ var fragment = slide.querySelector('.fragment[data-fragment-index="' + indices.f + '"][data-audio-advance]');
+ if (fragment) {
+ advanceNow = fragment.getAttribute('data-audio-advance');
+ }
+ } else if (slide.hasAttribute('data-audio-advance')) {
+ advanceNow = slide.getAttribute('data-audio-advance');
+ }
+ // advance immediately or set a timer - or do nothing
+ if (advance == "true" || advanceNow == 0) {
+ var previousAudio = currentAudio;
+ Reveal.next();
+ selectAudio(previousAudio);
+ } else if (advanceNow > 0) {
+ timer = setTimeout(function () {
+ var previousAudio = currentAudio;
+ Reveal.next();
+ selectAudio(previousAudio);
+ timer = null;
+ }, advanceNow);
+ }
+ }
+ });
+ audioElement.addEventListener('play', function (event) {
+ var evt = new CustomEvent('startplayback');
+ evt.timestamp = 1000 * audioElement.currentTime;
+ document.dispatchEvent(evt);
+
+ if (timer) {
+ clearTimeout(timer);
+ timer = null;
+ }
+ // preload next audio element so that it is available after slide change
+ var indices = Reveal.getIndices();
+ var nextId = "audioplayer-" + indices.h + '.' + indices.v;
+ if (indices.f != undefined && indices.f >= 0) {
+ nextId = nextId + '.' + (indices.f + 1);
+ } else {
+ nextId = nextId + '.0';
+ }
+ var nextAudio = document.getElementById(nextId);
+ if (!nextAudio) {
+ nextId = "audioplayer-" + indices.h + '.' + (indices.v + 1);
+ nextAudio = document.getElementById(nextId);
+ if (!nextAudio) {
+ nextId = "audioplayer-" + (indices.h + 1) + '.0';
+ nextAudio = document.getElementById(nextId);
+ }
+ }
+ if (nextAudio) {
+//console.debug( "Preload: " + nextAudio.id );
+ nextAudio.load();
+ }
+ });
+ audioElement.addEventListener('pause', function (event) {
+ if (timer) {
+ clearTimeout(timer);
+ timer = null;
+ }
+ document.dispatchEvent(new CustomEvent('stopplayback'));
+ });
+ audioElement.addEventListener('seeked', function (event) {
+ var evt = new CustomEvent('seekplayback');
+ evt.timestamp = 1000 * audioElement.currentTime;
+ document.dispatchEvent(evt);
+ if (timer) {
+ clearTimeout(timer);
+ timer = null;
+ }
+ });
+
+ if (audioFile != null) {
+ // Support comma separated lists of audio sources
+ audioFile.split(',').forEach(function (source) {
+ var audioSource = document.createElement('source');
+ audioSource.src = source;
+ audioElement.insertBefore(audioSource, audioElement.firstChild);
+ });
+ } else {
+ var audioExists = false;
+ try {
+ // check if audio file exists
+ var xhr = new XMLHttpRequest();
+ xhr.open('HEAD', prefix + indices + suffix, true);
+ xhr.onload = function () {
+ if (xhr.readyState === 4 && xhr.status >= 200 && xhr.status < 300) {
+ var audioSource = document.createElement('source');
+ audioSource.src = prefix + indices + suffix;
+ audioElement.insertBefore(audioSource, audioElement.firstChild);
+ audioExists = true;
+ } else {
+ setupFallbackAudio(audioElement, text, videoElement);
+ }
+ }
+ xhr.send(null);
+ } catch (error) {
+//console.log("Error checking audio" + audioExists);
+ // fallback if checking of audio file fails (e.g. when running the slideshow locally)
+ var audioSource = document.createElement('source');
+ audioSource.src = prefix + indices + suffix;
+ audioElement.insertBefore(audioSource, audioElement.firstChild);
+ setupFallbackAudio(audioElement, text, videoElement);
+ }
+ }
+ if (audioFile != null || defaultDuration > 0) {
+ container.appendChild(audioElement);
+ }
+ }
+
+
+})();
+
+/*****************************************************************
+ ** Create SilentAudio
+ ** based on: RIFFWAVE.js v0.03
+ ** http://www.codebase.es/riffwave/riffwave.js
+ **
+ ** Usage:
+ ** silence = new SilentAudio( 10 ); // create 10 seconds wave file
+ **
+ ******************************************************************/
+
+var FastBase64 = {
+ chars: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
+ encLookup: [],
+ Init: function () {
+ for (var e = 0; 4096 > e; e++) this.encLookup[e] = this.chars[e >> 6] + this.chars[63 & e]
+ },
+ Encode: function (e) {
+ for (var h = e.length, a = "", t = 0; h > 2;) n = e[t] << 16 | e[t + 1] << 8 | e[t + 2], a += this.encLookup[n >> 12] + this.encLookup[4095 & n], h -= 3, t += 3;
+ if (h > 0) {
+ var s = (252 & e[t]) >> 2, i = (3 & e[t]) << 4;
+ if (h > 1 && (i |= (240 & e[++t]) >> 4), a += this.chars[s], a += this.chars[i], 2 == h) {
+ var r = (15 & e[t++]) << 2;
+ r |= (192 & e[t]) >> 6, a += this.chars[r]
+ }
+ 1 == h && (a += "="), a += "="
+ }
+ return a
+ }
+};
+FastBase64.Init();
+var SilentAudio = function (e) {
+ function h(e) {
+ return [255 & e, e >> 8 & 255, e >> 16 & 255, e >> 24 & 255]
+ }
+
+ function a(e) {
+ return [255 & e, e >> 8 & 255]
+ }
+
+ function t(e) {
+ for (var h = [], a = 0, t = e.length, s = 0; t > s; s++) h[a++] = 255 & e[s], h[a++] = e[s] >> 8 & 255;
+ return h
+ }
+
+ this.data = [], this.wav = [], this.dataURI = "", this.header = {
+ chunkId: [82, 73, 70, 70],
+ chunkSize: 0,
+ format: [87, 65, 86, 69],
+ subChunk1Id: [102, 109, 116, 32],
+ subChunk1Size: 16,
+ audioFormat: 1,
+ numChannels: 1,
+ sampleRate: 8e3,
+ byteRate: 0,
+ blockAlign: 0,
+ bitsPerSample: 8,
+ subChunk2Id: [100, 97, 116, 97],
+ subChunk2Size: 0
+ }, this.Make = function (e) {
+ for (var s = 0; s < e * this.header.sampleRate; s++) this.data[s] = 127;
+ this.header.blockAlign = this.header.numChannels * this.header.bitsPerSample >> 3, this.header.byteRate = this.header.blockAlign * this.sampleRate, this.header.subChunk2Size = this.data.length * (this.header.bitsPerSample >> 3), this.header.chunkSize = 36 + this.header.subChunk2Size, this.wav = this.header.chunkId.concat(h(this.header.chunkSize), this.header.format, this.header.subChunk1Id, h(this.header.subChunk1Size), a(this.header.audioFormat), a(this.header.numChannels), h(this.header.sampleRate), h(this.header.byteRate), a(this.header.blockAlign), a(this.header.bitsPerSample), this.header.subChunk2Id, h(this.header.subChunk2Size), 16 == this.header.bitsPerSample ? t(this.data) : this.data), this.dataURI = "data:audio/wav;base64," + FastBase64.Encode(this.wav)
+ }, this.Make(e)
+};