172 lines
4.7 KiB
JavaScript
172 lines
4.7 KiB
JavaScript
import { onMounted, onUnmounted, ref, toValue, watch } from "vue";
|
|
|
|
//#region src/client/composables/audio.ts
|
|
const mimeTypes = {
|
|
"audio/flac": ["flac", "fla"],
|
|
"audio/mpeg": ["mp3", "mpga"],
|
|
"audio/mp4": ["mp4", "m4a"],
|
|
"audio/ogg": ["ogg", "oga"],
|
|
"audio/aac": ["aac", "adts"],
|
|
"audio/x-ms-wma": ["wma"],
|
|
"audio/x-aiff": [
|
|
"aiff",
|
|
"aif",
|
|
"aifc"
|
|
],
|
|
"audio/webm": ["webm"]
|
|
};
|
|
const playerList = [];
|
|
function useAudioPlayer(source, options = {}) {
|
|
let player = null;
|
|
let unknownSupport = false;
|
|
const isSupported = ref(false);
|
|
const loaded = ref(false);
|
|
const paused = ref(true);
|
|
const currentTime = ref(0);
|
|
const duration = ref(0);
|
|
const volume = ref(1);
|
|
function initialize() {
|
|
player = document.createElement("audio");
|
|
player.className = "audio-player";
|
|
player.style.display = "none";
|
|
player.preload = options.autoplay ? "auto" : "none";
|
|
player.autoplay = options.autoplay ?? false;
|
|
document.body.appendChild(player);
|
|
playerList.push(player);
|
|
player.onloadedmetadata = () => {
|
|
duration.value = player.duration;
|
|
currentTime.value = player.currentTime;
|
|
volume.value = player.volume;
|
|
loaded.value = true;
|
|
};
|
|
player.oncanplay = (...args) => {
|
|
loaded.value = true;
|
|
if (unknownSupport) isSupported.value = true;
|
|
options.oncanplay?.bind(player)(...args);
|
|
};
|
|
player.onplay = (...args) => {
|
|
paused.value = false;
|
|
options.onplay?.bind(player)(...args);
|
|
};
|
|
player.onpause = (...args) => {
|
|
paused.value = true;
|
|
options.onpause?.bind(player)(...args);
|
|
};
|
|
player.ontimeupdate = () => {
|
|
if (isValidDuration(player.duration)) {
|
|
const lastBufferTime = getLastBufferedTime();
|
|
if (lastBufferTime <= player.duration) {
|
|
options.ontimeupdate?.bind(player)(lastBufferTime);
|
|
currentTime.value = lastBufferTime;
|
|
options.onprogress?.bind(player)(lastBufferTime, player.duration);
|
|
}
|
|
}
|
|
};
|
|
player.onvolumechange = () => {
|
|
volume.value = player.volume;
|
|
options.onvolume?.bind(player)(player.volume);
|
|
};
|
|
player.onended = (...args) => {
|
|
paused.value = true;
|
|
options.onend?.bind(player)(...args);
|
|
};
|
|
player.onplaying = options.onplaying;
|
|
player.onload = options.onload;
|
|
player.onerror = options.onerror;
|
|
player.onseeked = options.onseeked;
|
|
player.oncanplaythrough = options.oncanplaythrough;
|
|
player.onwaiting = options.onwaiting;
|
|
isSupported.value = isSupportType();
|
|
player.src = toValue(source);
|
|
player.load();
|
|
}
|
|
function isSupportType() {
|
|
if (!player) return false;
|
|
let type = toValue(options.type);
|
|
if (!type) {
|
|
const ext = toValue(source).split(".").pop() || "";
|
|
type = Object.keys(mimeTypes).filter((type$1) => mimeTypes[type$1].includes(ext))[0];
|
|
}
|
|
if (!type) {
|
|
unknownSupport = true;
|
|
return false;
|
|
}
|
|
const isSupported$1 = player.canPlayType(type) !== "";
|
|
if (!isSupported$1) console.warn(`The specified type "${type}" is not supported by the browser.`);
|
|
return isSupported$1;
|
|
}
|
|
function getBufferedRanges() {
|
|
if (!player) return [];
|
|
const ranges = [];
|
|
const seekable = player.buffered || [];
|
|
const offset = 0;
|
|
for (let i = 0, length = seekable.length; i < length; i++) {
|
|
let start = seekable.start(i);
|
|
let end = seekable.end(i);
|
|
if (!isValidDuration(start)) start = 0;
|
|
if (!isValidDuration(end)) {
|
|
end = 0;
|
|
continue;
|
|
}
|
|
ranges.push({
|
|
start: start + offset,
|
|
end: end + offset
|
|
});
|
|
}
|
|
return ranges;
|
|
}
|
|
function getLastBufferedTime() {
|
|
const bufferedRanges = getBufferedRanges();
|
|
if (!bufferedRanges.length) return 0;
|
|
const buff = bufferedRanges.find((buff$1) => buff$1.start < player.currentTime && buff$1.end > player.currentTime);
|
|
if (buff) return buff.end;
|
|
return bufferedRanges[bufferedRanges.length - 1].end;
|
|
}
|
|
function isValidDuration(duration$1) {
|
|
if (duration$1 && !Number.isNaN(duration$1) && duration$1 !== Number.POSITIVE_INFINITY && duration$1 !== Number.NEGATIVE_INFINITY) return true;
|
|
return false;
|
|
}
|
|
function destroy() {
|
|
player?.pause();
|
|
player?.remove();
|
|
playerList.splice(playerList.indexOf(player), 1);
|
|
player = null;
|
|
}
|
|
onMounted(() => {
|
|
initialize();
|
|
watch([source, options.type], () => {
|
|
destroy();
|
|
loaded.value = false;
|
|
paused.value = true;
|
|
currentTime.value = 0;
|
|
duration.value = 0;
|
|
initialize();
|
|
});
|
|
});
|
|
onUnmounted(() => destroy());
|
|
return {
|
|
isSupported,
|
|
paused,
|
|
loaded,
|
|
currentTime,
|
|
duration,
|
|
player,
|
|
destroy,
|
|
play: () => {
|
|
if (options.mutex ?? true) {
|
|
for (const p of playerList) if (p !== player) p.pause();
|
|
}
|
|
player?.play();
|
|
},
|
|
pause: () => player?.pause(),
|
|
seek(time) {
|
|
if (player) player.currentTime = time;
|
|
},
|
|
setVolume(volume$1) {
|
|
if (player) player.volume = Math.min(1, Math.max(0, volume$1));
|
|
}
|
|
};
|
|
}
|
|
|
|
//#endregion
|
|
export { useAudioPlayer }; |