(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
*/ 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: * {label:string} */ 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; })();