239 lines
6.1 KiB
JavaScript
239 lines
6.1 KiB
JavaScript
"use strict";
|
|
var __defProp = Object.defineProperty;
|
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
var __publicField = (obj, key, value) => {
|
|
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
return value;
|
|
};
|
|
const common_vendor = require("../../common/vendor.js");
|
|
class AudioPool {
|
|
constructor(src, size, volume = 1) {
|
|
__publicField(this, "list", []);
|
|
__publicField(this, "cursor", 0);
|
|
__publicField(this, "warmedUp", false);
|
|
__publicField(this, "targetVolume", 1);
|
|
this.src = src;
|
|
this.size = size;
|
|
this.targetVolume = volume;
|
|
}
|
|
create() {
|
|
for (let i = 0; i < this.size; i++) {
|
|
const ctx = common_vendor.index.createInnerAudioContext();
|
|
ctx.src = this.src;
|
|
ctx.obeyMuteSwitch = false;
|
|
ctx.autoplay = false;
|
|
ctx.volume = this.targetVolume;
|
|
this.list.push(ctx);
|
|
}
|
|
}
|
|
setVolume(volume) {
|
|
this.targetVolume = Math.max(0, Math.min(1, volume));
|
|
this.list.forEach((ctx) => {
|
|
try {
|
|
ctx.volume = this.targetVolume;
|
|
} catch (_) {
|
|
}
|
|
});
|
|
}
|
|
/**
|
|
* 预热:短暂播放,让音频文件下载/解码到内存
|
|
* 真正首次 play 不会再卡冷启动
|
|
*/
|
|
warmUp() {
|
|
if (this.warmedUp)
|
|
return;
|
|
if (this.list.length === 0)
|
|
this.create();
|
|
this.list.forEach((ctx) => {
|
|
try {
|
|
ctx.volume = 0;
|
|
ctx.play();
|
|
setTimeout(() => {
|
|
try {
|
|
ctx.stop();
|
|
ctx.volume = this.targetVolume;
|
|
} catch (_) {
|
|
}
|
|
}, 60);
|
|
} catch (_) {
|
|
}
|
|
});
|
|
this.warmedUp = true;
|
|
}
|
|
play() {
|
|
if (this.list.length === 0) {
|
|
this.create();
|
|
this.warmUp();
|
|
}
|
|
const ctx = this.list[this.cursor];
|
|
this.cursor = (this.cursor + 1) % this.list.length;
|
|
try {
|
|
this.list.forEach((c) => {
|
|
try {
|
|
c.stop();
|
|
} catch (_) {
|
|
}
|
|
});
|
|
ctx.play();
|
|
} catch (_) {
|
|
try {
|
|
ctx.play();
|
|
} catch (__) {
|
|
}
|
|
}
|
|
}
|
|
destroy() {
|
|
this.list.forEach((ctx) => {
|
|
var _a;
|
|
try {
|
|
(_a = ctx.destroy) == null ? void 0 : _a.call(ctx);
|
|
} catch (_) {
|
|
}
|
|
});
|
|
this.list = [];
|
|
this.warmedUp = false;
|
|
}
|
|
}
|
|
const DEFAULT_CLICK_SRC = "https://gz-1349751149.cos.ap-guangzhou.myqcloud.com/uploads/file/20260526/20260526105128557ef8669.mp3";
|
|
function useMetronome(options = {}) {
|
|
const {
|
|
initialBpm = 80,
|
|
bpmMin = 40,
|
|
bpmMax = 240,
|
|
clickSrc = DEFAULT_CLICK_SRC,
|
|
accentSrc,
|
|
poolSize = 4,
|
|
volume = 1,
|
|
silent = false,
|
|
onBeat
|
|
} = options;
|
|
const bpm = common_vendor.ref(initialBpm);
|
|
const isPlaying = common_vendor.ref(false);
|
|
const beatIndex = common_vendor.ref(0);
|
|
const isAccent = common_vendor.ref(false);
|
|
const accentEveryRef = common_vendor.ref(options.accentEvery ?? 4);
|
|
const intervalMs = common_vendor.computed(() => 6e4 / bpm.value);
|
|
let clickPool = null;
|
|
let accentPool = null;
|
|
let timer = null;
|
|
let nextTickAt = 0;
|
|
const ensureAudio = () => {
|
|
if (silent)
|
|
return;
|
|
if (!clickPool) {
|
|
clickPool = new AudioPool(clickSrc, poolSize, volume);
|
|
clickPool.warmUp();
|
|
}
|
|
if (accentSrc && !accentPool) {
|
|
accentPool = new AudioPool(accentSrc, Math.max(2, Math.ceil(poolSize / 2)), volume);
|
|
accentPool.warmUp();
|
|
}
|
|
};
|
|
const playSound = (accent) => {
|
|
if (accent && accentPool) {
|
|
accentPool.play();
|
|
} else if (clickPool) {
|
|
clickPool.play();
|
|
}
|
|
};
|
|
const tick = () => {
|
|
if (!isPlaying.value)
|
|
return;
|
|
const every = Math.max(1, accentEveryRef.value);
|
|
const accent = beatIndex.value % every === 0;
|
|
isAccent.value = accent;
|
|
playSound(accent);
|
|
onBeat == null ? void 0 : onBeat(beatIndex.value, accent);
|
|
beatIndex.value++;
|
|
nextTickAt += intervalMs.value;
|
|
const nextDelay = Math.max(0, nextTickAt - Date.now());
|
|
timer = setTimeout(tick, nextDelay);
|
|
};
|
|
const start = () => {
|
|
if (isPlaying.value)
|
|
return;
|
|
ensureAudio();
|
|
isPlaying.value = true;
|
|
beatIndex.value = 0;
|
|
nextTickAt = Date.now();
|
|
tick();
|
|
};
|
|
const stop = () => {
|
|
isPlaying.value = false;
|
|
if (timer) {
|
|
clearTimeout(timer);
|
|
timer = null;
|
|
}
|
|
};
|
|
const setBpm = (val) => {
|
|
bpm.value = Math.max(bpmMin, Math.min(bpmMax, Math.round(val)));
|
|
};
|
|
const setAccentEvery = (n) => {
|
|
accentEveryRef.value = Math.max(1, Math.min(16, Math.round(n)));
|
|
beatIndex.value = 0;
|
|
};
|
|
const updateAudioSrc = (newClickSrc, newAccentSrc) => {
|
|
const wasPlaying = isPlaying.value;
|
|
if (wasPlaying)
|
|
stop();
|
|
clickPool == null ? void 0 : clickPool.destroy();
|
|
accentPool == null ? void 0 : accentPool.destroy();
|
|
clickPool = null;
|
|
accentPool = null;
|
|
clickPool = new AudioPool(newClickSrc, poolSize);
|
|
if (newAccentSrc) {
|
|
accentPool = new AudioPool(newAccentSrc, Math.max(2, Math.ceil(poolSize / 2)));
|
|
}
|
|
const previewCtx = common_vendor.index.createInnerAudioContext();
|
|
previewCtx.src = newClickSrc;
|
|
previewCtx.obeyMuteSwitch = false;
|
|
previewCtx.volume = 0.4;
|
|
try {
|
|
previewCtx.play();
|
|
setTimeout(() => {
|
|
var _a;
|
|
try {
|
|
(_a = previewCtx.destroy) == null ? void 0 : _a.call(previewCtx);
|
|
} catch (_) {
|
|
}
|
|
}, 500);
|
|
} catch (_) {
|
|
}
|
|
if (wasPlaying)
|
|
start();
|
|
};
|
|
const preload = () => {
|
|
try {
|
|
common_vendor.index.setInnerAudioOption({
|
|
obeyMuteSwitch: false,
|
|
mixWithOther: true
|
|
});
|
|
} catch (_) {
|
|
}
|
|
ensureAudio();
|
|
};
|
|
common_vendor.onUnmounted(() => {
|
|
stop();
|
|
clickPool == null ? void 0 : clickPool.destroy();
|
|
accentPool == null ? void 0 : accentPool.destroy();
|
|
clickPool = null;
|
|
accentPool = null;
|
|
});
|
|
return {
|
|
bpm,
|
|
isPlaying,
|
|
beatIndex,
|
|
isAccent,
|
|
intervalMs,
|
|
accentEvery: accentEveryRef,
|
|
start,
|
|
stop,
|
|
setBpm,
|
|
setAccentEvery,
|
|
updateAudioSrc,
|
|
preload
|
|
};
|
|
}
|
|
exports.useMetronome = useMetronome;
|
|
//# sourceMappingURL=../../../.sourcemap/mp-weixin/training/hooks/useMetronome.js.map
|