mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-21 09:09:01 +00:00
252 lines
6 KiB
JavaScript
252 lines
6 KiB
JavaScript
(function() {
|
|
const invariant = (assertion, message) => {
|
|
if (!assertion) {
|
|
console.error(message);
|
|
}
|
|
};
|
|
|
|
async function readFileAsArrayBuffer(file) {
|
|
return new Promise((resolve, reject) => {
|
|
const reader = new FileReader();
|
|
reader.onload = function(e) {
|
|
const arrayBuffer = e.target.result;
|
|
resolve(arrayBuffer);
|
|
};
|
|
reader.onerror = function(e) {
|
|
reject(e);
|
|
};
|
|
reader.readAsArrayBuffer(file);
|
|
});
|
|
}
|
|
|
|
async function readUrlAsArrayBuffer(url) {
|
|
return new Promise((resolve, reject) => {
|
|
const oReq = new XMLHttpRequest();
|
|
oReq.open("GET", url, true);
|
|
oReq.responseType = "arraybuffer";
|
|
|
|
oReq.onload = () => {
|
|
const arrayBuffer = oReq.response; // Note: not oReq.responseText
|
|
resolve(arrayBuffer);
|
|
};
|
|
|
|
oReq.onerror = e => {
|
|
reject(e);
|
|
};
|
|
|
|
oReq.send(null);
|
|
return true;
|
|
});
|
|
}
|
|
|
|
const STATUS = {
|
|
WAITING: "WAITING",
|
|
PLAYING: "PLAYING",
|
|
STOPPED: "STOPPED",
|
|
PAUSED: "PAUSED"
|
|
};
|
|
|
|
class BufferSource extends window.Emitter {
|
|
constructor(context, destination) {
|
|
super();
|
|
this._context = context;
|
|
this._destination = destination;
|
|
this._loop = false;
|
|
this._buffer = null;
|
|
this._source = null;
|
|
this._status = STATUS.STOPPED;
|
|
this._startTime = null;
|
|
this._autoPlay = false;
|
|
this._pausedAtTime = null;
|
|
this._onEnded = this._onEnded.bind(this);
|
|
}
|
|
|
|
disconnect() {
|
|
if (this._source != null) {
|
|
this._source.disconnect();
|
|
}
|
|
}
|
|
|
|
// Just adapt the callback into a promise.
|
|
_decodeAudioData(buffer) {
|
|
return new Promise((resolve, reject) => {
|
|
this._context.decodeAudioData(buffer, resolve, reject);
|
|
});
|
|
}
|
|
|
|
async loadFile(file) {
|
|
this._setStatus(STATUS.WAITING);
|
|
const arrayBuffer = await readFileAsArrayBuffer(file);
|
|
this._buffer = await this._decodeAudioData(arrayBuffer);
|
|
if (this._autoPlay) {
|
|
this.play();
|
|
}
|
|
}
|
|
|
|
async loadUrl(url) {
|
|
this._setStatus(STATUS.WAITING);
|
|
const arrayBuffer = await readUrlAsArrayBuffer(url);
|
|
this._buffer = await this._decodeAudioData(arrayBuffer);
|
|
if (this._autoPlay) {
|
|
this.play();
|
|
}
|
|
}
|
|
|
|
play() {
|
|
switch (this._status) {
|
|
case STATUS.WAITING:
|
|
case STATUS.PLAYING:
|
|
case STATUS.STOPPED:
|
|
this._start(0);
|
|
break;
|
|
case STATUS.PAUSED:
|
|
invariant(
|
|
this._pausedAtTime != null,
|
|
"Status is paused, but there is no _pausedAtTime"
|
|
);
|
|
this._start(this._pausedAtTime);
|
|
break;
|
|
}
|
|
}
|
|
|
|
pause() {
|
|
this._pausedAtTime = this.getTimeElapsed();
|
|
this._silence(STATUS.PAUSED);
|
|
}
|
|
|
|
stop() {
|
|
this._silence(STATUS.STOPPED);
|
|
}
|
|
|
|
seekToTime(time) {
|
|
time = Math.min(time, this.getDuration());
|
|
time = Math.max(time, 0);
|
|
this._start(time);
|
|
}
|
|
|
|
getStatus() {
|
|
return this._status;
|
|
}
|
|
|
|
getDuration() {
|
|
if (this._buffer == null) {
|
|
return 0;
|
|
}
|
|
return this._buffer.duration;
|
|
}
|
|
|
|
getTimeElapsed() {
|
|
switch (this._status) {
|
|
case STATUS.PLAYING:
|
|
invariant(
|
|
this._startTime != null,
|
|
"status is playing, but there is no _startTime"
|
|
);
|
|
return this._context.currentTime - this._startTime;
|
|
case STATUS.STOPPED:
|
|
case STATUS.WAITING:
|
|
return 0;
|
|
case STATUS.PAUSED:
|
|
invariant(
|
|
this._pausedAtTime != null,
|
|
"Status is paused, but there is no _pausedAtTime"
|
|
);
|
|
return this._pausedAtTime;
|
|
}
|
|
throw new Error("Invalid status");
|
|
}
|
|
|
|
getNumberOfChannels() {
|
|
if (this._buffer == null) {
|
|
return null;
|
|
}
|
|
return this._buffer.numberOfChannels;
|
|
}
|
|
|
|
getSampleRate() {
|
|
if (this._buffer == null) {
|
|
return null;
|
|
}
|
|
return this._buffer.sampleRate;
|
|
}
|
|
|
|
getLoop() {
|
|
return this._loop;
|
|
}
|
|
|
|
setLoop(shouldLoop) {
|
|
this._loop = shouldLoop;
|
|
if (this._source) {
|
|
this._source.loop = this._loop;
|
|
}
|
|
}
|
|
|
|
setAutoPlay(shouldAutoPlay) {
|
|
this._autoPlay = shouldAutoPlay;
|
|
}
|
|
|
|
_start(position) {
|
|
if (this._buffer == null) {
|
|
return;
|
|
}
|
|
if (this._source) {
|
|
// Does this work?
|
|
this._source.disconnect();
|
|
this._source = null;
|
|
}
|
|
this._source = this._context.createBufferSource();
|
|
this._source.buffer = this._buffer;
|
|
this._source.loop = this._loop;
|
|
this._source.connect(this._destination);
|
|
this._source.onended = this._onEnded;
|
|
this._startTime = this._context.currentTime - position;
|
|
this._setStatus(STATUS.PLAYING);
|
|
this._source.start(0, position);
|
|
}
|
|
|
|
_silence(reason) {
|
|
if (!reason) {
|
|
throw new Error("Tried to silence the audio without a reason");
|
|
}
|
|
// We must always update the status before ending the audio, so that onEnded
|
|
// can distinguish between audio that got to the end of the track, and audio
|
|
// that the user stopped/paused
|
|
this._setStatus(reason);
|
|
if (this._source == null) {
|
|
return;
|
|
}
|
|
this._source.stop(0);
|
|
this._source = null;
|
|
}
|
|
|
|
_startMonitoringPosition() {
|
|
this._nextPositionUpdate = setTimeout(() => {
|
|
this.trigger("positionChange");
|
|
this._startMonitoringPosition();
|
|
}, 100);
|
|
}
|
|
|
|
_stopMonitoringPosition() {
|
|
clearTimeout(this._nextPositionUpdate);
|
|
}
|
|
|
|
_setStatus(status) {
|
|
this._status = status;
|
|
if (status === STATUS.PLAYING) {
|
|
this._startMonitoringPosition();
|
|
} else {
|
|
this._stopMonitoringPosition();
|
|
}
|
|
this.trigger("statusChange");
|
|
}
|
|
|
|
_onEnded() {
|
|
if (this._status === STATUS.PLAYING) {
|
|
this._setStatus(STATUS.STOPPED);
|
|
}
|
|
}
|
|
}
|
|
|
|
window.BufferSource = BufferSource;
|
|
//export default BufferSource;
|
|
})();
|