|
@@ -18,6 +18,7 @@ import Crunker from "/src/utils/crunker"
|
|
|
import tickMp3 from "/src/assets/tick.wav"
|
|
|
import tockMp3 from "/src/assets/tock.wav"
|
|
|
import { metronomeData } from "/src/helpers/metronome";
|
|
|
+import { showToast } from "vant"
|
|
|
|
|
|
export const audioData = reactive({
|
|
|
songEle: null as HTMLAudioElement | null, // 原生
|
|
@@ -44,7 +45,10 @@ export const audioData = reactive({
|
|
|
mingSongGirlEle: null as HTMLAudioElement | null,
|
|
|
beatMingSongEle: null as HTMLAudioElement | null,
|
|
|
beatMingSongGirlEle: null as HTMLAudioElement | null
|
|
|
- }
|
|
|
+ },
|
|
|
+ combineIndex: -1, // 当前 播放的总谱音频索引
|
|
|
+ combineMusics: {} as Record<string, any>, // 音频 url
|
|
|
+ combineMusicEles:[] as {key:number, value:HTMLAudioElement, beatValue:HTMLAudioElement|null}[] // 存储的音频el 当大于4个时候删除
|
|
|
});
|
|
|
const midiRef = ref();
|
|
|
/** 播放或暂停 */
|
|
@@ -204,6 +208,141 @@ export const changeMingSongType = () =>{
|
|
|
audioData.songCollection.beatMingSongEle = mingSongType === 1 ? beatMingSongEle : beatMingSongGirlEle
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+const createAudio = (src?: string): Promise<HTMLAudioElement | null> => {
|
|
|
+ if(!src){
|
|
|
+ return Promise.resolve(null)
|
|
|
+ }
|
|
|
+ return new Promise((resolve) => {
|
|
|
+ const a = new Audio(src + '?v=' + Date.now());
|
|
|
+ a.onloadedmetadata = () => {
|
|
|
+ resolve(a);
|
|
|
+ };
|
|
|
+ a.onerror = () => {
|
|
|
+ resolve(null);
|
|
|
+ };
|
|
|
+ // 当未加载 资源之前 切换到其他浏览器标签,浏览器可能会禁止资源加载所以无法触发onloadedmetadata事件,导致一直在加载中,这里做个兼容
|
|
|
+ if (document.visibilityState === 'visible') {
|
|
|
+ a.load();
|
|
|
+ } else {
|
|
|
+ const onVisibilityChange = () => {
|
|
|
+ if (document.visibilityState === 'visible') {
|
|
|
+ document.removeEventListener('visibilitychange', onVisibilityChange);
|
|
|
+ a.load();
|
|
|
+ }
|
|
|
+ };
|
|
|
+ document.addEventListener('visibilitychange', onVisibilityChange);
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+// 合成节拍器资源
|
|
|
+const crunker = new Crunker()
|
|
|
+async function mergeBeatAudio(music?:string, accompany?:string){
|
|
|
+ let beatMusic, beatAccompany
|
|
|
+ if(!state.isMixBeat) {
|
|
|
+ return [beatMusic, beatAccompany]
|
|
|
+ }
|
|
|
+ console.time("音频合成时间")
|
|
|
+ try{
|
|
|
+ console.time("音频加载时间")
|
|
|
+ const [musicBuff, accompanyBuff, tickBuff, tockBuff] = await crunker.fetchAudio(music?`${music}?v=${Date.now()}`:undefined, accompany?`${accompany}?v=${Date.now()}`:undefined, tickMp3, tockMp3)
|
|
|
+ console.timeEnd("音频加载时间")
|
|
|
+ // 计算音频空白时间
|
|
|
+ const silenceDuration = musicBuff&&!state.isEvxml ? crunker.calculateSilenceDuration(musicBuff) : 0
|
|
|
+ const silenceBgDuration = accompanyBuff&&!state.isEvxml ? crunker.calculateSilenceDuration(accompanyBuff) : 0
|
|
|
+ console.log(`音频空白时间:${silenceDuration};${silenceBgDuration}`)
|
|
|
+ const beats:AudioBuffer[] = []
|
|
|
+ const beatsTime:number[] = []
|
|
|
+ const beatsBgTime:number[] = []
|
|
|
+ metronomeData.metroMeasure.map(measures=>{
|
|
|
+ measures.map((item:any)=>{
|
|
|
+ beats.push(item.isTick?tickBuff!:tockBuff!)
|
|
|
+ beatsTime.push(item.time + silenceDuration) // xml 计算的时候 加上空白的时间
|
|
|
+ beatsBgTime.push(item.time + silenceBgDuration) // xml 计算的时候 加上空白的时间 没有背景不赋值
|
|
|
+ })
|
|
|
+ })
|
|
|
+ console.time("音频合并时间")
|
|
|
+ const musicBuffMeg = musicBuff && crunker.mergeAudioBuffers([musicBuff,...beats],[0,...beatsTime])
|
|
|
+ const accompanyBuffMeg = accompanyBuff && crunker.mergeAudioBuffers([accompanyBuff,...beats],[0,...beatsBgTime])
|
|
|
+ console.timeEnd("音频合并时间")
|
|
|
+ console.time("音频audioDom生成时间")
|
|
|
+ beatMusic = musicBuffMeg && crunker.exportAudioElement(musicBuffMeg)
|
|
|
+ beatAccompany = accompanyBuffMeg && crunker.exportAudioElement(accompanyBuffMeg)
|
|
|
+ console.timeEnd("音频audioDom生成时间")
|
|
|
+ }catch(err){
|
|
|
+ console.log(err)
|
|
|
+ }
|
|
|
+ console.timeEnd("音频合成时间")
|
|
|
+ return [beatMusic, beatAccompany]
|
|
|
+}
|
|
|
+// 切换对应的声轨,并且配置当前的audio
|
|
|
+export async function changeCombineAudio (combineIndex: number){
|
|
|
+ // 重复点击的时候取消选中 原音
|
|
|
+ if(combineIndex === audioData.combineIndex){
|
|
|
+ audioData.combineIndex = -1
|
|
|
+ state.playSource = "background"
|
|
|
+ state.music = ""
|
|
|
+ // 当没有背景音文件的时候
|
|
|
+ if(!state.accompany) {
|
|
|
+ state.noMusicSource = true
|
|
|
+ }
|
|
|
+ return
|
|
|
+ }
|
|
|
+ state.loadingText = "音频资源加载中,请稍后…";
|
|
|
+ state.isLoading = true;
|
|
|
+ const musicUrl = audioData.combineMusics[combineIndex]
|
|
|
+ // 有就拿缓存,没有就加载
|
|
|
+ const cacheMusicIndex = audioData.combineMusicEles.findIndex(ele => {
|
|
|
+ return ele.key === combineIndex
|
|
|
+ })
|
|
|
+ const cacheMusic = audioData.combineMusicEles[cacheMusicIndex]
|
|
|
+ if(cacheMusic?.value){
|
|
|
+ audioData.songCollection.songEle = cacheMusic.value
|
|
|
+ audioData.songCollection.beatSongEle = cacheMusic.beatValue
|
|
|
+ // 使用缓存之后 当前数据位置向后偏移,删除缓存的时候以使用顺序位置
|
|
|
+ const itemMusic = audioData.combineMusicEles.splice(cacheMusicIndex, 1)
|
|
|
+ audioData.combineMusicEles.push(...itemMusic)
|
|
|
+ }else{
|
|
|
+ const music = await createAudio(musicUrl)
|
|
|
+ const [beatMusic] = await mergeBeatAudio(musicUrl)
|
|
|
+ // 当没有背景音的时候 需要绑定事件
|
|
|
+ if(!state.accompany){
|
|
|
+ if(music){
|
|
|
+ music.addEventListener("play", onPlay);
|
|
|
+ music.addEventListener("ended", onEnded);
|
|
|
+ }
|
|
|
+ if(beatMusic){
|
|
|
+ beatMusic.addEventListener("play", onPlay);
|
|
|
+ beatMusic.addEventListener("ended", onEnded);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ audioData.combineMusicEles.push({
|
|
|
+ key: combineIndex,
|
|
|
+ value: music!,
|
|
|
+ beatValue: beatMusic!
|
|
|
+ })
|
|
|
+ // 当大于4个数据的时候 删除掉最前面的数据
|
|
|
+ if(audioData.combineMusicEles.length > 4){
|
|
|
+ audioData.combineMusicEles.splice(0,1)
|
|
|
+ }
|
|
|
+ audioData.songCollection.songEle = music
|
|
|
+ audioData.songCollection.beatSongEle = beatMusic!
|
|
|
+ }
|
|
|
+ audioData.combineIndex = combineIndex
|
|
|
+ state.music = musicUrl
|
|
|
+ state.playSource = "music"
|
|
|
+ // 当没有背景音文件的时候
|
|
|
+ if(!state.accompany) {
|
|
|
+ state.noMusicSource = false
|
|
|
+ }
|
|
|
+ showToast({
|
|
|
+ message: "已开启原声",
|
|
|
+ position: "top",
|
|
|
+ className: "selectionToast",
|
|
|
+ });
|
|
|
+ state.isLoading = false;
|
|
|
+}
|
|
|
export default defineComponent({
|
|
|
name: "audio-list",
|
|
|
setup() {
|
|
@@ -238,33 +377,6 @@ export default defineComponent({
|
|
|
}
|
|
|
);
|
|
|
|
|
|
- const createAudio = (src?: string): Promise<HTMLAudioElement | null> => {
|
|
|
- if(!src){
|
|
|
- return Promise.resolve(null)
|
|
|
- }
|
|
|
- return new Promise((resolve) => {
|
|
|
- const a = new Audio(src + '?v=' + Date.now());
|
|
|
- a.onloadedmetadata = () => {
|
|
|
- resolve(a);
|
|
|
- };
|
|
|
- a.onerror = () => {
|
|
|
- resolve(null);
|
|
|
- };
|
|
|
- // 当未加载 资源之前 切换到其他浏览器标签,浏览器可能会禁止资源加载所以无法触发onloadedmetadata事件,导致一直在加载中,这里做个兼容
|
|
|
- if (document.visibilityState === 'visible') {
|
|
|
- a.load();
|
|
|
- } else {
|
|
|
- const onVisibilityChange = () => {
|
|
|
- if (document.visibilityState === 'visible') {
|
|
|
- document.removeEventListener('visibilitychange', onVisibilityChange);
|
|
|
- a.load();
|
|
|
- }
|
|
|
- };
|
|
|
- document.addEventListener('visibilitychange', onVisibilityChange);
|
|
|
- }
|
|
|
- });
|
|
|
- };
|
|
|
-
|
|
|
/**
|
|
|
* #11046
|
|
|
* 声音与圆点消失的节点不一致,可能原因是部分安卓手机没有立即播放,所以需要等待有音频进度返回时再播放节拍器
|
|
@@ -338,46 +450,6 @@ export default defineComponent({
|
|
|
function loadBeatAudio(){
|
|
|
return Promise.all([createAudio(state.beatSong.music), createAudio(state.beatSong.accompany), createAudio(state.beatSong.fanSong), createAudio(state.beatSong.banSong), createAudio(state.beatSong.mingSong), createAudio(state.beatSong.mingSongGirl)])
|
|
|
}
|
|
|
- // 合成节拍器资源
|
|
|
- async function mergeBeatAudio(){
|
|
|
- let beatMusic, beatAccompany
|
|
|
- if(!state.isMixBeat) {
|
|
|
- return [beatMusic, beatAccompany]
|
|
|
- }
|
|
|
- console.time("音频合成时间")
|
|
|
- try{
|
|
|
- const crunker = new Crunker()
|
|
|
- console.time("音频加载时间")
|
|
|
- const [musicBuff, accompanyBuff, tickBuff, tockBuff] = await crunker.fetchAudio(state.music?`${state.music}?v=${Date.now()}`:undefined, state.accompany?`${state.accompany}?v=${Date.now()}`:undefined, tickMp3, tockMp3)
|
|
|
- console.timeEnd("音频加载时间")
|
|
|
- // 计算音频空白时间
|
|
|
- const silenceDuration = musicBuff&&!state.isEvxml ? crunker.calculateSilenceDuration(musicBuff) : 0
|
|
|
- const silenceBgDuration = accompanyBuff&&!state.isEvxml ? crunker.calculateSilenceDuration(accompanyBuff) : 0
|
|
|
- console.log(`音频空白时间:${silenceDuration};${silenceBgDuration}`)
|
|
|
- const beats:AudioBuffer[] = []
|
|
|
- const beatsTime:number[] = []
|
|
|
- const beatsBgTime:number[] = []
|
|
|
- metronomeData.metroMeasure.map(measures=>{
|
|
|
- measures.map((item:any)=>{
|
|
|
- beats.push(item.isTick?tickBuff!:tockBuff!)
|
|
|
- beatsTime.push(item.time + silenceDuration) // xml 计算的时候 加上空白的时间
|
|
|
- beatsBgTime.push(item.time + silenceBgDuration) // xml 计算的时候 加上空白的时间 没有背景不赋值
|
|
|
- })
|
|
|
- })
|
|
|
- console.time("音频合并时间")
|
|
|
- const musicBuffMeg = musicBuff && crunker.mergeAudioBuffers([musicBuff,...beats],[0,...beatsTime])
|
|
|
- const accompanyBuffMeg = accompanyBuff && crunker.mergeAudioBuffers([accompanyBuff,...beats],[0,...beatsBgTime])
|
|
|
- console.timeEnd("音频合并时间")
|
|
|
- console.time("音频audioDom生成时间")
|
|
|
- beatMusic = musicBuffMeg && crunker.exportAudioElement(musicBuffMeg)
|
|
|
- beatAccompany = accompanyBuffMeg && crunker.exportAudioElement(accompanyBuffMeg)
|
|
|
- console.timeEnd("音频audioDom生成时间")
|
|
|
- }catch(err){
|
|
|
- console.log(err)
|
|
|
- }
|
|
|
- console.timeEnd("音频合成时间")
|
|
|
- return [beatMusic, beatAccompany]
|
|
|
- }
|
|
|
onMounted(async () => {
|
|
|
// 预览的时候不走音频加载逻辑
|
|
|
if(state.isPreView){
|
|
@@ -430,7 +502,7 @@ export default defineComponent({
|
|
|
// 处理带节拍器的音源
|
|
|
//const [beatMusic, beatAccompany, beatFanSong, beatBanSong, beatMingSong, beatMingSongGirl] = await loadBeatAudio()
|
|
|
// 客户端合成节拍器
|
|
|
- const [beatMusic, beatAccompany, beatFanSong, beatBanSong, beatMingSong, beatMingSongGirl] = await mergeBeatAudio()
|
|
|
+ const [beatMusic, beatAccompany, beatFanSong, beatBanSong, beatMingSong, beatMingSongGirl] = await mergeBeatAudio(state.music, state.accompany)
|
|
|
Object.assign(audioData.songCollection, {
|
|
|
beatSongEle:beatMusic,
|
|
|
beatBackgroundEle:beatAccompany,
|