/*****************************************************************
 ** 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)
};