瀏覽代碼

Merge branch 'feature-tianyong-newVersion' of http://git.dayaedu.com/liushengqiang/music-score into ktyq-online-new

tianyong 1 月之前
父節點
當前提交
0d4a5c0e5f

+ 1 - 1
osmd-extended

@@ -1 +1 @@
-Subproject commit 036466bd82deff8603e510b95888d89010b38aa0
+Subproject commit bae380f80800211a0585d82b88faaf34d145836e

+ 5 - 2
src/helpers/formateMusic.ts

@@ -35,7 +35,7 @@ export const getFixTime = (speed: number) => {
 	const duration: any = getDuration(state.osmd as unknown as OpenSheetMusicDisplay);
 	let numerator = duration.numerator || 0;
 	let denominator = duration.denominator || 4;
-	const beatUnit = duration.beatUnit || "quarter";
+	const beatUnit = "quarter";
 	// if (state.repeatedBeats) {
 	// 	// 音频制作问题仅2拍不重复
 	// 	numerator = numerator === 2 ? 4 : numerator;
@@ -965,6 +965,7 @@ export const formatXML = (xml: string, xmlUrl?: string, resourceType?: string):
 				// TODO:删除妙极客曲子无意义的words
 				// wordArr?.push(word?.textContent)
 				if (word?.textContent && reg.test(word?.textContent) && word?.parentNode?.parentNode) {
+					// console.log('测试',1,measure)
 					measure.removeChild(word.parentNode.parentNode);
 					// deleteWordArr?.push(word?.textContent)
 				}
@@ -1558,7 +1559,7 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 			 * 曲子:1795013295024062466(春暖花开),如果音符有times信息,休止符没有times信息,此种规则是认为休止符不参与时值计算的,需要过滤掉该休止符
 			 */
 			let evNoteStartTime = 0, evNoteEndTime = 0;
-			if (state.isEvxml && note?.noteTimeInfo?.length === 0 && state.xmlHasTimes ) {
+			if (state.isEvxml && note?.noteTimeInfo?.length === 0 && state.xmlHasTimes && !state.isCustomNoteTime) {
 				// 找出这个音符前面音符的结束时间
 				let preNoteTImes = allNotes[allNotes.length - 1]?.endtime*1000
 				if(!preNoteTImes){
@@ -1980,6 +1981,8 @@ const analyzeEvxml = (xmlParse: any, xmlUrl?: string) => {
 	const denNum2 = xmlParse.getElementsByTagName("timegap")[0]?.getElementsByTagName("values")[0]?.getElementsByTagName("item")[1]?.getAttribute('den');
 	const timeGaps: any = xmlParse.getElementsByTagName("timegap")?.length ? Array.from(xmlParse.getElementsByTagName("timegap")?.[0]?.getElementsByTagName("values")?.[0]?.getElementsByTagName("item")) : [];
 	state.xmlHasTimes = !!xmlParse.getElementsByTagName("times")?.length
+	state.isCustomNoteTime = !!xmlParse.getElementsByTagName("custom-note-time")?.length
+	state.isTogetherHeighLight = !!xmlParse.getElementsByTagName("cen-same-lyric")?.length
 	// 第一个音符的起始时间
 	const firstMeasure = xmlParse.getElementsByTagName("measure")[0];
 	if (firstMeasure) {

+ 13 - 1
src/page-instrument/view-detail/index.module.less

@@ -386,4 +386,16 @@
         font-size: 14px;
         color: #fff;
     }
-}
+}
+
+:global {
+    #vf-auto1151 + rect {
+        display: none;
+    }
+    #vf-auto2155 + rect {
+        display: none;
+    }
+    #vf-auto1072 + rect, #vf-auto1533 + rect, #vf-auto1630 + rect, #vf-auto1727 + rect, #vf-auto1839 + rect {
+        display: none;
+    }
+}

+ 78 - 12
src/state.ts

@@ -8,7 +8,7 @@ import { IFingering, mappingVoicePart, subjectFingering, matchVoicePart } from "
 import { handleStartTick, closeTick } from "./view/tick";
 import { audioListStart, getAudioCurrentTime, getAudioDuration, setAudioCurrentTime, setAudioPlaybackRate, audioData } from "./view/audio-list";
 import { toggleFollow } from "./view/follow-practice";
-import { browser, setStorageSpeed, setGlobalData, checkDecimal } from "./utils";
+import { browser, setStorageSpeed, setGlobalData, checkDecimal, getNumbers } from "./utils";
 import { api_cloudGetMediaStatus, api_createMusicPlayer, api_cloudChangeSpeed, api_cloudSuspend, api_cloudSetCurrentTime, api_cloudDestroy } from "./helpers/communication";
 import { verifyCanRepeat, getDuration, xmlAddPartName } from "./helpers/formateMusic";
 import { getMusicSheetDetail, getInstrumentCode } from "./utils/baseApi"
@@ -380,6 +380,10 @@ const state = reactive({
   headTopHeight: 0,
   /** 是否是来源于缓存的xml */
   xmlFromStore: false,
+  /** 是否是自定义的note时值 */
+  isCustomNoteTime: false,    
+  /** 中英文歌词是否需要同时高亮 */
+  isTogetherHeighLight: false,
 });
 const browserInfo = browser();
 let offset_duration = 0;
@@ -1125,6 +1129,7 @@ export const hanldeDirectSelection = (list: any[]) => {
   }, 0);
 };
 let offsetTop = 0, musicScrollTop = 0;
+let lastLineTop = 0; // 记录上一次的行位置
 /**
  * 窗口内滚动到音符的区域
  * @param isScroll 可选: 强制滚动到顶部, 默认: false
@@ -1138,10 +1143,15 @@ export const scrollViewNote = (resetTop?: boolean) => {
   }
   if (state.activeNoteIndex <= 1 || resetTop) {
     offsetTop = 0;
+    lastLineTop = 0; // 重置行位置记录
   }
   const domId = "vf" + noteId;
   // 合并休止小节没有音符,取小节的位置,否则取音符指针位置
-  const cursorElement: any = !noteId && state.times[state.activeNoteIndex]?.totalMultipleRestMeasures ? document.querySelector(`.measureIndex_${state.activeMeasureIndex}`) : document.querySelector(`[data-vf=${domId}]`)?.parentElement;
+  let cursorElement: any = !noteId && state.times[state.activeNoteIndex]?.totalMultipleRestMeasures ? document.querySelector(`.measureIndex_${state.activeMeasureIndex}`) : document.querySelector(`[data-vf=${domId}]`)?.parentElement;
+  // 如果是简谱
+  if (state.musicRenderType === EnumMusicRenderType.firstTone || state.musicRenderType === EnumMusicRenderType.fixedTone) {
+    // cursorElement = cursorElement.querySelector('.node-dot')
+  }
   const musicAndSelection = document.getElementById(state.scrollContainer)!;
   if (!state.headTopHeight) {
     state.headTopHeight = document.querySelector('.headHeight')?.getBoundingClientRect()?.height || 100;
@@ -1149,16 +1159,63 @@ export const scrollViewNote = (resetTop?: boolean) => {
   if (!cursorElement || !musicAndSelection) {
     return
   }
-  // offsetTop = musicAndSelection.scrollTop || offsetTop;
-  const noteCenterOffsetTop = cursorElement ? cursorElement?.offsetTop + (cursorElement?.offsetHeight/2) : 0;
-  // console.log('滑动',offsetTop, noteCenterOffsetTop)
+  
+  // 获取当前音符的位置信息
+  const rect = cursorElement.getBoundingClientRect();
+  const rawOffsetTop = cursorElement.offsetTop;
+  const currentNote = state.times[state.activeNoteIndex];
+  const scrollTarget = state.musicRenderType === EnumMusicRenderType.staff ? rawOffsetTop : rect.height/2 + rawOffsetTop;
+  
+  // 同一行内的音符,offsetTop 可能因为音高不同而有 60-80px 的差异
+  // 但真正换行时,位置变化通常会超过 100px(向上翻页)或 150px(向下换行)
+  // 所以直接使用 rawOffsetTop 的实际差异来判断,而不是用固定行高分组
+  
+  let currentTop = rawOffsetTop;
+  let isLineChanged = false;
+  
+  // 计算与上次位置的差异
+  const positionDiff = currentTop - lastLineTop;
+  
+  // 换行判断阈值:
+  // - 向下换行(新行在下方):位置增加超过 100px
+  // - 向上翻页(新行在上方):位置减少超过 100px
+  // - 同一行内移动:位置变化在 -100px 到 +100px 之间
+  const lineChangeThreshold = 100; // 真正换行的阈值
+  
+  if (Math.abs(positionDiff) > lineChangeThreshold) {
+    // 位置变化超过阈值,认为是换行
+    isLineChanged = true;
+  } else {
+    // 位置变化较小,认为是同一行内移动
+    isLineChanged = false;
+    // 同一行内,使用上次的 lastLineTop 作为基准,避免累积误差
+    currentTop = lastLineTop;
+  }
+  
+  // console.log('滚动检测', { 
+  //   currentTop, 
+  //   lastLineTop, 
+  //   isLineChanged, 
+  //   positionDiff,
+  //   absDiff: Math.abs(positionDiff),
+  //   noteIndex: state.activeNoteIndex,
+  //   rawOffsetTop,
+  //   scrollTarget,
+  //   MeasureNumberXML: currentNote?.MeasureNumberXML
+  // })
+
   if (Math.abs(musicAndSelection?.scrollTop - musicScrollTop) > 30) {
     // 手动滑动谱面,重新播放需要滚动到对应位置
+    lastLineTop = currentTop; // 更新行位置记录
   } else {
-    if (offsetTop === cursorElement.offsetTop || Math.abs(offsetTop - cursorElement.offsetTop) < 30) return;
+    // 如果没有换行,则不执行滚动,直接返回
+    if (!isLineChanged) {
+      return;
+    }
   }
-  // offsetTop = noteCenterOffsetTop;
-  offsetTop = cursorElement.offsetTop;
+  // 换行了,更新 offsetTop 和行位置记录
+  lastLineTop = currentTop;
+  offsetTop = scrollTarget; // 使用计算好的滚动目标值
   const animateType = browser().android ? "instant" : "smooth"
   if (offsetTop > (state.headTopHeight + 30)) {
     musicScrollTop = (offsetTop - state.headTopHeight - 30) * state.musicZoom
@@ -1937,12 +1994,21 @@ export const fillWordColor = () => {
   })
   const currentLyrics: SVGAElement[] = Array.from(document.querySelectorAll(`.lyric${currentNote?.noteId}`));
   currentLyrics.forEach((lyric, index) => {
-    const lyricIndex = lyric.getAttribute('lyricIndex');
+    let lyricIndex = lyric.getAttribute('lyricIndex');
     // bug:#10942,如果需要反复唱的小节,只有一遍歌词,反复唱的时候,歌词都需要高亮
     const onlyOneLyric = currentNote.measures?.every((item: any) => item?.formatLyricsEntries?.length <= 1);
-    if ((index === currentNote.repeatIdx && currentNote.repeatIdx + 1 == lyricIndex) || (currentNote.repeatIdx != index && !onlyOneLyric && currentNote.repeatIdx + 1 == lyricIndex) || (currentNote.repeatIdx > 0 && currentNote.formatLyricsEntries?.length === 1 && onlyOneLyric)) {
-      lyric?.classList.add('lyricActive')
-    } 
+
+    // 中英文歌词同时高亮
+    if (state.isTogetherHeighLight) {
+      let multiIdxs = getNumbers(currentNote.repeatIdx+1)
+      if ((index === currentNote.repeatIdx && multiIdxs.includes(Number(lyricIndex)) ) || (currentNote.repeatIdx != index && !onlyOneLyric && multiIdxs.includes(Number(lyricIndex)) ) || (currentNote.repeatIdx > 0 && currentNote.formatLyricsEntries?.length === 1 && onlyOneLyric)) {
+        lyric?.classList.add('lyricActive')
+      }       
+    } else {
+      if ((index === currentNote.repeatIdx && currentNote.repeatIdx + 1 == lyricIndex) || (currentNote.repeatIdx != index && !onlyOneLyric && currentNote.repeatIdx + 1 == lyricIndex) || (currentNote.repeatIdx > 0 && currentNote.formatLyricsEntries?.length === 1 && onlyOneLyric)) {
+        lyric?.classList.add('lyricActive')
+      } 
+    }
     // bug: #11189,兼容处理需要唱4遍,但是只打了2遍歌词的情况,1、3唱一样的歌词,2、4唱一样的歌词
     if ( currentNote.formatLyricsEntries.length == 2 && currentNote.repeatIdx >= 2 && index === (currentNote.repeatIdx - 2) ) {
       lyric?.classList.add('lyricActive')

+ 4 - 0
src/utils/index.ts

@@ -191,4 +191,8 @@ export const debounce = (fn: Function, ms = 0) => {
 // 使用正则表达式匹配小数点后第一位数字是否是 0 或 9
 export const checkDecimal = (num: number | string) => {
 	return /^\d*\.(0|9)/.test(num.toString());
+}
+
+export const getNumbers = (n: number) => {
+  return [2 * n - 1, 2 * n];
 }

+ 9 - 0
src/view/music-score/index.module.less

@@ -127,3 +127,12 @@
         }        
    } 
 }
+
+:global {
+    #vf-auto1151 + rect {
+        display: none;
+    }
+    #vf-auto2155 + rect {
+        display: none;
+    }
+}

+ 1 - 1
src/view/music-score/index.tsx

@@ -117,7 +117,7 @@ export default defineComponent({
 				renderSingleHorizontalStaffline: state.isSingleLine ? true : false,
 				// autoGenerateMultipleRestMeasuresFromRestMeasures: state.isSingleLine ? false : true, // 连续休止小节是否合并显示
 				autoGenerateMultipleRestMeasuresFromRestMeasures: state.setting.combineMultipleRest, // 是否自动合并休止小节
-				drawLyrics: ((state.playType === 'sing' || !state.isEvxml || state.isCreateImg || query.showLyrics === '1') && !state.isSimplePage) ? true : false, // 演唱模式才渲染歌词,simple页面不显示歌词
+				drawLyrics: ((state.playType === 'sing' || state.playType === 'play' || !state.isEvxml || state.isCreateImg || query.showLyrics === '1') && !state.isSimplePage) ? true : false, // 演唱模式才渲染歌词,simple页面不显示歌词
 				// darkMode: true, // 暗黑模式
 				// pageFormat: 'A4_P',
 				// autoBeam: true,

+ 2 - 2
vite.config.ts

@@ -84,9 +84,9 @@ export default defineConfig({
         // target: "https://kt.colexiu.com",
         // target: "https://test.lexiaoya.cn",
         // target: "https://kt.colexiu.com",
-        target: "https://test.resource.colexiu.com", // 内容平台开发环境,内容平台开发,需在url链接上加上isCbs=true
+        // target: "https://test.resource.colexiu.com", // 内容平台开发环境,内容平台开发,需在url链接上加上isCbs=true
         // target: "https://test.kt.colexiu.com",
-        // target: "https://mec.colexiu.com",
+        target: "https://mec.colexiu.com",
         changeOrigin: true,
         rewrite: (path) => path.replace(/^\/instrument/, ""),
       },