黄琪勇 пре 11 месеци
родитељ
комит
571369b8b3

+ 7 - 0
src/router/router-root.ts

@@ -231,6 +231,13 @@ export default [
     }
   },
   {
+    path: '/playCreation',
+    component: () => import('@/views/creation/playCreation'),
+    meta: {
+      title: '作品播放'
+    }
+  },
+  {
     path: '/instrumentDetailView',
     component: () => import('@/views/information/instrument-detail/view'),
     meta: {

BIN
src/views/creation/images/back.png


BIN
src/views/creation/images/pause1.png


BIN
src/views/creation/images/play1.png


+ 12 - 0
src/views/creation/index-share.tsx

@@ -301,6 +301,17 @@ export default defineComponent({
         pauseVisualDraw
       }
     }
+    function handlerLandscapeScreen(event:any){
+      event.stopPropagation()
+      router.push({
+        path:"/playCreation",
+        query:{
+          resourceUrl:encodeURIComponent(state.musicDetail?.videoUrl),
+          musicSheetName:encodeURIComponent(state.musicDetail?.musicSheetName),
+          username:encodeURIComponent(state.musicDetail?.username),
+        }
+      })
+    }
     const checkLogin = async () => {
       try {
         // 判断是否登录
@@ -467,6 +478,7 @@ export default defineComponent({
                         {getSecondRPM(plyrState.duration)}
                       </div>
                     </div>
+                    <div class={styles.landscapeScreen} onClick={handlerLandscapeScreen}></div>
                   </>
                 }
               </div>

+ 9 - 1
src/views/creation/index.module.less

@@ -142,6 +142,15 @@
       display: flex;
     }
   }
+  .landscapeScreen{
+    width: 26px;
+    height: 26px;
+    position: absolute;
+    background: url("./images/Landscape.png") no-repeat;
+    background-size: 100% 100%;
+    right: 10px;
+    top: 10px;
+  }
 }
 
 .musicSection {
@@ -384,7 +393,6 @@
     }
   }
 }
-
 // 分享样式
 .logoDownload{
   display: flex;

+ 12 - 0
src/views/creation/index.tsx

@@ -319,6 +319,17 @@ export default defineComponent({
 				pauseVisualDraw
 			}
 		}
+    function handlerLandscapeScreen(event:any){
+      event.stopPropagation()
+      router.push({
+        path:"/playCreation",
+        query:{
+          resourceUrl:encodeURIComponent(state.musicDetail?.videoUrl),
+          musicSheetName:encodeURIComponent(state.musicDetail?.musicSheetName),
+          username:encodeURIComponent(state.musicDetail?.username),
+        }
+      })
+    }
     onMounted(async () => {
       setStatusBarTextColor(true)
       try {
@@ -429,6 +440,7 @@ export default defineComponent({
                 {getSecondRPM(plyrState.duration)}
               </div>
             </div>
+            <div class={styles.landscapeScreen} onClick={handlerLandscapeScreen}></div>
           </div>
         </Sticky>
         <div class={styles.musicSection}>

+ 150 - 0
src/views/creation/playCreation/index.module.less

@@ -0,0 +1,150 @@
+.playCreation{
+  width: 100vw;
+  height: 100vh;
+  position: relative;
+  &.landscapeScreen{
+    width: 100vh;
+    height: 100vw;
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%) rotate(90deg);
+    transform-origin: center;
+  }
+  :global {
+      .plyr {
+          width: 100%;
+          height: 100%;
+          .plyr__control.plyr__control--overlaid{
+              width: 48px;
+              height: 48px;
+              background: url("../images/midPlay.png") no-repeat;
+              background-size: 100% 100%;
+              .plyr__sr-only, svg{
+                  display: none;
+              }
+          }
+          .plyr__controls{
+              background: initial;
+              padding: 0 20px 13px;
+              .plyr__controls__item.plyr__control{
+                  padding: 0;
+                  width: 18px;
+                  height: 18px;
+                  &:hover{
+                      background: initial;
+                  }
+                  .icon--pressed{
+                      width: 100%;
+                      height: 100%;
+                      background: url("../images/pause1.png") no-repeat;
+                      background-size: 100% 100%;
+                      use{
+                          display: none;
+                      }
+                  }
+                  .icon--not-pressed{
+                      width: 100%;
+                      height: 100%;
+                      background: url("../images/play1.png") no-repeat;
+                      background-size: 100% 100%;
+                      use{
+                          display: none;
+                      }
+                  }
+              }
+              .plyr__controls__item.plyr__progress__container{
+                  margin-left: 9px;
+                  input[type=range]{
+                      color: #73C1FF;
+                      height: 20px;
+                  }
+                  input[type="range"]::-webkit-slider-runnable-track {
+                      height: 4px;
+                  }
+                  input[type="range"]::-webkit-slider-thumb {
+                      width: 12px;
+                      height: 12px;
+                      margin-top: -4px;
+                  }
+                  .plyr__progress__buffer{
+                      height: 4px;
+                      color: rgba(115,193,255,0.8);
+                      background-color: #fff;
+                      margin-top: -2px;
+                  }
+              }
+              .plyr__controls__item.plyr__time{
+                  font-weight: 500;
+                  font-size: 14px;
+                  color: #FFFFFF;
+                  display: initial;
+                  &.plyr__time--current{
+                      margin-left: 9px;
+                  }
+              }
+
+          }
+      }
+  }
+  .videoBox{
+      width: 100%;
+      height: 100%;
+  }
+  .audioBox{
+      width: 100%;
+      height: 100%;
+      background: url("../images/audioBg.png") no-repeat;
+      background-size: 100% 100%;
+      position: relative;
+      .audioBga{
+          width: 100%;
+          height: 100%;
+          overflow: hidden;
+      }
+      :global {
+          .plyr {
+              position: absolute;
+              height: initial;
+              left: 0;
+              bottom: 0;
+          }
+      }
+      .audioVisualizer{
+          position: absolute;
+          top: 50%;
+          left: 50%;
+          transform: translate(-50%,-50%);
+          width: 370px;
+          height: 66px;
+      }
+  }
+  .backBox{
+    position: absolute;
+    left: 30px;
+    top: 20px;
+    display: flex;
+    z-index: 10;
+    .backImg{
+      width: 32px;
+      height: 32px;
+    }
+    .musicDetail{
+      margin-left: 10px;
+      .musicSheetName{
+        margin-top: 4px;
+        font-weight: 600;
+        font-size: 18px;
+        color: #FFFFFF;
+        line-height: 20px;
+      }
+      .username{
+        margin-top: 2px;
+        font-weight: 400;
+        font-size: 12px;
+        color: rgba(255, 255, 255, 0.7);
+        line-height: 18px
+      }
+    }
+  }
+}

+ 217 - 0
src/views/creation/playCreation/index.tsx

@@ -0,0 +1,217 @@
+import { defineComponent, onMounted, reactive, ref, onUnmounted } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import { browser } from "@/helpers/utils"
+import styles from './index.module.less';
+import "plyr/dist/plyr.css";
+import Plyr from "plyr";
+import { Vue3Lottie } from "vue3-lottie";
+import audioBga from "../images/audioBga.json";
+import videobg from "../images/videobg.png";
+import backImg from "../images/back.png";
+import {
+  postMessage
+} from '@/helpers/native-message';
+
+export default defineComponent({
+  name: 'playCreation',
+  setup() {
+    const {isApp} = browser()
+    const route = useRoute();
+    const router = useRouter();
+    const resourceUrl = decodeURIComponent(route.query.resourceUrl as string || '');
+    const musicSheetName = decodeURIComponent(route.query.musicSheetName as string || '');
+    const username = decodeURIComponent(route.query.username as string || '');
+    const playType = resourceUrl.lastIndexOf('mp4') !== -1 ? 'Video' : 'Audio'
+    const lottieDom = ref()
+    const landscapeScreen = ref(false)
+    function initPlay(){
+      const id = playType === "Audio" ? "#audioMediaSrc" : "#videoMediaSrc";
+      const _plrl = new Plyr(id, {
+        controls: ["play-large", "play", "progress", "current-time", "duration"]
+      });
+      // 创建音波数据
+      if(playType === "Audio"){
+        const audioDom = document.querySelector("#audioMediaSrc") as HTMLAudioElement
+        const canvasDom = document.querySelector("#audioVisualizer") as HTMLCanvasElement
+        const { pauseVisualDraw, playVisualDraw } = audioVisualDraw(audioDom, canvasDom)
+        _plrl.on('play', () => {
+          lottieDom.value.play()
+          playVisualDraw()
+        });
+        _plrl.on('pause', () => {
+          lottieDom.value.pause()
+          pauseVisualDraw()
+        });
+      }
+    }
+        /**
+     * 音频可视化
+     * @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.7)"
+          })
+          if (!isPause) {
+            requestAnimationFrameFun()
+          }
+        })
+      }
+      let isPause = true
+      const playVisualDraw = () => {
+        isPause = false
+        audioCtx.resume()
+        requestAnimationFrameFun()
+      }
+      const pauseVisualDraw = () => {
+        isPause = true
+        audioCtx.suspend()
+      }
+      return {
+        playVisualDraw,
+        pauseVisualDraw
+      }
+    }
+    function handlerBack(){
+      router.back()
+    }
+    function handlerLandscapeScreen(){
+      // app端调用app的横屏
+      if(isApp){
+        postMessage({
+          api: "setRequestedOrientation",
+          content: {
+            orientation: 0,
+          },
+        });
+      }else{
+        // web端使用旋转的方式
+        updateLandscapeScreenState()
+        window.addEventListener('resize', updateLandscapeScreenState)
+      }
+    }
+    function updateLandscapeScreenState(){
+      if(window.innerWidth > window.innerHeight){
+        landscapeScreen.value = false
+      }else{
+        landscapeScreen.value = true
+      }
+    }
+    handlerLandscapeScreen()
+    onMounted(()=>{
+      initPlay()
+    })
+    onUnmounted(()=>{
+      if(isApp){
+        postMessage({
+          api: "setRequestedOrientation",
+          content: {
+            orientation: 1,
+          },
+        });
+      }else{
+        window.removeEventListener('resize', updateLandscapeScreenState)
+      }
+    })
+    return () =>
+    <div class={[styles.playCreation,landscapeScreen.value && styles.landscapeScreen]}>
+      <div class={styles.backBox}>
+        <img class={styles.backImg} src={backImg}onClick={handlerBack} />
+        <div class={styles.musicDetail}>
+          <div class={styles.musicSheetName}>{musicSheetName}</div>
+          <div class={styles.username}>{username}</div>
+        </div>
+      </div>
+      {
+          playType === 'Audio' ?
+          <div class={styles.audioBox}>
+            <canvas class={styles.audioVisualizer} id="audioVisualizer"></canvas>
+            <Vue3Lottie ref={lottieDom} class={styles.audioBga} animationData={audioBga} autoPlay={false} loop={true}></Vue3Lottie>
+            <audio
+              crossorigin="anonymous"
+              id="audioMediaSrc"
+              src={resourceUrl}
+              controls="false"
+              preload="metadata"
+              playsinline
+            />
+          </div>
+          :
+          <video
+            id="videoMediaSrc"
+            class={styles.videoBox}
+            src={resourceUrl}
+            data-poster={videobg}
+            preload="metadata"
+            playsinline
+          />
+      }
+    </div>;
+  }
+});