oralsite.new
clone your own copy | download snapshot

Snapshots | iceberg

Inside this repository

player.js
application/javascript

Download raw (7.3 KB)

(function () {
  // Track
  // Chapters
  // Play button

  // Play / Pause
  // Jump in time
  // Set volume

  /**
   * Transform time in seconds to timecode: (hh:?)mm:ss
   * @param {number} t time
   */
  function toTimecode (t) {
    t = Math.round(t)
    var hours = Math.floor(t / 3600),
        minutes = Math.floor((t % 3600) / 60),
        seconds = t % 60,
        timecode = '';

    if (hours  > 0) {
        timecode += hours.toString(10)
        timecode += ':';
    }
    
    timecode += (minutes > 9) ? minutes.toString(10) : '0' + minutes.toString(10)
    timecode += ':' + ((seconds > 9) ? seconds.toString(10) : '0' + seconds.toString(10))

    return timecode;
  }

  // Update timer when the player is playing

  // Update on seek

  // events:

  // play
  // pause
  // ended

  // seeked

  // timeupdate

  function initPlayer(audio, player) {
    var trackEl = player.querySelector('[data-role="track"]'),
        trackProgressEl = player.querySelector('[data-role="progress"]'),
        currentTimeEl = player.querySelector('[data-role="current-time"]'),
        durationEl = player.querySelector('[data-role="duration"]'),
        playPauseEl = player.querySelector('[data-role="play--pause"]'),
        scrubberEl = player.querySelector('[data-role="scrubber"]'),
        scrubberLabelEl = player.querySelector('[data-role="scrubber--label"]'),
        timeMarkerStartEl = player.querySelector('[data-role="time-marker--start"]'),
        timeMarkerEndEl = player.querySelector('[data-role="time-marker--end"]'),
        scrubbing = false,
        trackRect = null,
        scrubbingStart = -1;

    function updateTrackRect () {
      trackRect = trackEl.getBoundingClientRect();
    }

    function play () {
      audio.play();
    }

    function pause () {
      audio.pause();
    }

    function setPlayState () {
      player.dataset.playing = (audio.paused) ? 'false' : 'true';
    };
        
    function setProgress () {
      var timecode = toTimecode(audio.currentTime);
      currentTimeEl.innerText = timecode;
      trackProgressEl.style.height = getPositionForTime(audio.currentTime).toFixed(2) + 'px';
      if (!scrubbing) {
        scrubberLabelEl.innerText = timecode;
      }
    }

    function setDuration() {
      timeMarkerStartEl.innerText = toTimecode(0);
      timeMarkerEndEl.innerText = toTimecode(audio.duration);
      updateAudioBookmarks();
    }

    /**
     * returns a a time for the given y position
     * @param {int} y Position relative to the top of the track element
     */
    function getTimeForPos (y) {
      return Math.max(0, Math.min(1, y / trackRect.height)) * audio.duration;
    }

    /**
     * returns a height for given t
     * @param {int} t time in seconds
     * returns a 
     */
    function getPositionForTime (t) {
      return Math.max(0, Math.min(1, t / audio.duration)) * trackRect.height;
    }

    /**
     * Seek in the player
     * @param {int} t 
     */
    function seek (t) {
      audio.currentTime = t;
    }

    /*
      Creates a following HTML structure and attaches event listeners
    
      <section data-role="track--bookmark" class="track--bookmark"`>
        <span class="track--bookmark--timecode"></span>
        <span class="track--bookmark--label"></span>
      </section>
    */
    function makeAudioBookmark (t, timecode, label) {
      var bookmark = document.createElement('section'),
          bookmarkTimecode = document.createElement('span'),
          bookmarkLabel = document.createElement('span');

      bookmarkTimecode.classList.add('track--bookmark--timecode');
      bookmarkTimecode.appendChild(document.createTextNode(timecode));

      bookmarkLabel.classList.add('track--bookmark--label');
      bookmarkLabel.appendChild(document.createTextNode(label));

      bookmark.dataset.role = 'track--bookmark';
      bookmark.dataset.t = t;
      bookmark.classList.add('track--bookmark');
      bookmark.appendChild(bookmarkTimecode);
      bookmark.appendChild(bookmarkLabel);

      bookmark.addEventListener('mousedown', function (e) {
        e.stopImmediatePropagation(); // Avoid it to reach the track
        seek(t);
      });

      return bookmark;
    }

    /***
     * Finds audio bookmarks in the htmls document and places them on the player's timeline.
     * 
     * Audio bookmarks are expected to have this syntax:
     * <span class="audiobookmark" data-time="{t:int}">{label:string}</span>
     */
    function collectAudioBookmarks () {
      var markers = document.querySelectorAll('.audiobookmark');
      
      for (var i = 0; i < markers.length; i++) {
        var label = markers[i].innerHTML,
        t = markers[i].dataset.time;
        
        var bookmark = makeAudioBookmark(t, toTimecode(t), label);
        trackEl.appendChild(bookmark);
      }

      // for (var i=markers.length-1; i > -1; i--) {
      //   markers[i].remove();
      // }
    }

    /**
     * Update position of audiobookmarks on duration changes in the audio file.
     */
    function updateAudioBookmarks () {
      var bookmarks = trackEl.querySelectorAll('.track--bookmark');
      for (var i=0; i < bookmarks.length; i++) {
        pos = parseFloat(bookmarks[i].dataset.t) / audio.duration;
        bookmarks[i].style.top = parseFloat(100 * pos).toFixed(2) + '%'
      }
    }

    audio.addEventListener('play', setPlayState);
    audio.addEventListener('pause', setPlayState);
    audio.addEventListener('ended', setPlayState);
    audio.addEventListener('seeked', setPlayState);
    audio.addEventListener('timeupdate', setProgress);
    audio.addEventListener('durationchange', setDuration);

    trackEl.addEventListener('mousedown', function (e) {
      e.stopImmediatePropagation();
      e.stopImmediatePropagation();
      seek(getTimeForPos(e.clientY - trackRect.top));
    });

    // mouse down
    scrubberEl.addEventListener('mousedown', function (e) {
      e.stopImmediatePropagation();
      e.stopImmediatePropagation();
      scrubbing = true;
      scrubbingStart = e.clientY;
      scrubberEl.dataset.scrubbing = 'true';
      scrubberLabelEl.innerText = toTimecode(getTimeForPos(e.clientY - trackRect.top));
    });

    // mouse move
    document.body.addEventListener('mousemove', function (e) {
      e.stopImmediatePropagation();
      if (scrubbing) {
        offset = e.clientY - scrubbingStart;
        scrubberEl.style.transform = 'translateY(' + offset.toString() + 'px)';
        scrubberLabelEl.innerText = toTimecode(getTimeForPos(e.clientY - trackRect.top));
      }
    });

    // mouse up
    document.body.addEventListener('mouseup', function (e) {
      e.stopImmediatePropagation();
      if (scrubbing) {
        e.stopImmediatePropagation();
        scrubbing = false;
        seek(getTimeForPos(e.clientY - trackRect.top));
        scrubberEl.style.transform = '';
        scrubberEl.dataset.scrubbing = 'false';
      }
    })

    // Create a drag thing, style a bit better
    // Introduce markers

    playPauseEl.addEventListener('click', function (e) {
      e.stopImmediatePropagation();
      if (audio.paused) {
        play();
      }
      else {
        pause();
      }
    });

    window.addEventListener('resize', updateTrackRect);
    window.addEventListener('resize', setProgress);

    collectAudioBookmarks();
    updateTrackRect();
    setDuration();
    setProgress();
    setPlayState();
  }


  window.initPlayer = initPlayer;

  
})();