|
@@ -81,132 +81,152 @@ export default defineComponent({
|
|
|
return "video";
|
|
|
});
|
|
|
|
|
|
- const openAudioAndVideo = () => {
|
|
|
- shareData.show = true;
|
|
|
- if (shareData.isInitPlyr) return;
|
|
|
- nextTick(() => {
|
|
|
- const id = mediaType.value === "audio" ? "#audioSrc" : "#videoSrc";
|
|
|
- shareData._plrl = new Plyr(id, {
|
|
|
- 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,
|
|
|
- };
|
|
|
- }
|
|
|
+ const openAudioAndVideo = () => {
|
|
|
+ shareData.show = true;
|
|
|
+ if (shareData.isInitPlyr) return;
|
|
|
+ nextTick(() => {
|
|
|
+ const id = mediaType.value === "audio" ? "#audioSrc" : "#videoSrc";
|
|
|
+ shareData._plrl = new Plyr(id, {
|
|
|
+ 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
|
|
|
+ let audioCtx : AudioContext | null = null
|
|
|
+ let analyser : AnalyserNode | null = null
|
|
|
+ let source : MediaElementAudioSourceNode | null = null
|
|
|
+ const dataArray = new Uint8Array(fftSize / 2)
|
|
|
+ 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
|
|
|
+ let step = (w / 2 - lineGap * dataLen) / dataLen
|
|
|
+ step < 1 && (step = 1)
|
|
|
+ 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 = () => {
|
|
|
+ if (!audioCtx) {
|
|
|
+ audioCtx = new AudioContext()
|
|
|
+ source = audioCtx.createMediaElementSource(audioDom)
|
|
|
+ analyser = audioCtx.createAnalyser()
|
|
|
+ analyser.fftSize = fftSize
|
|
|
+ source?.connect(analyser)
|
|
|
+ analyser.connect(audioCtx.destination)
|
|
|
+ }
|
|
|
+ //audioCtx.resume() // 重新更新状态 加了暂停和恢复音频音质发生了变化 所以这里取消了
|
|
|
+ isPause = false
|
|
|
+ requestAnimationFrameFun()
|
|
|
+ }
|
|
|
+ const pauseVisualDraw = () => {
|
|
|
+ isPause = true
|
|
|
+ //audioCtx?.suspend() // 暂停 加了暂停和恢复音频音质发生了变化 所以这里取消了
|
|
|
+ // source?.disconnect()
|
|
|
+ // analyser?.disconnect()
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ playVisualDraw,
|
|
|
+ pauseVisualDraw
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return () => (
|
|
|
+ <div class={[styles.headerTop, browserInfo.android && styles.android]}>
|
|
|
+ <div class={styles.left}>
|
|
|
+ <div class={[styles.back, !storeData.isApp && styles.disabled]} onClick={handleBack}>
|
|
|
+ <img src={iconBack} />
|
|
|
+ </div>
|
|
|
+ <div class={styles.leftContent}>
|
|
|
+ {/* <div class={styles.lcName}>{state.examSongName}</div> */}
|
|
|
+ <Title class={styles.lcName} text={state.examSongName} rightView={false} />
|
|
|
+ <div class={styles.lcScore}>{level[scoreData.value.heardLevel]}|综合分数:{scoreData.value.score}分</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
|
|
|
return () => (
|
|
|
<>
|