Files
junisaber/src/components/song-preview.js
2018-10-16 21:19:48 -07:00

240 lines
7.3 KiB
JavaScript

var utils = require('../utils');
const PREVIEW_VOLUME = 0.5;
/**
* Song preview when search result selected with smart logic for preloading.
*/
AFRAME.registerComponent('song-preview-system', {
schema: {
challengeId: {default: ''},
debug: {default: false},
isSearching: {default: false},
isSongLoading: {default: false}, // Continue to play preview song during loading.
selectedChallengeId: {type: 'string'}
},
init: function () {
this.analyserEl = document.getElementById('audioAnalyser');
this.audio = document.createElement('audio'); // Dummy.
this.audioStore = {};
this.preloadedAudioIds = [];
this.preloadQueue = [];
},
update: function (oldData) {
const data = this.data;
// Play clicked.
// But don't start playing it if it wasn't playing already.
if (!oldData.challengeId && data.challengeId) {
if (this.analyserEl.components.audioanalyser.volume === 0 ||
this.audio.currentTime < 1) {
this.stopSong();
} else {
this.fadeDown();
}
return;
}
// Unselect.
if (oldData.selectedChallengeId && !data.selectedChallengeId && !data.isSearching) {
this.stopSong();
}
// Song finished loading.
// Continue to play preview song during loading to keep entertained.
if (oldData.isSongLoading && !data.isSongLoading) {
this.stopSong();
return;
}
// Selected challenge ID updated.
if (data.selectedChallengeId && oldData.selectedChallengeId !== data.selectedChallengeId) {
if (this.audio) { this.stopSong(); }
// If not yet preloaded, pause the preload queue until this song is loaded.
if (!this.preloadedAudioIds.includes(data.selectedChallengeId) &&
data.selectedChallengeId !== this.currentLoadingId) {
this.prioritizePreloadSong();
}
this.playSong(data.selectedChallengeId);
}
},
log: function (str) {
if (!this.data.debug) { return; }
console.log(str);
},
/**
* Song was selected so pause preload queue, prioritize its loading, and try to play ASAP.
*/
prioritizePreloadSong: function () {
const data = this.data;
const preloadQueue = this.preloadQueue;
this.log(`[song-preview] Prioritizing loading of ${data.selectedChallengeId}`);
this.priorityLoadingChallengeId = data.selectedChallengeId;
this.audioStore[data.selectedChallengeId].addEventListener('loadeddata', () => {
this.log(`[song-preview] Finished load of priority ${data.selectedChallengeId}`);
this.preloadedAudioIds.push(data.selectedChallengeId);
this.priorityLoadingChallengeId = '';
// Resume preloading queue.
if (preloadQueue.length) {
this.log(`[song-preview] Resuming queue with ${preloadQueue[0].challengeId}`);
this.preloadMetadata(preloadQueue[0]);
}
});
// Preload.
this.audioStore[data.selectedChallengeId].src =
utils.getS3FileUrl(data.selectedChallengeId, 'song.ogg');
// Remove from preload queue.
for (let i = 0; i < preloadQueue.length; i++) {
if (preloadQueue[i].challengeId === data.selectedChallengeId) {
preloadQueue.splice(i, 1);
break;
}
}
},
/**
* Create an audio element and queue to preload. If the queue is empty, preload it
* immediately.
*/
queuePreloadSong: function (challengeId, previewStartTime) {
if (this.audioStore[challengeId]) { return; }
const audio = document.createElement('audio');
audio.crossOrigin = 'anonymous';
audio.dataset.previewStartTime = previewStartTime;
this.audioStore[challengeId] = audio;
let src = utils.getS3FileUrl(challengeId, 'song.ogg');
if (this.currentLoadingId) {
// Audio currently loading, add to queue.
this.preloadQueue.push({
audio: audio,
challengeId: challengeId,
src: src
});
} else {
// Empty queue, preload now.
this.preloadMetadata({
audio: audio,
challengeId: challengeId,
src: src
});
}
},
/**
* Preload metadata of audio file for quick play.
* Set `src` and `preload`.
* A preload queue is set up so we only preload one at a time to not bog down
* the network. If a song is selected to preview, we can bump it to the front of the
* queue.
*/
preloadMetadata: function (preloadItem) {
const audio = preloadItem.audio;
this.log(`[song-preview] Preloading song preview ${preloadItem.challengeId}`);
audio.addEventListener('loadedmetadata', () => {
// Song preloaded.
this.log(`[song-preview] Finished preloading song preview ${preloadItem.challengeId}`);
this.preloadedAudioIds.push(preloadItem.challengeId);
this.currentLoadingId = '';
// Move on to next song in queue if any.
this.log(`[song-preview] ${this.preloadQueue.length} in queue`);
if (this.preloadQueue.length && !this.priorityLoadingChallengeId) {
this.preloadMetadata(this.preloadQueue.shift());
}
});
audio.preload = 'metadata';
audio.src = preloadItem.src;
this.currentLoadingId = preloadItem.challengeId;
},
stopSong: function () {
if (!this.audio) { return; }
if (!this.audio.paused) { this.audio.pause(); }
},
playSong: function (challengeId) {
if (!challengeId) { return; }
const audioanalyser = this.analyserEl.components.audioanalyser;
this.audio = this.audioStore[challengeId];
audioanalyser.resumeContext();
this.audio.currentTime = this.audio.dataset.previewStartTime;
this.audio.play();
this.fadeIn();
this.updateAnalyser();
// Prefetch buffer for playing.
if (audioanalyser.xhr) { audioanalyser.xhr.abort(); }
audioanalyser.fetchAudioBuffer(utils.getS3FileUrl(challengeId, 'song.ogg'));
},
updateAnalyser: function () {
this.analyserEl.setAttribute('audioanalyser', 'src', this.audio);
},
/**
* Stop song from preloading.
*/
clearSong: function (challengeId) {
let audio = this.audioStore[challengeId];
audio.preload = 'none';
// Remove from queue if in there.
let index;
for (let i = 0; i < this.preloadQueue.length; i++) {
if (this.preloadQueue[i].id === challengeId) {
index = i;
break;
}
}
if (!index) { return; }
this.preloadQueue.splice(index, 1);
},
fadeIn: function () {
const context = this.analyserEl.components.audioanalyser.context;
const gainNode = this.analyserEl.components.audioanalyser.gainNode;
gainNode.gain.linearRampToValueAtTime(PREVIEW_VOLUME, context.currentTime + 1.5);
},
fadeDown: function () {
const context = this.analyserEl.components.audioanalyser.context;
const gainNode = this.analyserEl.components.audioanalyser.gainNode;
gainNode.gain.linearRampToValueAtTime(0.05, context.currentTime + 1.5);
}
});
/**
* Data component attached to search result for song preview system.
*/
AFRAME.registerComponent('song-preview', {
schema: {
challengeId: {type: 'string'},
previewStartTime: {type: 'number'}
},
update: function (oldData) {
if (oldData.challengeId && this.data.challengeId !== oldData.challengeId) {
this.el.sceneEl.components['song-preview-system'].clearSong(oldData.challengeId);
}
this.el.sceneEl.components['song-preview-system'].queuePreloadSong(
this.data.challengeId, this.data.previewStartTime
);
}
});