| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139 |
- import { defineComponent, reactive, onMounted } from "vue";
- import tockAndTick from "/src/constant/tockAndTick.json";
- import { Howl } from "howler";
- import { Popup } from "vant";
- import styles from "./index.module.less";
- import state from "/src/state";
- import { browser } from "/src/utils/index";
- import tickWav from "/src/assets/tick.mp3";
- import tockWav from "/src/assets/tock.mp3";
- const browserInfo = browser();
- export const tickData = reactive({
- list: [] as number[],
- len: 0,
- tickEnd: false,
- /** 节拍器时间 */
- beatLengthInMilliseconds: 0,
- state: "",
- source1: "" as unknown as Howl,
- source2: "" as unknown as Howl,
- index: 0,
- show: false,
- });
- const handlePlay = (i: number, source: any | null) => {
- return new Promise((resolve) => {
- setTimeout(() => {
- if (tickData.tickEnd) {
- resolve(i)
- return
- };
- tickData.index++;
- if (source) {
- const beatVolume = state.setting.beatVolume / 100
- source.volume = beatVolume;
- if (source.volume <= 0) {
- source.muted = true
- } else {
- source.muted = false
- }
- source.play();
- }
- resolve(i);
- }, tickData.beatLengthInMilliseconds);
- });
- };
- // HTMLAudioElement 音频
- const audioData = reactive({
- tick: null as unknown as HTMLAudioElement,
- tock: null as unknown as HTMLAudioElement,
- });
- const createAudio = (src: string): Promise<HTMLAudioElement | null> => {
- return new Promise((resolve) => {
- const a = new Audio(src + '?v=' + Date.now());
- a.load();
- a.onloadedmetadata = () => {
- resolve(a);
- };
- a.onerror = () => {
- resolve(null);
- };
- });
- };
- /** 设置节拍器
- * @param beatLengthInMilliseconds 节拍间隔时间
- * @param beat 节拍数
- */
- export const handleInitTick = (beatLengthInMilliseconds: number, beat: number) => {
- tickData.state = "";
- tickData.beatLengthInMilliseconds = beatLengthInMilliseconds
- tickData.len = beat;
- };
- /** 开始节拍器 */
- export const handleStartTick = async () => {
- tickData.show = true;
- tickData.tickEnd = false;
- if (tickData.state !== "ok") {
- tickData.source1 = new Howl({
- src: tockAndTick.tick,
- // 如果是ios手机,需要强制使用audio,不然部分系统版本第一次播放没有声音
- html5: browserInfo.ios,
- });
- tickData.source2 = new Howl({
- src: tockAndTick.tock,
- });
- tickData.state = "ok";
- }
- tickData.index = 0;
- tickData.beatLengthInMilliseconds = (60 / state.speed) * 1000;
- for(let i = 0; i <= tickData.len; i++){
- // 提前结束, 直接放回false
- if (tickData.tickEnd) return false;
- // Howl 插件播放音频
- // const source = i === 0 ? tickData.source1 : i === tickData.len ? null : tickData.source2;
- // Audio 标签播放音频
- const source = i === 0 ? audioData.tick : i === tickData.len ? null : audioData.tock;
- await handlePlay(i, source)
- }
- tickData.show = false;
- return true
- };
- export default defineComponent({
- name: "metronome",
- setup() {
- /** 手动关闭 */
- const handleClose = () => {
- tickData.tickEnd = true
- };
- onMounted(() => {
- Promise.all([createAudio(tickWav), createAudio(tockWav)]).then(
- ([tick, tock]) => {
- if (tick) {
- audioData.tick = tick;
- }
- if (tock) {
- audioData.tock = tock;
- }
- }
- );
- });
- return () => (
- <Popup class={[styles.popup, 'normal-close']} v-model:show={tickData.show} closeable onClickCloseIcon={handleClose}>
- <div class={styles.dots}>
- {Array(tickData.len)
- .fill(0)
- .map((n, i) => (
- <div class={[styles.dot, tickData.index > i && styles.active, tickData.index > i && i === 0 && styles.one]}></div>
- ))}
- </div>
- </Popup>
- );
- },
- });
|