瀏覽代碼

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

TIANYONG 11 月之前
父節點
當前提交
be01e6dcf4

二進制
src/page-instrument/view-evaluat-report/component/share-top/image/audioBg.png


File diff suppressed because it is too large
+ 0 - 0
src/page-instrument/view-evaluat-report/component/share-top/image/leftCloud.json


File diff suppressed because it is too large
+ 0 - 0
src/page-instrument/view-evaluat-report/component/share-top/image/rightCloud.json


+ 21 - 1
src/page-instrument/view-evaluat-report/component/share-top/index.module.less

@@ -303,7 +303,19 @@
         border-radius: 16px;
         .audioBga{
             width: 100%;
-            height: 100%;
+            height: 88%;
+        }
+        .audioBga1{
+            position: absolute;
+            left: 0;
+            top: 8px;
+            width: 146px;
+        }        
+        .audioBga2{
+            width: 268px;
+            position: absolute;
+            right: -24px;
+            top: -8px;
         }
         :global {
             .plyr {
@@ -313,6 +325,14 @@
                 bottom: 0;
             }
         }
+        .audioVisualizer{
+            position: absolute;
+            top: 50%;
+            left: 50%;
+            transform: translate(-50%,-50%);
+            width: 370px;
+            height: 66px;
+        }
     }
 }
 

+ 124 - 1
src/page-instrument/view-evaluat-report/component/share-top/index.tsx

@@ -18,6 +18,8 @@ import { storeData } from "/src/store";
 import Title from "/src/page-instrument/header-top/title";
 import { Vue3Lottie } from "vue3-lottie";
 import audioBga from "./image/audioBga.json";
+import audioBga1 from "./image/leftCloud.json";
+import audioBga2 from "./image/rightCloud.json";
 
 type IItemType = "intonation" | "cadence" | "integrity";
 
@@ -38,6 +40,9 @@ export default defineComponent({
 			isInitPlyr: false,
 			_plrl: null as any,
 		});
+		const lottieDom = ref<any>()
+		const lottieDom1 = ref<any>()
+		const lottieDom2 = ref<any>()
 		const level: any = {
 			BEGINNER: "入门级",
 			ADVANCED: "进阶级",
@@ -84,9 +89,123 @@ export default defineComponent({
 					controls: ["play-large", "play", "progress", "current-time", "duration"],
 					fullscreen: { enabled: false },
 				});
+				// 创建音波数据
+				if(mediaType.value === "audio"){
+					setTimeout(() => {
+						const audioDom = document.querySelector("#audioSrc") as HTMLAudioElement
+						const canvasDom = document.querySelector("#audioVisualizer") as HTMLCanvasElement
+						const { pauseVisualDraw, playVisualDraw } = audioVisualDraw(audioDom, canvasDom)
+						shareData._plrl.on('play', () => {
+							lottieDom.value.play()
+							lottieDom1.value.play()
+							lottieDom2.value.play()
+							playVisualDraw()
+						});
+						shareData._plrl.on('pause', () => {
+							lottieDom.value.pause()
+							lottieDom1.value.pause()
+							lottieDom2.value.pause()
+							pauseVisualDraw()
+						});
+					}, 300); // 弹窗动画是0.25秒 这里用定时器 确保canvas 能获取到宽高
+				}
 				shareData.isInitPlyr = true;
 			});
 		};
+		/**
+		 * 音频可视化
+		 * @param audioDom 
+		 * @param canvasDom 
+		 * @param fftSize  2的幂数,最小为32
+		 */
+		function audioVisualDraw(audioDom: HTMLAudioElement, canvasDom: HTMLCanvasElement, fftSize = 128) {
+			type propsType = { canvWidth: number; canvHeight: number; canvFillColor: string; lineColor: string; lineGap: number }
+			// canvas
+			const canvasCtx = canvasDom.getContext("2d")!
+			const { width, height } = canvasDom.getBoundingClientRect()
+			canvasDom.width = width
+			canvasDom.height = height
+			// audio
+			const audioCtx = new AudioContext()
+			const analyser = audioCtx.createAnalyser()
+			const source = audioCtx.createMediaElementSource(audioDom)
+			analyser.fftSize = fftSize
+			source.connect(analyser)
+			analyser.connect(audioCtx.destination)
+			const dataArray = new Uint8Array(analyser.frequencyBinCount)
+			const draw = (data: Uint8Array, ctx: CanvasRenderingContext2D, { lineGap, canvWidth, canvHeight, canvFillColor, lineColor }: propsType) => {
+				if (!ctx) return
+				const w = canvWidth
+				const h = canvHeight
+				fillCanvasBackground(ctx, w, h, canvFillColor)
+			   // 可视化
+				const dataLen = data.length
+				const step = (w / 2 - lineGap * dataLen) / dataLen
+				const midX = w / 2
+				const midY = h / 2
+				let xLeft = midX
+				for (let i = 0; i < dataLen; i++) {
+					const value = data[i]
+					const percent = value / 255 // 最大值为255
+					const barHeight = percent * midY
+					canvasCtx.fillStyle = lineColor
+					// 中间加间隙
+					if (i === 0) {
+						xLeft -= lineGap / 2
+					}
+					canvasCtx.fillRect(xLeft - step, midY - barHeight, step, barHeight)
+					canvasCtx.fillRect(xLeft - step, midY, step, barHeight)
+					xLeft -= step + lineGap
+				}
+				let xRight = midX
+				for (let i = 0; i < dataLen; i++) {
+					const value = data[i]
+					const percent = value / 255 // 最大值为255
+					const barHeight = percent * midY
+					canvasCtx.fillStyle = lineColor
+					if (i === 0) {
+						xRight += lineGap / 2
+					}
+					canvasCtx.fillRect(xRight, midY - barHeight, step, barHeight)
+					canvasCtx.fillRect(xRight, midY, step, barHeight)
+					xRight += step + lineGap
+				}
+			}
+			const fillCanvasBackground = (ctx: CanvasRenderingContext2D, w: number, h: number, colors: string) => {
+				ctx.clearRect(0, 0, w, h)
+				ctx.fillStyle = colors
+				ctx.fillRect(0, 0, w, h)
+			}
+			const requestAnimationFrameFun = () => {
+				requestAnimationFrame(() => {
+					analyser.getByteFrequencyData(dataArray)
+					draw(dataArray, canvasCtx, {
+						lineGap: 2,
+						canvWidth: width,
+						canvHeight: height,
+						canvFillColor: "transparent",
+						lineColor: "rgba(255, 255, 255, 0.3)"
+					})
+					if (!isPause) {
+						requestAnimationFrameFun()
+					}
+				})
+			}
+			let isPause = true
+			const playVisualDraw = () => {
+				isPause = false
+				audioCtx.resume()
+				requestAnimationFrameFun()
+			}
+			const pauseVisualDraw = () => {
+				isPause = true
+				audioCtx.suspend()
+			}
+			return {
+				playVisualDraw,
+				pauseVisualDraw
+			}
+		}
 		return () => (
 			<div class={[styles.headerTop, browserInfo.android && styles.android]}>
 				<div class={styles.left}>
@@ -324,8 +443,12 @@ export default defineComponent({
 						{
 							mediaType.value === "audio" ? 
 							<div class={styles.audioBox}>
-								<Vue3Lottie class={styles.audioBga} animationData={audioBga} loop={true}></Vue3Lottie>
+								<canvas class={styles.audioVisualizer} id="audioVisualizer"></canvas>
+								<Vue3Lottie ref={lottieDom} class={styles.audioBga} animationData={audioBga} autoPlay={false} loop={true}></Vue3Lottie>
+								<Vue3Lottie ref={lottieDom1} class={styles.audioBga1} animationData={audioBga1} autoPlay={false} loop={true}></Vue3Lottie>
+								<Vue3Lottie ref={lottieDom2} class={styles.audioBga2} animationData={audioBga2} autoPlay={false} loop={true}></Vue3Lottie>
 								<audio
+									crossorigin="anonymous"
 									id="audioSrc"
 									src={scoreData.value.videoFilePath}
 									controls="false"

Some files were not shown because too many files changed in this diff