(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;
})();