소스 검색

feat: 音频合成改为按需合成

TIANYONG 2 주 전
부모
커밋
9d589a8966

+ 1 - 1
osmd-extended

@@ -1 +1 @@
-Subproject commit 7c412c691b06b746fcedb430ed177f65e83d9474
+Subproject commit 6b139a79b2825dd9e4621336e29f5db52c771092

+ 10 - 7
src/page-instrument/header-top/index.tsx

@@ -33,6 +33,7 @@ import { isMusicList, musicListShow } from "../component/the-music-list";
 import { EvaluatingDriver, FollowDriver, PractiseDriver } from "../custom-plugins/guide-driver";
 import { fingerRef } from "/src/page-instrument/view-detail/index"
 import { permissionPopup } from "/src/page-instrument/component/vip/permission";
+import { handleLoadBeatMusic } from "/src/view/audio-list"
 
 const ModeView = defineAsyncComponent(() =>
   import('./modeView')
@@ -156,7 +157,7 @@ let isClickMode = false;
  * @param oldPlaySource  没改变之前的播放类型
  * @param isforceReset   是否强制刷新播放状态 模式times时值改变时候也刷新
  */
-export function handlerModeChange(oldPlayType: "play" | "sing", oldPlaySource: IPlayState, isforceReset?: boolean) {
+export async function handlerModeChange(oldPlayType: "play" | "sing", oldPlaySource: IPlayState, isforceReset?: boolean) {
   const isModeChange = modeChangeHandleTimes(oldPlayType, oldPlaySource);
   // 没有切换的时候 不处理下面的
   if (isModeChange) {
@@ -171,6 +172,8 @@ export function handlerModeChange(oldPlayType: "play" | "sing", oldPlaySource: I
     // 隐藏重播按钮
     resetBtn && (resetBtn.value.display = false);
   }
+  // 节拍器音频加载
+  await handleLoadBeatMusic()
   // 当模式改变的时候  放在这里是因为需要等谱面加载完成之后再提示(点击按钮模式切换才提示)
   if (isClickMode) {
     showToast({
@@ -410,14 +413,14 @@ export default defineComponent({
       // 总谱渲染在播放过程中 不能切换 
       if(state.isCombineRender && state.playState === "play") return { display: true, disabled: true } 
       if (!state.isAppPlay) {
+        // 播放过程中不能切换
+        if (state.playState === "play") {
+          return { display: true, disabled: true };
+        }
         if (state.playType === "play") {
           // 原声, 伴奏 少一个,就不能切换
           if (state.music && state.accompany) return { display: true, disabled: false };
         } else {
-          // 播放过程中不能切换
-          if (state.playState === "play") {
-            return { display: true, disabled: true };
-          }
           // 范唱
           let index = 0;
           state.fanSong && index++;
@@ -844,7 +847,7 @@ export default defineComponent({
               id={state.platform === IPlatform.PC ? "teacherTop-1" : "studnetT-1"}
               style={{ display: originBtn.value.display ? "" : "none" }}
               class={["driver-3", styles.btn, originBtn.value.disabled && styles.disabled, state.playType === "play" ? styles.playSource : styles.songSource, state.playSource === "background" ? styles.backgroundSongAct : styles.musicSongAct]}
-              onClick={() => {
+              onClick={async () => {
                 const oldPlayType = state.playType;
                 const oldPlaySource = state.playSource;
                 if (state.playType === "play") {
@@ -858,7 +861,7 @@ export default defineComponent({
                     state.playSource = state.fanSong ? "music" : "background";
                   }
                 }
-                handlerModeChange(oldPlayType, oldPlaySource);
+                await handlerModeChange(oldPlayType, oldPlaySource);
                 // 总谱 并且开启了单个声轨音频时候
                 if(state.isCombineRender && state.playSource === "background") {
                   audioData.combineIndex = -1

+ 4 - 1
src/page-instrument/header-top/settting/index.tsx

@@ -17,6 +17,7 @@ import Dragbom from "/src/view/plugins/useDrag/dragbom";
 import { storeData } from "/src/store";
 import { getGuidance, setGuidance } from "../../custom-plugins/guide-page/api";
 import { metronomeData } from "/src/helpers/metronome";
+import { handleLoadBeatMusic } from "/src/view/audio-list"
 
 export default defineComponent({
 	name: "settting",
@@ -140,7 +141,7 @@ export default defineComponent({
                             </div>
                         </div>
                         {
-                            state.modeType === 'practise' && state.playType === "sing" && state.mingSong && state.mingSongGirl &&
+                            state.modeType === 'practise' && state.playSource === "mingSong" && state.mingSong && state.mingSongGirl &&
                             <div class={styles.cellBox}>
                                 <div class={styles.tit}>唱名类型</div>
                                 <div class={styles.radioBox}>
@@ -151,6 +152,8 @@ export default defineComponent({
                                                     return
                                                 }
                                                 audioData.mingSongType = item.value as 0|1
+                                                // 加载节拍器音频
+                                                handleLoadBeatMusic()
                                                 changeMingSongType()
                                             } }>{item.name}</div>
                                         })

+ 4 - 0
src/page-instrument/header-top/speed/index.tsx

@@ -9,6 +9,7 @@ import { getQuery } from "/src/utils/queryString";
 import { api_createMusicPlayer, api_updateMusicPlayer, api_checkSocketStatus } from "/src/helpers/communication";
 import { storeData } from "/src/store";
 import { data as workData } from "/src/page-instrument/custom-plugins/work-index";
+import { handleLoadBeatMusic } from "/src/view/audio-list"
 
 export default defineComponent({
 	name: "speed",
@@ -63,7 +64,10 @@ export default defineComponent({
 		})
 		// 切换节拍器
 		const toggleSwitch = async (res: any) => {
+			switchLoading.value = true;
 			metronomeDisable.value = res;
+			await handleLoadBeatMusic()
+			switchLoading.value = false;
 			return
 			switchLoading.value = true;
 			try {

+ 2 - 2
src/state.ts

@@ -1501,7 +1501,7 @@ const getMusicInfo = async (res: any) => {
   downloadXmlStr.value = xmlString //给musice-score 赋值xmlString 以免加载2次
   const tracks = xmlToTracks(xmlString) //获取声轨列表
   // 是否显示节拍器  (管乐迷 默认显示节拍器)
-  //state.isMixBeat = res.data?.isMixBeat  
+  state.isMixBeat = res.data?.isMixBeat  
   /* 设置partIndex */
   let partIndexs = query["part-index"] ? query["part-index"].split(",") : ["-1"] // -1为partIndex没有值的时候
   // 如果传入的是part-name,需要将part-name转换成part-index
@@ -1715,7 +1715,7 @@ function initMusicSource(data: any, tracks: string[], partIndex: number, workRec
   }
    /*  目前 管乐迷没有用到 后台生成的节拍器 */
   // 当使用节拍器的时候才加载节拍器音频
-  if(state.isMixBeat && false) {
+  if(state.isMixBeat) {
     Object.assign(state.beatSong, {
       music: musicObj?.audioBeatMixUrl,
       accompany: accompanyObj?.audioBeatMixUrl,

+ 118 - 23
src/view/audio-list/index.tsx

@@ -237,45 +237,135 @@ const createAudio = (src?: string): Promise<HTMLAudioElement | null> => {
 };
 
 // 合成节拍器资源
-const crunker = new Crunker()
-async function mergeBeatAudio(music?:string, accompany?:string){
-	let beatMusic, beatAccompany
+let CrunkerInstance: Crunker
+async function mergeBeatAudio(music?:string){
+	let beatMusic
 	if(!state.isMixBeat) {
-		return [beatMusic, beatAccompany]
+		return beatMusic
+	}
+	if(!music){
+		return beatMusic
 	}
 	console.time("音频合成时间")
 	try{
+		/* 音频合成 */
+		if(!CrunkerInstance){
+			CrunkerInstance = new Crunker()
+		}
 		console.time("音频加载时间")
-		const [musicBuff, accompanyBuff, tickBuff, tockBuff] = await crunker.fetchAudio(music?`${music}?v=${Date.now()}`:undefined, accompany?`${accompany}?v=${Date.now()}`:undefined, tickMp3, tockMp3)
+		const [musicBuff, tickBuff, tockBuff] = await CrunkerInstance.fetchAudio(music?`${music}`: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 silenceDuration = musicBuff&&!state.isEvxml ? CrunkerInstance.calculateSilenceDuration(musicBuff) : 0
+		console.log(`音频空白时间:${silenceDuration}`)
 		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])
+		const musicBuffMeg = musicBuff && CrunkerInstance.mergeAudioBuffers([musicBuff,...beats],[0,...beatsTime])
 		console.timeEnd("音频合并时间")
 		console.time("音频audioDom生成时间")
-		beatMusic = musicBuffMeg && crunker.exportAudioElement(musicBuffMeg)
-		beatAccompany = accompanyBuffMeg && crunker.exportAudioElement(accompanyBuffMeg)
+		beatMusic = musicBuffMeg && CrunkerInstance.exportAudioElement(musicBuffMeg)
 		console.timeEnd("音频audioDom生成时间")
 	}catch(err){
 		console.log(err)
 	}
 	console.timeEnd("音频合成时间")
-	return [beatMusic, beatAccompany]
+	return beatMusic
 }
+
+// 处理加载节拍器音频
+export const handleLoadBeatMusic = async () => {
+	if(metronomeData.disable) {
+		return
+	}
+	const playType = state.playType
+	const playSource = state.playSource
+	const mingSongType = audioData.mingSongType
+	// 当前模式下 如果已经有合成音频了,就不走合成逻辑了
+	let isBeatMusic = false
+	let currentMusic  //当前的音频
+	const beatPlayObj = {
+		"play_music":"beatSongEle",
+		"play_background":"beatBackgroundEle",
+		"sing_music":"beatFanSongEle",
+		"sing_background":"beatBanSongEle"
+	}
+	const playObj = {
+		"play_music":"music",
+		"play_background":"accompany",
+		"sing_music":"fanSong",
+		"sing_background":"banSong",
+	}
+	if(playSource === "mingSong") {
+		// 当男声女声都有的时候 才区分
+		if(state.mingSong && state.mingSongGirl){
+			isBeatMusic = mingSongType === 1 ? !!audioData.mingSongTypeCollection.beatMingSongEle : !!audioData.mingSongTypeCollection.beatMingSongGirlEle
+			currentMusic = mingSongType === 1 ? state.mingSong : state.mingSongGirl
+		}else{
+			isBeatMusic = !!audioData.mingSongTypeCollection.beatMingSongEle
+			currentMusic = state.mingSong
+		}
+	}else{
+		// @ts-ignore
+		isBeatMusic = !!audioData.songCollection[beatPlayObj[`${playType}_${playSource}`]]
+		// @ts-ignore
+		currentMusic = state[playObj[`${playType}_${playSource}`]]
+	}
+	if(isBeatMusic || !currentMusic){
+		return
+	}
+	state.loadingText = "资源加载中,请稍后…"
+	state.isLoading = true
+	const musicAudio = await mergeBeatAudio(currentMusic) as any
+	const playEleObj = {
+		"play_music":"beatSongEle",
+		"play_background":"beatBackgroundEle",
+		"sing_music":"beatFanSongEle",
+		"sing_background":"beatBanSongEle"
+	}
+	// 给音频赋值
+	if(playSource === "mingSong"){
+		// 当男声女声都有的时候 才区分
+		if(state.mingSong && state.mingSongGirl){
+			if(mingSongType === 1) {
+				audioData.mingSongTypeCollection.beatMingSongEle = musicAudio
+			}else {
+				audioData.mingSongTypeCollection.beatMingSongGirlEle = musicAudio
+			}
+		}else{
+			audioData.songCollection.beatMingSongEle = musicAudio
+			audioData.mingSongTypeCollection.beatMingSongEle = musicAudio
+		}
+		if(musicAudio){
+			musicAudio.addEventListener("play", onPlay);
+			musicAudio.addEventListener("ended", onEnded);
+		}
+		changeMingSongType()
+	}else{
+		if(playType === "play" && !audioData.songCollection.beatSongEle && !audioData.songCollection.beatBackgroundEle){
+			if(musicAudio){
+				musicAudio.addEventListener("play", onPlay);
+				musicAudio.addEventListener("ended", onEnded);
+			}
+		}		
+		if(playType === "sing" && !audioData.songCollection.beatFanSongEle && !audioData.songCollection.beatBanSongEle){
+			if(musicAudio){
+				musicAudio.addEventListener("play", onPlay);
+				musicAudio.addEventListener("ended", onEnded);
+			}
+		}
+		// @ts-ignore
+		audioData.songCollection[playEleObj[`${playType}_${playSource}`]] = musicAudio
+	}
+	state.isLoading = false
+}
+
 // 切换对应的声轨,并且配置当前的audio
 export async function changeCombineAudio (combineIndex: number){
 	// 重复点击的时候取消选中 原音
@@ -283,6 +373,8 @@ export async function changeCombineAudio (combineIndex: number){
 		audioData.combineIndex = -1
 		state.playSource = "background"
 		state.music = ""
+		// 当开启节拍器的时候,切为伴奏的时候合成节拍器1
+		await handleLoadBeatMusic()		
 		// 当没有背景音文件的时候
 		if(!state.accompany) {
 			state.noMusicSource = true
@@ -305,18 +397,21 @@ export async function changeCombineAudio (combineIndex: number){
 		audioData.combineMusicEles.push(...itemMusic)
 	}else{
 		const music = await createAudio(musicUrl)
-		const [beatMusic] = await mergeBeatAudio(musicUrl)
+		const beatMusic = await mergeBeatAudio(musicUrl)
 		// 当没有背景音的时候 需要绑定事件
-		if(!state.accompany){
+		if(!audioData.songCollection.backgroundEle){
 			if(music){
 				music.addEventListener("play", onPlay);
 				music.addEventListener("ended", onEnded);
 			}			
-			if(beatMusic){
-				beatMusic.addEventListener("play", onPlay);
-				beatMusic.addEventListener("ended", onEnded);
-			}
 		}
+		// 取消掉背景音绑定的时候,然后给当前原音节拍音频绑定事件,这样防止没有背景节拍的时候,能给
+		if(beatMusic){
+			audioData.songCollection.beatBackgroundEle?.removeEventListener("play", onPlay)
+			audioData.songCollection.beatBackgroundEle?.removeEventListener("ended", onEnded)
+			beatMusic.addEventListener("play", onPlay);
+			beatMusic.addEventListener("ended", onEnded);
+		}		
 		audioData.combineMusicEles.push({
 			key: combineIndex,
 			value: music!,
@@ -501,7 +596,7 @@ export default defineComponent({
 				}
 				// 处理带节拍器的音源
 				//const [beatMusic, beatAccompany, beatFanSong, beatBanSong, beatMingSong, beatMingSongGirl] = await loadBeatAudio()
-				// 客户端合成节拍器
+				/*// 客户端合成节拍器
 				const [beatMusic, beatAccompany, beatFanSong, beatBanSong, beatMingSong, beatMingSongGirl] = await mergeBeatAudio(state.music, state.accompany)
 				Object.assign(audioData.songCollection, {
 					beatSongEle:beatMusic,
@@ -539,7 +634,7 @@ export default defineComponent({
 				if(beatMingSongGirl){
 					beatMingSongGirl.addEventListener("play", onPlay);
 					beatMingSongGirl.addEventListener("ended", onEnded);
-				}
+				} */
 				// 给男声女声赋值
 				const userGender = storeData.user.gender
 				// 当不为null 和undefined的时候 取userGender的值