Import source: js/player
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
+220
@@ -0,0 +1,220 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const Player = {
|
||||||
|
audio: new Audio(),
|
||||||
|
ctx: null,
|
||||||
|
analyser: null,
|
||||||
|
freq: null,
|
||||||
|
queue: [],
|
||||||
|
index: -1,
|
||||||
|
dur: 0,
|
||||||
|
cur: 0,
|
||||||
|
playing: false,
|
||||||
|
endedFlag: false,
|
||||||
|
|
||||||
|
onState: null,
|
||||||
|
onTime: null,
|
||||||
|
onTrack: null,
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.volume = Store.volume;
|
||||||
|
this.shuffle = Store.shuffle;
|
||||||
|
this.repeat = Store.repeat;
|
||||||
|
this.audio.volume = this.volume;
|
||||||
|
this.audio.preload = "auto";
|
||||||
|
|
||||||
|
this.audio.addEventListener("loadedmetadata", () => {
|
||||||
|
this.dur = isFinite(this.audio.duration) ? this.audio.duration : 0;
|
||||||
|
this.emitTime();
|
||||||
|
});
|
||||||
|
this.audio.addEventListener("durationchange", () => {
|
||||||
|
this.dur = isFinite(this.audio.duration) ? this.audio.duration : this.dur;
|
||||||
|
this.emitTime();
|
||||||
|
});
|
||||||
|
this.audio.addEventListener("timeupdate", () => {
|
||||||
|
this.cur = this.audio.currentTime;
|
||||||
|
this.emitTime();
|
||||||
|
});
|
||||||
|
this.audio.addEventListener("play", () => {
|
||||||
|
this.playing = true;
|
||||||
|
if (this.onState) this.onState(true);
|
||||||
|
});
|
||||||
|
this.audio.addEventListener("pause", () => {
|
||||||
|
this.playing = false;
|
||||||
|
if (this.onState) this.onState(false);
|
||||||
|
});
|
||||||
|
this.audio.addEventListener("ended", () => this.onEnded());
|
||||||
|
this.audio.addEventListener("error", () => {
|
||||||
|
if (this.endedFlag) return;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
emitTime() {
|
||||||
|
if (this.onTime) this.onTime(this.cur, this.dur);
|
||||||
|
},
|
||||||
|
|
||||||
|
current() {
|
||||||
|
return this.queue[this.index] || null;
|
||||||
|
},
|
||||||
|
|
||||||
|
playContext(ids, start) {
|
||||||
|
if (!ids || !ids.length) return;
|
||||||
|
this.queue = ids.map((x) => (typeof x === "object" && x != null ? x : D.song(x))).filter(Boolean);
|
||||||
|
this.index = start || 0;
|
||||||
|
this.loadCurrent();
|
||||||
|
},
|
||||||
|
|
||||||
|
loadCurrent() {
|
||||||
|
const s = this.current();
|
||||||
|
if (!s) return;
|
||||||
|
this.endedFlag = false;
|
||||||
|
this.audio.src = s.audio;
|
||||||
|
this.audio.play().catch(() => {});
|
||||||
|
Store.pushHistory(s.id);
|
||||||
|
if (this.onTrack) this.onTrack(s);
|
||||||
|
},
|
||||||
|
|
||||||
|
togglePlay() {
|
||||||
|
if (!this.current()) {
|
||||||
|
this.playContext(D.topSongs(40).map((s) => s.id), 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.audio.paused) this.audio.play().catch(() => {});
|
||||||
|
else this.audio.pause();
|
||||||
|
},
|
||||||
|
|
||||||
|
next() {
|
||||||
|
if (!this.queue.length) return;
|
||||||
|
if (this.shuffle) {
|
||||||
|
if (this.queue.length < 2) return;
|
||||||
|
let i;
|
||||||
|
do {
|
||||||
|
i = Math.floor(Math.random() * this.queue.length);
|
||||||
|
} while (i === this.index);
|
||||||
|
this.index = i;
|
||||||
|
this.loadCurrent();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.index + 1 < this.queue.length) {
|
||||||
|
this.index++;
|
||||||
|
this.loadCurrent();
|
||||||
|
} else if (this.repeat === 1) {
|
||||||
|
this.index = 0;
|
||||||
|
this.loadCurrent();
|
||||||
|
} else {
|
||||||
|
this.audio.pause();
|
||||||
|
this.cur = 0;
|
||||||
|
this.dur = this.current() ? this.current().dur : 0;
|
||||||
|
this.emitTime();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
prev() {
|
||||||
|
if (!this.queue.length) return;
|
||||||
|
if (this.audio.currentTime > 3) {
|
||||||
|
this.seek(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.index - 1 >= 0) {
|
||||||
|
this.index--;
|
||||||
|
this.loadCurrent();
|
||||||
|
} else if (this.repeat === 1 && this.queue.length > 1) {
|
||||||
|
this.index = this.queue.length - 1;
|
||||||
|
this.loadCurrent();
|
||||||
|
} else {
|
||||||
|
this.seek(0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onEnded() {
|
||||||
|
if (this.repeat === 2) {
|
||||||
|
this.audio.currentTime = 0;
|
||||||
|
this.audio.play().catch(() => {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.next();
|
||||||
|
},
|
||||||
|
|
||||||
|
seek(t) {
|
||||||
|
if (!this.current()) return;
|
||||||
|
const clamp = Math.max(0, Math.min(t, this.dur || t));
|
||||||
|
this.audio.currentTime = clamp;
|
||||||
|
this.cur = clamp;
|
||||||
|
this.emitTime();
|
||||||
|
},
|
||||||
|
|
||||||
|
setVolume(v) {
|
||||||
|
this.volume = Math.max(0, Math.min(1, v));
|
||||||
|
this.audio.volume = this.volume;
|
||||||
|
Store.volume = this.volume;
|
||||||
|
Store.save();
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleMute() {
|
||||||
|
if (this.audio.volume > 0) {
|
||||||
|
this._lastVol = this.audio.volume;
|
||||||
|
this.setVolume(0);
|
||||||
|
} else {
|
||||||
|
this.setVolume(this._lastVol || 0.85);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleShuffle() {
|
||||||
|
this.shuffle = !this.shuffle;
|
||||||
|
Store.shuffle = this.shuffle;
|
||||||
|
Store.save();
|
||||||
|
},
|
||||||
|
|
||||||
|
cycleRepeat() {
|
||||||
|
this.repeat = (this.repeat + 1) % 3;
|
||||||
|
Store.repeat = this.repeat;
|
||||||
|
Store.save();
|
||||||
|
},
|
||||||
|
|
||||||
|
removeAt(i) {
|
||||||
|
if (i < 0 || i >= this.queue.length) return;
|
||||||
|
const removedCurrent = i === this.index;
|
||||||
|
this.queue.splice(i, 1);
|
||||||
|
if (!this.queue.length) {
|
||||||
|
this.audio.pause();
|
||||||
|
this.audio.removeAttribute("src");
|
||||||
|
this.index = -1;
|
||||||
|
this.cur = 0;
|
||||||
|
this.dur = 0;
|
||||||
|
this.emitTime();
|
||||||
|
if (this.onTrack) this.onTrack(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (removedCurrent) {
|
||||||
|
if (i >= this.queue.length) this.index = this.queue.length - 1;
|
||||||
|
else this.index = i;
|
||||||
|
this.loadCurrent();
|
||||||
|
} else if (i < this.index) {
|
||||||
|
this.index--;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
ensureGraph() {
|
||||||
|
if (this.ctx) return;
|
||||||
|
const Ctx = window.AudioContext || window.webkitAudioContext;
|
||||||
|
if (!Ctx) return;
|
||||||
|
try {
|
||||||
|
this.ctx = new Ctx();
|
||||||
|
const src = this.ctx.createMediaElementSource(this.audio);
|
||||||
|
this.analyser = this.ctx.createAnalyser();
|
||||||
|
this.analyser.fftSize = 256;
|
||||||
|
this.analyser.smoothingTimeConstant = 0.82;
|
||||||
|
src.connect(this.analyser);
|
||||||
|
this.analyser.connect(this.ctx.destination);
|
||||||
|
this.freq = new Uint8Array(this.analyser.frequencyBinCount);
|
||||||
|
} catch (e) {}
|
||||||
|
},
|
||||||
|
|
||||||
|
resumeGraph() {
|
||||||
|
if (this.ctx && this.ctx.state === "suspended") {
|
||||||
|
this.ctx.resume().catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Player.init();
|
||||||
Reference in New Issue
Block a user