v2.0.0.1
Publisium
- (function () {
- var SPEEDS = [1, 1.25, 1.5, 1.75, 2, 0.75];
-
- function format(seconds) {
- if (!isFinite(seconds) || seconds < 0) {
- seconds = 0;
- }
- var whole = Math.floor(seconds);
- var minutes = Math.floor(whole / 60);
- var rest = whole % 60;
-
- return minutes + ':' + (rest < 10 ? '0' : '') + rest;
- }
-
- function init(root) {
- var tracks;
- try {
- tracks = JSON.parse(root.getAttribute('data-tracks') || '[]');
- } catch (error) {
- return;
- }
- if (!Array.isArray(tracks) || tracks.length === 0) {
- return;
- }
-
- var toggle = root.querySelector('.read-aloud-toggle');
- var seek = root.querySelector('.read-aloud-seek');
- var time = root.querySelector('.read-aloud-time');
- var speedButton = root.querySelector('.read-aloud-speed');
- var labelPlay = root.getAttribute('data-label-play') || 'Play';
- var labelPause = root.getAttribute('data-label-pause') || 'Pause';
-
- var audio = new Audio();
- audio.preload = 'metadata';
-
- var durations = tracks.map(function () { return 0; });
- var index = 0;
- var speedIndex = 0;
- var probed = false;
- var dragging = false;
- var loaded = false;
-
- seek.disabled = true;
-
- function total() {
- return durations.reduce(function (sum, value) { return sum + value; }, 0);
- }
-
- function known() {
- return durations.every(function (value) { return value > 0; });
- }
-
- function offset(trackIndex) {
- var sum = 0;
- for (var i = 0; i < trackIndex; i++) {
- sum += durations[i];
- }
-
- return sum;
- }
-
- function position() {
- return offset(index) + (audio.currentTime || 0);
- }
-
- function render() {
- var current = loaded ? position() : 0;
-
- if (known()) {
- var sum = total();
- if (!dragging) {
- seek.value = String(sum > 0 ? Math.round((current / sum) * 1000) : 0);
- }
- seek.disabled = false;
- time.textContent = format(dragging ? (seek.value / 1000) * sum : current) + ' / ' + format(sum);
- } else {
- time.textContent = format(audio.currentTime || 0);
- }
-
- seek.style.setProperty('--read-aloud-progress', (seek.value / 10) + '%');
- }
-
- function probe() {
- if (probed) {
- return;
- }
- probed = true;
-
- tracks.forEach(function (url, trackIndex) {
- var probeAudio = new Audio();
- probeAudio.preload = 'metadata';
- probeAudio.addEventListener('loadedmetadata', function () {
- if (isFinite(probeAudio.duration) && probeAudio.duration > 0) {
- durations[trackIndex] = probeAudio.duration;
- render();
- }
- });
- probeAudio.src = url;
- });
- }
-
- function load(trackIndex, autoplay, startAt) {
- index = trackIndex;
- loaded = true;
- audio.src = tracks[trackIndex];
- audio.defaultPlaybackRate = SPEEDS[speedIndex];
- audio.playbackRate = SPEEDS[speedIndex];
-
- if (startAt > 0) {
- audio.addEventListener('loadedmetadata', function seekAfterLoad() {
- audio.removeEventListener('loadedmetadata', seekAfterLoad);
- audio.currentTime = startAt;
- });
- }
-
- if (autoplay) {
- var attempt = audio.play();
- if (attempt && typeof attempt.catch === 'function') {
- attempt.catch(function () {});
- }
- }
- }
-
- function setPlaying(playing) {
- root.classList.toggle('is-playing', playing);
- var label = playing ? labelPause : labelPlay;
- toggle.setAttribute('aria-label', label);
- toggle.setAttribute('title', label);
- }
-
- function nextTrack() {
- if (index < tracks.length - 1) {
- load(index + 1, true, 0);
- return;
- }
-
- setPlaying(false);
- load(0, false, 0);
- loaded = false;
- seek.value = '0';
- render();
- }
-
- toggle.addEventListener('click', function () {
- probe();
-
- if (!loaded) {
- load(0, true, 0);
- return;
- }
-
- if (audio.paused) {
- var attempt = audio.play();
- if (attempt && typeof attempt.catch === 'function') {
- attempt.catch(function () {});
- }
- } else {
- audio.pause();
- }
- });
-
- speedButton.addEventListener('click', function () {
- speedIndex = (speedIndex + 1) % SPEEDS.length;
- audio.defaultPlaybackRate = SPEEDS[speedIndex];
- audio.playbackRate = SPEEDS[speedIndex];
- speedButton.textContent = SPEEDS[speedIndex] + '×';
- });
-
- seek.addEventListener('input', function () {
- dragging = true;
- render();
- });
-
- seek.addEventListener('change', function () {
- dragging = false;
- if (!known()) {
- return;
- }
-
- var target = (seek.value / 1000) * total();
- var trackIndex = 0;
- while (trackIndex < tracks.length - 1 && target >= offset(trackIndex) + durations[trackIndex]) {
- trackIndex++;
- }
- var within = Math.max(0, target - offset(trackIndex));
- var wasPlaying = loaded && !audio.paused;
-
- if (loaded && trackIndex === index) {
- audio.currentTime = within;
- } else {
- load(trackIndex, wasPlaying, within);
- }
- render();
- });
-
- audio.addEventListener('play', function () { setPlaying(true); });
- audio.addEventListener('pause', function () { setPlaying(false); });
- audio.addEventListener('timeupdate', render);
- audio.addEventListener('ended', nextTrack);
- audio.addEventListener('loadedmetadata', function () {
- if (isFinite(audio.duration) && audio.duration > 0) {
- durations[index] = audio.duration;
- render();
- }
- });
- audio.addEventListener('error', function () {
- if (index < tracks.length - 1) {
- load(index + 1, true, 0);
- } else {
- setPlaying(false);
- root.classList.add('is-error');
- }
- });
-
- root.addEventListener('pointerenter', probe, { once: true });
- root.addEventListener('focusin', probe, { once: true });
-
- setPlaying(false);
- render();
- }
-
- document.querySelectorAll('[data-read-aloud]').forEach(init);
- })();
-