فهرست منبع

Merge branch 'hqyDev' of http://git.dayaedu.com/liushengqiang/classroom-app into dev

黄琪勇 1 سال پیش
والد
کامیت
c2bdbc7af6
38فایلهای تغییر یافته به همراه2120 افزوده شده و 1140 حذف شده
  1. 7 0
      src/router/router-root.ts
  2. 1 1
      src/router/routes-common.ts
  3. 18 5
      src/views/creation/edit/index.module.less
  4. 21 24
      src/views/creation/edit/index.tsx
  5. BIN
      src/views/creation/images/Landscape.png
  6. BIN
      src/views/creation/images/audioBg.png
  7. 0 0
      src/views/creation/images/audioBga.json
  8. BIN
      src/views/creation/images/back.png
  9. BIN
      src/views/creation/images/bg.png
  10. BIN
      src/views/creation/images/cance.png
  11. BIN
      src/views/creation/images/confirm.png
  12. BIN
      src/views/creation/images/edit.png
  13. BIN
      src/views/creation/images/icon-delete.png
  14. BIN
      src/views/creation/images/icon-download.png
  15. BIN
      src/views/creation/images/icon-member.png
  16. BIN
      src/views/creation/images/icon-share.png
  17. BIN
      src/views/creation/images/icon-zan.png
  18. BIN
      src/views/creation/images/logo.png
  19. BIN
      src/views/creation/images/logo1.png
  20. BIN
      src/views/creation/images/midPlay.png
  21. BIN
      src/views/creation/images/pause1.png
  22. BIN
      src/views/creation/images/play.png
  23. BIN
      src/views/creation/images/play1.png
  24. BIN
      src/views/creation/images/prompt.png
  25. BIN
      src/views/creation/images/upward.png
  26. BIN
      src/views/creation/images/videobg.png
  27. 510 284
      src/views/creation/index-share.tsx
  28. 461 574
      src/views/creation/index.module.less
  29. 441 242
      src/views/creation/index.tsx
  30. 192 0
      src/views/creation/playCreation/index.module.less
  31. 441 0
      src/views/creation/playCreation/index.tsx
  32. BIN
      src/views/creation/share-model/images/audio-share-bg.png
  33. BIN
      src/views/creation/share-model/images/audioLabel.png
  34. BIN
      src/views/creation/share-model/images/share-bg.png
  35. BIN
      src/views/creation/share-model/images/video-share-bg.png
  36. BIN
      src/views/creation/share-model/images/videoLabel.png
  37. 16 7
      src/views/creation/share-model/index.module.less
  38. 12 3
      src/views/creation/share-model/index.tsx

+ 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: {

+ 1 - 1
src/router/routes-common.ts

@@ -198,7 +198,7 @@ export default [
         path: '/creation-edit',
         component: () => import('@/views/creation/edit/index'),
         meta: {
-          title: '作品详情'
+          title: '编辑'
         }
       },
       {

+ 18 - 5
src/views/creation/edit/index.module.less

@@ -1,3 +1,15 @@
+.editBg{
+  position: fixed;
+  z-index: -1;
+  width: 100vw;
+  height: 100vh;
+  top: 0;
+  left: 0;
+  background-color: #F8F8F8;
+}
+.edit{
+  overflow: hidden;
+}
 .sectionVideo {
   position: relative;
   line-height: 0;
@@ -6,7 +18,7 @@
 
   .videoBg {
     width: 100%;
-    height: 160px;
+    height: 196px;
     object-fit: cover;
     border-radius: 10px;
   }
@@ -14,7 +26,7 @@
   .btnGroup {
     position: absolute;
     left: 50%;
-    bottom: -12px;
+    bottom: 10px;
     background: linear-gradient(180deg, rgba(128, 158, 200, 0.59) 0%, rgba(58, 101, 162, 0.59) 100%);
     border-radius: 15px;
     height: 30px;
@@ -48,6 +60,7 @@
         background: url('../images/icon-cropper.png') no-repeat center;
         background-size: contain;
         margin-right: 4px;
+        margin-top: -3px;
       }
     }
 
@@ -76,6 +89,7 @@
             background: url('../images/icon-image.png') no-repeat center;
             background-size: contain;
             margin-right: 4px;
+            margin-top: -2px;
           }
 
           &::after {
@@ -140,8 +154,7 @@
       left: 0;
       right: 0;
       z-index: 10;
-      background: #000000;
-      opacity: 0.37;
+      background: rgba(0,0,0,0.37);
       line-height: 20px;
       height: 20px;
       font-size: 13px;
@@ -211,4 +224,4 @@
 .btnGroup {
   margin: 32px 24px 12px;
 
-}
+}

+ 21 - 24
src/views/creation/edit/index.tsx

@@ -6,7 +6,7 @@ import { Button, Field, showToast } from 'vant';
 import MUploader from '@/components/m-uploader';
 import { api_userMusicDetail, api_userMusicSave } from '../api';
 import { useRoute, useRouter } from 'vue-router';
-import videoBg from '../images/video-bg.png';
+import videoBg from '../images/videoBg.png';
 import { postMessage } from '@/helpers/native-message';
 
 export default defineComponent({
@@ -64,7 +64,6 @@ export default defineComponent({
         state.desc = data.desc;
         state.videoImg = data.videoImg;
         state.img = data.img ? [data.img] : [];
-
         if (data?.videoUrl.lastIndexOf('mp4') !== -1) {
           state.playType = 'Video';
         } else {
@@ -75,11 +74,27 @@ export default defineComponent({
       }
     });
     return () => (
-      <div>
+      <div class={styles.edit}>
         <MSticky position="top">
-          <MHeader border={false} />
+          <MHeader background={"#F1F1F1"} border={false} />
         </MSticky>
-
+        <div class={styles.editBg}></div>
+        <div class={[styles.section, styles.sectionFile]}>
+          <div class={styles.uploadImg}>
+            <MUploader
+              class={styles.muploader}
+              // native
+              cropper
+              deletable={false}
+              v-model:modelValue={state.img}
+            />
+            {/* <div class={styles.tip}>选封面</div> */}
+          </div>
+          <div class={styles.musicDetail}>
+            <p class={styles.musicName}>{state.musicDetail.musicSheetName}</p>
+            <p class={styles.username}>{state.musicDetail.username}</p>
+          </div>
+        </div>
         {state.playType === 'Video' && (
           <div class={[styles.section, styles.sectionVideo]}>
             <img src={state.videoImg || videoBg} class={styles.videoBg} />
@@ -115,30 +130,12 @@ export default defineComponent({
             v-model={state.desc}
           />
         </div>
-
-        <div class={[styles.section, styles.sectionFile]}>
-          <div class={styles.uploadImg}>
-            <MUploader
-              class={styles.muploader}
-              // native
-              cropper
-              deletable={false}
-              v-model:modelValue={state.img}
-            />
-            {/* <div class={styles.tip}>选封面</div> */}
-          </div>
-          <div class={styles.musicDetail}>
-            <p class={styles.musicName}>{state.musicDetail.musicSheetName}</p>
-            <p class={styles.username}>{state.musicDetail.username}</p>
-          </div>
-        </div>
-
         <div class={styles.btnGroup}>
           <Button
             type="primary"
             round
             block
-            color="linear-gradient(73deg, #5BECFF 0%, #259CFE 100%)"
+            color="linear-gradient(90deg, #44C9FF 0%, #259CFE 100%)"
             onClick={onSubmit}>
             {state.musicDetail.type === 'FORMAL' ? '保存' : '发布'}
           </Button>

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


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


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
src/views/creation/images/audioBga.json


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


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


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


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


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


BIN
src/views/creation/images/icon-delete.png


BIN
src/views/creation/images/icon-download.png


BIN
src/views/creation/images/icon-member.png


BIN
src/views/creation/images/icon-share.png


BIN
src/views/creation/images/icon-zan.png


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


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


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


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


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


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


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


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


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


+ 510 - 284
src/views/creation/index-share.tsx

@@ -9,14 +9,21 @@ import {
 // import WaveSurfer from 'wavesurfer.js';
 // import Regions from 'wavesurfer.js/dist/plugins/regions.js';
 import styles from './index.module.less';
-import { Cell, Image, List, Popup, Slider, showDialog } from 'vant';
+import { Cell, Image, List, Popup, Sticky, TextEllipsis } from 'vant';
 import iconMember from './images/icon-member.png';
 import iconZan from './images/icon-zan.png';
 import iconZanActive from './images/icon-zan-active.png';
-import iconZ from './images/icon-z.png';
+import logoImg from './images/logo.png';
+import logo1Img from './images/logo1.png';
+import iconUpward from './images/upward.png';
 import iconPlay from './images/icon-play.png';
 import iconPause from './images/icon-pause.png';
-import { browser, getGradeCh, getSecondRPM } from '@/helpers/utils';
+import audioPan from './images/audio-pan.png';
+import audioLabel from './share-model/images/audioLabel.png';
+import videoLabel from './share-model/images/videoLabel.png';
+import musicBg from './share-model/images/music-bg.png';
+import playImg from './images/play.png';
+import { browser, getGradeCh, getSecondRPM, vaildMusicScoreUrl } from '@/helpers/utils';
 import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router';
 import {
   api_openUserMusicDetail,
@@ -33,32 +40,32 @@ import { setLogout } from '@/state';
 import { storage } from '@/helpers/storage';
 import { ACCESS_TOKEN } from '@/store/mutation-types';
 import MWxTip from '@/components/m-wx-tip';
-import { usePageVisibility } from '@vant/use';
+import { usePageVisibility, useEventListener } from '@vant/use';
 import videoBg from './images/video-bg.png';
 import LoginChangeModel from './login-change-model';
+import MSticky from '@/components/m-sticky';
+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";
 
 export default defineComponent({
   name: 'creation-detail',
   setup() {
     const route = useRoute();
     const router = useRouter();
-    const audioId = 'a' + +Date.now() + Math.floor(Math.random() * 100);
-
+    const isScreenScroll = ref(false)
+    const lottieDom = ref()
     const state = reactive({
       id: route.query.id,
+      isEmpty:false,
       loginTag: false, // 是否登录标识
       loginStatus: false,
       loginChangeState: false, // 切换账号
       credential: {} as any,
       playType: '' as 'Audio' | 'Video' | '', // 播放类型
       musicDetail: {} as any,
-      timer: null as any,
-      paused: true,
-      audioWidth: 0,
-      currentTime: 0,
-      duration: 0.1,
-      loop: false,
-      dragStatus: false, // 是否开始拖动
       isClick: false,
       list: [] as any,
       listState: {
@@ -71,37 +78,26 @@ export default defineComponent({
         rows: 20
       },
       messageStatus: false,
-      message: ''
+      message: '',
+      _plrl: null as any,
+      heightV:0,
+      heightB:0
     });
-    const wavesurfer = ref();
-    // window.AudioContext = window.AudioContext || window.webkitAudioContext;
-    const audioDom = new Audio();
-    audioDom.controls = true;
-    audioDom.style.width = '100%';
-    audioDom.className = styles.audio;
-
-    /** 改变播放时间 */
-    const handleChangeTime = (val: number) => {
-      state.currentTime = val;
-      clearTimeout(state.timer);
-      state.timer = setTimeout(() => {
-        audioDom.currentTime = val;
-        state.timer = null;
-      }, 60);
-    };
-
-    // 切换音频播放
-    const onToggleAudio = (e: any) => {
-      e.stopPropagation();
-      if (audioDom.paused) {
-        audioDom.play();
-      } else {
-        audioDom.pause();
-      }
-
-      state.paused = audioDom.paused;
-    };
-
+    const plyrState = reactive({
+      duration: 0,
+      currentTime: 0,
+      mediaTimeShow: false,
+      playIngShow: true,
+      loaded:false
+    })
+    // 谱面
+    const staffState = reactive({
+      staffSrc: "",
+      isShow: false,
+      height:"initial"
+    })
+    const staffDom= ref<HTMLIFrameElement>()
+    const {playStaff, pauseStaff, updateProgressStaff} = staffMoveInstance()
     // 点赞
     const onStarChange = async () => {
       await checkLogin();
@@ -164,66 +160,184 @@ export default defineComponent({
         }
       });
     };
-
-    const initAudio = () => {
-      try {
-        audioDom.src = state.musicDetail.videoUrl;
-        audioDom.load();
-        audioDom.oncanplaythrough = () => {
-          state.paused = audioDom.paused;
-          state.duration = audioDom.duration;
-        };
-        // 播放时监听
-        audioDom.addEventListener('timeupdate', () => {
-          state.duration = audioDom.duration;
-          state.currentTime = audioDom.currentTime;
-          const rate = (state.currentTime / state.duration) * 100;
-          state.audioWidth = rate > 100 ? 100 : rate;
+    // 初始化 媒体播放
+    function initMediaPlay(){
+      const id = state.playType === "Audio" ? "#audioMediaSrc" : "#videoMediaSrc";
+      state._plrl = new Plyr(id, {
+        controls: ["progress"],
+        fullscreen: { enabled: false },
+      });
+      const player = state._plrl
+        // 创建音波数据
+      if(state.playType === "Audio"){
+        const audioDom = document.querySelector("#audioMediaSrc") as HTMLAudioElement
+        const canvasDom = document.querySelector("#audioVisualizer") as HTMLCanvasElement
+        const { pauseVisualDraw, playVisualDraw } = audioVisualDraw(audioDom, canvasDom)
+        player.on('play', () => {
+          lottieDom.value.play()
+          playVisualDraw()
         });
-        audioDom.addEventListener('ended', () => {
-          state.paused = audioDom.paused;
+        player.on('pause', () => {
+          lottieDom.value.pause()
+          pauseVisualDraw()
         });
-        // wavesurfer.value = WaveSurfer.create({
-        //   container: document.querySelector(`#${audioId}`) as HTMLElement,
-        //   waveColor: '#fff',
-        //   progressColor: '#2FA1FD',
-        //   url: state.musicDetail.videoUrl,
-        //   cursorWidth: 0,
-        //   height: 35,
-        //   width: 'auto',
-        //   normalize: true,
-        //   // Set a bar width
-        //   barWidth: 2,
-        //   // Optionally, specify the spacing between bars
-        //   barGap: 2,
-        //   // And the bar radius
-        //   barRadius: 4,
-        //   barHeight: 1.2,
-        //   /** If autoScroll is enabled, keep the cursor in the center of the waveform during playback */
-        //   // autoCenter: true,
-        //   hideScrollbar: false,
-        //   media: audioDom
-        // });
-        // // console.log(wavesurfer.value);
-
-        // // const wsRegions = wavesurfer.value.registerPlugin(Regions.create());
-        // wavesurfer.value.once('interaction', () => {
-        //   // wavesurfer.value.play();
-        // });
-        // wavesurfer.value.once('ready', () => {
-        //   state.paused = audioDom.paused;
-        //   state.duration = audioDom.duration;
-        // });
-
-        // wavesurfer.value.on('finish', () => {
-        //   state.paused = true;
-        // });
-      } catch (e) {
-        //
-        console.log(e);
       }
-    };
-
+      // 在微信中运行的时候,微信没有开放自动加载资源的权限,所以要等播放之后才显示播放控制器
+      player.on('loadedmetadata', () => {
+        plyrState.loaded = true
+      });
+      player.on("timeupdate", ()=>{
+        plyrState.currentTime = player.currentTime
+      })
+      player.on('play', () => {
+        plyrState.playIngShow = false
+        playStaff()
+      });
+      player.on('pause', () => {
+        plyrState.playIngShow = true
+        pauseStaff()
+      });
+      // 处理按压事件
+      const handleStart = () => {
+        plyrState.duration = player.duration
+        plyrState.mediaTimeShow = true
+      };
+      // 处理松开事件
+      const handleEnd = () => {
+        plyrState.mediaTimeShow = false
+        // 暂停的时候调用
+        if(!player.playing){
+          updateProgressStaff(player.currentTime)
+        }
+      };
+      const progressDom = document.querySelector("#playMediaSection .plyr__controls .plyr__progress__container") as HTMLElement
+      progressDom.addEventListener('mousedown', handleStart);
+      progressDom.addEventListener('touchstart', handleStart);
+      progressDom.addEventListener('mouseup', handleEnd);
+      progressDom.addEventListener('touchend', handleEnd);
+    }
+    //点击改变播放状态
+    function handlerClickPlay(){
+      if (state._plrl.playing) {
+        state._plrl.pause();
+      } else {
+        state._plrl.play();
+      }
+    }
+    /**
+     * 音频可视化
+     * @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.7)"
+          })
+          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
+      }
+    }
+    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),
+          musicSheetId:encodeURIComponent(state.musicDetail?.musicSheetId)
+        }
+      })
+    }
     const checkLogin = async () => {
       try {
         // 判断是否登录
@@ -251,234 +365,348 @@ export default defineComponent({
       await checkLogin();
       try {
         const res = await api_openUserMusicDetail(state.id);
-
         if (res.code === 999) {
-          state.message = res.message;
-          state.messageStatus = true;
+          // 没有的时候显示缺省页
+          state.isEmpty = true
           return;
         } else {
           state.musicDetail = res.data;
+          // 五线谱
+          initStaff()
           getList();
           // 判断是视频还是音频
-
           if (res.data.videoUrl.lastIndexOf('mp4') !== -1) {
             state.playType = 'Video';
           } else {
             state.playType = 'Audio';
-            // 初始化
-            nextTick(() => {
-              initAudio();
-            });
           }
-        }
-      } catch (err) {
-        //
-        state.listState.dataShow = false;
+          // 初始化
+          nextTick(() => {
+            initMediaPlay();
+          });
+}
+      } catch (err:any) {
+        // 没有的时候显示缺省页
+        state.message = err;
+        state.messageStatus = true;
       }
     };
-
+    // 滚动事件
+    const cleanScrollEvent = useEventListener('scroll', () => {
+      const height =
+        window.scrollY ||
+        document.documentElement.scrollTop
+        // 防止多次调用
+        if(height > 0 && isScreenScroll.value === false){
+          isScreenScroll.value = true
+        }
+        if(height <= 0){
+          isScreenScroll.value = false
+        }
+    })
+    function handlerDownLoad(){
+      router.push({
+        path:"/transfer"
+      })
+    }
     const pageVisibility = usePageVisibility();
     watch(pageVisibility, value => {
-      console.log(value);
       if (value === 'hidden') {
-        if (audioDom) {
-          audioDom.pause();
-          state.paused = audioDom.paused;
-        }
+        state._plrl?.pause();
       }
     });
+    // 初始化五线谱
+    function initStaff(){
+      const src = `${vaildMusicScoreUrl()}/instrument/#/simple-detail?id=${state.musicDetail.musicSheetId}&musicRenderType=staff`;
+      //const src = `http://192.168.3.68:3000/instrument.html#/simple-detail?id=${state.musicDetail.musicSheetId}&musicRenderType=staff`;
+      staffState.staffSrc = src
+      window.addEventListener('message', (event) => {
+        const { api, height } = event.data;
+        if (api === 'api_musicPage') {
+          staffState.isShow = true
+          staffState.height = height + "px"
+          // 如果是播放中自动开始 播放
+          if(state._plrl.playing){
+            playStaff()
+          }
+        }
+      });
+    }
+    function staffMoveInstance(){
+      let isPause = true
+      const requestAnimationFrameFun = () => {
+        requestAnimationFrame(() => {
+          staffDom.value?.contentWindow?.postMessage(
+            {
+              api: 'api_playProgress',
+              content: {
+                currentTime: state._plrl.currentTime
+              }
+            },
+            "*"
+          )
+          if (!isPause) {
+            requestAnimationFrameFun()
+          }
+        })
+      }
+      const playStaff = () => {
+        // 没渲染不执行
+        if(!staffState.isShow) return
+        isPause = false
+        staffDom.value?.contentWindow?.postMessage(
+          {
+            api: 'api_play'
+          },
+          "*"
+        )
+        requestAnimationFrameFun()
+      }
+      const pauseStaff = () => {
+        // 没渲染不执行
+        if(!staffState.isShow) return
+        isPause = true
+        staffDom.value?.contentWindow?.postMessage(
+          {
+            api: 'api_paused'
+          },
+          "*"
+        )
+      }
+      const updateProgressStaff = (currentTime: string) => {
+        // 没渲染不执行
+        if(!staffState.isShow) return
+        staffDom.value?.contentWindow?.postMessage(
+          {
+            api: 'api_updateProgress',
+            content: {
+              currentTime: state._plrl.currentTime
+            }
+          },
+          "*"
+        )
+      }
+      return {
+        playStaff,
+        pauseStaff,
+        updateProgressStaff
+      }
+    }
     onMounted(async () => {
       __init();
     });
 
     onUnmounted(() => {
-      if (audioDom) {
-        audioDom.pause();
-        state.paused = audioDom.paused;
-      }
+      cleanScrollEvent()
     });
 
     onBeforeRouteUpdate((to: any) => {
       state.id = to.query.id;
       state.playType = '';
       state.params.page = 1;
-      if (audioDom) {
-        audioDom.currentTime = 0;
-        audioDom.pause();
-        state.paused = audioDom.paused;
-      }
       state.list = [];
+      if(state._plrl){
+        state._plrl.destroy()
+      }
+      staffState.staffSrc = ""
+      staffState.isShow = false
+      staffState.height = "initial"
       __init();
     });
     return () => (
       <div
+        style={
+          {
+            '--barheight':state.heightV + "px"
+          }
+        }
         class={[
           styles.creation,
-          browser().isTablet ? styles.creationTablet : ''
+          browser().isTablet ? styles.creationTablet : '',
+          isScreenScroll.value && styles.isShareScreenScroll
         ]}>
-        {/* <video
-          src={state.musicDetail.videoUrl}
-          style="width: 100%;height: 200px"
-          controls></video> */}
-        <div class={styles.playSection}>
-          {state.playType === 'Video' && (
-            <MVideo
-              class={styles.videoSection}
-              src={state.musicDetail.videoUrl}
-              poster={state.musicDetail?.videoImg || videoBg}
-            />
-          )}
-          {state.playType === 'Audio' && (
-            <div class={styles.audioSection}>
-              <div class={styles.audioContainer}>
-                {/* <div
-                  id={audioId}
-                  onClick={(e: MouseEvent) => {
-                    e.stopPropagation();
-                  }}></div> */}
-                <div
-                  class={styles.waveActive}
-                  style={{
-                    width: state.audioWidth + '%'
-                  }}></div>
-                <div class={styles.waveDefault}></div>
-              </div>
-
-              <div class={styles.audioBox}>
-                <div
-                  class={[styles.audioPan, state.paused && styles.imgRotate]}>
-                  <Image class={styles.audioImg} src={state.musicDetail?.img} />
-                </div>
-                <i class={styles.audioPoint}></i>
-                <i
-                  class={[styles.audioZhen, state.paused && styles.active]}></i>
-              </div>
-              <div
-                class={[styles.controls]}
-                onClick={(e: Event) => {
-                  e.stopPropagation();
-                }}
-                onTouchmove={(e: TouchEvent) => {
-                  // emit('close');
-                }}>
-                <div class={styles.actions}>
-                  <div class={styles.actionBtn} onClick={onToggleAudio}>
-                    <img src={state.paused ? iconPlay : iconPause} />
-                  </div>
-                </div>
-                <div class={[styles.slider]}>
-                  <Slider
-                    step={0.01}
-                    class={styles.timeProgress}
-                    v-model={state.currentTime}
-                    max={state.duration}
-                    onUpdate:modelValue={val => {
-                      handleChangeTime(val);
-                    }}
-                    onDragStart={() => {
-                      state.dragStatus = true;
-                      console.log('onDragStart');
-                    }}
-                    onDragEnd={() => {
-                      state.dragStatus = false;
-                      console.log('onDragEnd');
-                    }}
-                  />
-                </div>
-                <div class={styles.time}>
-                  <div>{getSecondRPM(state.currentTime)}</div>
-                  <span>/</span>
-                  <div>{getSecondRPM(state.duration)}</div>
-                </div>
-              </div>
+        <div class={styles.creationBg}></div>
+        <MSticky position="top"
+          onBarHeight={(height: any) => {
+            console.log(height, 'height', height)
+            state.heightV = height
+          }}
+        >
+            <div class={styles.logoDownload}>
+              <img src={isScreenScroll.value ? logo1Img : logoImg} class={styles.logoImg}></img>
+              <div class={styles.logTit} onClick={handlerDownLoad}>下载App</div>
             </div>
-          )}
-        </div>
-
-        <Cell class={styles.userSection} center border={false}>
-          {{
-            icon: () => (
-              <Image class={styles.userLogo} src={state.musicDetail.avatar} />
-            ),
-            title: () => (
-              <div class={styles.userInfo}>
-                <p class={styles.name}>
-                  <span>{state.musicDetail.username}</span>
-                  {state.musicDetail.vipFlag && (
-                    <img src={iconMember} class={styles.iconMember} />
-                  )}
-                </p>
-                <p class={styles.sub}>
-                  {state.musicDetail.subjectName}{' '}
-                  {getGradeCh(state.musicDetail.currentGradeNum - 1)}
-                </p>
+        </MSticky>
+        {
+          state.isEmpty ?
+          <div class={styles.isEmpty}>
+            <MEmpty description="作品已删除~" />
+          </div> :
+          <>
+            <div class={styles.singerBox}>
+              <div class={styles.musicSheetName}>
+                {state.musicDetail?.musicSheetName}
               </div>
-            ),
-            value: () => (
-              <div
-                class={[
-                  styles.zan,
-                  state.musicDetail.starFlag && styles.zanActive
-                ]}
-                onClick={onStarChange}>
-                <img
-                  src={state.musicDetail.starFlag ? iconZanActive : iconZan}
-                  class={styles.iconZan}
-                />
-                {state.musicDetail.likeNum}
+              <div class={styles.singerName}>
+                演奏:{state.musicDetail?.username}
               </div>
-            )
-          }}
-        </Cell>
-
-        <div class={styles.musicSection}>
-          <div class={styles.musicName}>
-            <span class={styles.musicTag}>曲目名称</span>
-            {state.musicDetail?.musicSheetName}
-          </div>
-          {state.musicDetail?.desc && (
-            <div class={styles.musicDesc}>{state.musicDetail?.desc}</div>
-          )}
-        </div>
-
-        <div class={styles.likeSection}>
-          <div class={styles.likeTitle}>推荐作品</div>
-
-          {state.listState.dataShow ? (
-            <List
-              finished={state.listState.finished}
-              finishedText=" "
-              class={[styles.container, styles.containerInformation]}
-              onLoad={getList}
-              immediateCheck={false}>
-              <div class={styles.cellGroup}>
-                {state.list.map((item: any) => (
-                  <div class={styles.cell} onClick={() => onDetail(item)}>
-                    <div class={styles.cellImg}>
-                      <Image
-                        class={styles.cellImage}
-                        src={item.img}
-                        fit="cover"
+            </div>
+            <Sticky offsetTop={state.heightV - 1 + "px"}>
+              <div class={[styles.playSection, plyrState.mediaTimeShow && styles.mediaTimeShow,!plyrState.loaded && styles.notLoaded]} id="playMediaSection" onClick={handlerClickPlay}>
+                {
+                  state.playType &&
+                  <>
+                    {
+                      state.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={state.musicDetail?.videoUrl}
+                          controls="false"
+                          preload="metadata"
+                          playsinline
+                        />
+                      </div>
+                    }
+                    {
+                      state.playType === 'Video' &&
+                      <video
+                        id="videoMediaSrc"
+                        class={styles.videoBox}
+                        src={state.musicDetail?.videoUrl}
+                        data-poster={videobg}
+                        preload="metadata"
+                        playsinline
                       />
-
-                      <div class={styles.iconZan}>{item.likeNum}</div>
+                    }
+                    <div class={[styles.playLarge, plyrState.playIngShow && styles.playIngShow]}></div>
+                    <div class={styles.mediaTime}>
+                      <div>
+                        {getSecondRPM(plyrState.currentTime)}
+                      </div>
+                      <div class={styles.note}>/</div>
+                      <div class={styles.duration}>
+                        {getSecondRPM(plyrState.duration)}
+                      </div>
                     </div>
-                    <div class={[styles.cellTitle, 'van-ellipsis']}>
-                      {item.musicSheetName}
+                    <div class={styles.landscapeScreen} onClick={handlerLandscapeScreen}></div>
+                    {/* 谱面 */}
+                    {
+                      staffState.staffSrc &&
+                      <div
+                        class={[styles.staffBox, staffState.isShow && styles.staffBoxShow]}
+                        style={
+                          {
+                            '--staffBoxHeight':staffState.height
+                          }
+                        }
+                      >
+                        <div class={styles.mask}></div>
+                        <iframe
+                          ref={staffDom}
+                          class={styles.staff}
+                          frameborder="0"
+                          src={staffState.staffSrc}>
+                        </iframe>
+                      </div>
+                    }
+                  </>
+                }
+              </div>
+            </Sticky>
+            <div class={[styles.musicSection, styles.musicShareSection]}>
+              <div class={styles.avatarInfoBox}>
+                <div class={styles.avatar}>
+                  <Image class={styles.userLogo} src={state.musicDetail.avatar} />
+                  <div class={styles.infoCon}>
+                    <div class={styles.info}>
+                      <span class={styles.userName}>{state.musicDetail?.username}</span>
+                      {state.musicDetail.vipFlag && (
+                        <img src={iconMember} class={styles.iconMember} />
+                      )}
                     </div>
-                    <div class={styles.users}>
-                      <Image src={item.avatar} class={styles.userImg} />
-                      <span class={styles.name}>{item.username}</span>
+                    <div class={styles.sub}>
+                      {state.musicDetail.subjectName}{' '}
+                      {getGradeCh(state.musicDetail.currentGradeNum - 1)}
                     </div>
                   </div>
-                ))}
+                </div>
+                <div class={styles.linkes}  onClick={onStarChange}>
+                  <img src={state.musicDetail.starFlag ? iconZanActive : iconZan} class={styles.iconZan} />
+                  <span>{state.musicDetail.likeNum}</span>
+                </div>
               </div>
-            </List>
-          ) : (
-            <MEmpty description="暂无数据" />
-          )}
-        </div>
-
+              <TextEllipsis class={styles.textEllipsis} rows={2} content={state.musicDetail?.desc} expand-text="展开" collapse-text="收起" />
+            </div>
+            <div class={styles.likeSection}>
+              <div class={styles.likeTitle}>推荐作品</div>
+              {state.listState.dataShow ? (
+                <List
+                  finished={state.listState.finished}
+                  finishedText=" "
+                  class={[styles.container, styles.containerInformation]}
+                  onLoad={getList}
+                  immediateCheck={false}>
+                  {state.list.map((item: any, index: number) => (
+                    <Cell
+                      class={[styles.likeShareItem, index===state.list.length-1&&styles.likeShareItemLast]}
+                      border={false}
+                      onClick={() => onDetail(item)}
+                    >
+                      {{
+                        icon: () => (
+                          <div class={styles.audioImgBox}>
+                            <img
+                              src={audioPan}
+                              class={styles.audioPan}
+                              crossorigin="anonymous"
+                            />
+                            <img
+                              src={
+                                item.img || musicBg
+                              }
+                              class={styles.muploader}
+                              crossorigin="anonymous"
+                            />
+                            <img class={styles.imgLabel} src={item.videoUrl?.lastIndexOf('mp4') !== -1 ? videoLabel : audioLabel} />
+                          </div>
+                        ),
+                        title: () => (
+                          <div class={styles.userInfo}>
+                            <div class={[styles.musicSheetName,'van-ellipsis']}>{item.musicSheetName}</div>
+                            <div class={styles.usernameCon}>
+                              <div class={styles.likeNum}>
+                                <img src={iconZanActive} />
+                                <span>{item.likeNum}</span>
+                              </div>
+                              <div class={styles.username}>{item.username}</div>
+                            </div>
+                          </div>
+                        ),
+                        value: () => (
+                          <img src={playImg} class={styles.playImg} />
+                        )
+                      }}
+                    </Cell>
+                  ))}
+                </List>
+              ) : (
+                <MEmpty description="暂无数据" />
+              )}
+            </div>
+            {
+              !isScreenScroll.value &&
+              <MSticky position="bottom" offsetBottom={state.heightB - 1 + "px"} >
+                <div class={styles.upward}>
+                  <img src={iconUpward} />
+                </div>
+              </MSticky>
+            }
+          </>
+        }
         <Popup
           v-model:show={state.loginStatus}
           style={{ background: 'transparent', overflow: 'inherit' }}>
@@ -499,7 +727,6 @@ export default defineComponent({
             }}
           />
         </Popup>
-
         <Popup
           v-model:show={state.loginChangeState}
           style={{ background: 'transparent', overflow: 'inherit' }}>
@@ -517,7 +744,6 @@ export default defineComponent({
             }}
           />
         </Popup>
-
         <MWxTip
           v-model:show={state.messageStatus}
           message={state.message}

+ 461 - 574
src/views/creation/index.module.less

@@ -1,343 +1,272 @@
-.playSection {
-  min-height: 175px;
-
-  :global {
-    .vjs-poster {
-      background-size: cover;
-    }
-
-    .video-js .vjs-progress-control:hover .vjs-progress-holder {
-      font-size: inherit !important;
-      outline: none;
+.creationBg{
+  position: fixed;
+  z-index: -1;
+  width: 100vw;
+  height: 100vh;
+  top: 0;
+  left: 0;
+  background: url("./images/bg.png") no-repeat;
+  background-size: cover;
+}
+.creation{
+  :global{
+    .van-nav-bar .van-icon{
+      color: #ffffff;
     }
-
-    .video-js .vjs-slider:focus {
-      box-shadow: none !important;
-      text-shadow: none !important;
-      outline: none;
+  }
+  &.isScreenScroll{
+    :global{
+      .van-nav-bar .van-icon{
+        color: #333333;
+      }
     }
   }
 }
-
-@keyframes rotateImg {
-  100% {
-    transform: rotate(360deg);
-  }
+.singer{
+  text-align: center;
+  font-weight: 400;
+  font-size: 14px;
+  color: rgba(255,255,255,0.7);
+  line-height: 20px;
+  margin-bottom: 14vh;
 }
 
-.audioSection {
+.playSection{
+  height: 210px;
   position: relative;
-  background: url('./images/audio-banner-bg.png') no-repeat top center;
-  background-size: cover;
-  height: 175px;
-
-  .audioContainer {
-    position: absolute;
-    top: 0;
-    left: 50%;
-    width: 196px;
-    height: 35px;
-    transform: translate(-50%, 60px);
-
-    .waveActive,
-    .waveDefault {
+  :global {
+      .plyr {
+          width: 100%;
+          height: 100%;
+          .plyr__controls{
+              background: initial;
+              padding: 0 12px;
+              opacity: 1 !important;
+              transform: translateY(0) !important;
+              pointer-events: initial !important;
+              .plyr__controls__item.plyr__progress__container{
+                  input[type=range]{
+                      color: #73C1FF;
+                      height: 10px;
+                  }
+                  input[type="range"]::-webkit-slider-runnable-track {
+                      height: 2px;
+                  }
+                  input[type="range"]::-webkit-slider-thumb {
+                      width: 8px;
+                      height: 8px;
+                      margin-top: -3px;
+                  }
+                  .plyr__progress__buffer{
+                      height: 2px;
+                      color: rgba(115,193,255,0.8);
+                      background-color: #fff;
+                      margin-top: -1px;
+                  }
+              }
+          }
+      }
+  }
+  .videoBox{
+    width: 100%;
+    height: 100%;
+  }
+  .audioBox{
       width: 100%;
       height: 100%;
+      background: url("./images/audioBg.png") no-repeat;
+      background-size: 100% 100%;
+      position: relative;
+      .audioBga{
+        position: absolute;
+        left: 0;
+        bottom: 0;
+        width: 100%;
+        height: 100%;
+      }
+      :global {
+          .plyr {
+              position: absolute;
+              height: initial;
+              left: 0;
+              bottom: 0;
+          }
+      }
+      .audioVisualizer{
+          position: absolute;
+          top: 50%;
+          left: 50%;
+          transform: translate(-50%,-50%);
+          width: 308px;
+          height: 55px;
+      }
+  }
+  .playLarge{
+    position: absolute;
+    left: 50%;
+    top: 50%;
+    transform: translate(-50%, -50%);
+    width: 48px;
+    height: 48px;
+    background: url("./images/midPlay.png") no-repeat;
+    background-size: 100% 100%;
+    z-index: 12;
+    display: none;
+    &.playIngShow{
+      display: initial;
+    }
+  }
+  .mediaTime{
+    position: absolute;
+    left: 50%;
+    transform: translateX(-50%);
+    bottom: 56px;
+    display: none;
+    font-weight: 500;
+    font-size: 14px;
+    color: #FFFFFF;
+    line-height: 20px;
+    z-index: 10;
+    & div:first-child{
+      width: 50px;
+      text-align: right;
     }
-
-    .waveDefault {
-      position: absolute;
-      top: 0;
-      left: 0;
-      background: url('./images/wave-1.png')no-repeat center left;
-      background-size: cover;
+    .note{
+      margin: 0 4px;
     }
-
-    .waveActive {
-      position: absolute;
-      top: 0;
-      left: 0;
-      z-index: 1;
-      background: url('./images/wave-2.png')no-repeat center left;
-      background-size: cover;
+    .duration{
+      color: rgba(255,255,255,0.5);
     }
   }
-
-
-  .audioBox {
-    position: absolute;
-    left: 50%;
-    transform: translate(-50%, 50%);
-    z-index: 2;
-    width: 74px;
-    height: 75px;
-    background: url('./images/audio-bg.png') no-repeat center;
-    background-size: contain;
-
-    // &::after {
-    //   content: '';
-    //   width: 134px;
-    //   height: 73px;
-    //   position: absolute;
-    //   left: 50%;
-    //   transform: translate(-50%, 50%);
-    //   background: url('./images/audio-shadow.png') no-repeat center;
-    //   background-size: contain;
-    //   z-index: -1;
-    // }
-    .audioPan {
-      position: absolute;
-      left: 8px;
-      top: 6px;
-      z-index: 8;
-      width: 59px;
-      height: 60px;
-      background: url('./images/audio-pan.png') no-repeat center;
-      background-size: contain;
+  &.mediaTimeShow{
+    .mediaTime{
       display: flex;
-      align-items: center;
-      justify-content: center;
-
-      animation: rotateImg 6s linear infinite;
-
-      &.imgRotate {
-        animation-play-state: paused;
-      }
     }
-
-    .audioImg {
-      width: 32px;
-      height: 32px;
-      border-radius: 50%;
-      overflow: hidden;
+  }
+  .landscapeScreen{
+    width: 26px;
+    height: 26px;
+    position: absolute;
+    background: url("./images/Landscape.png") no-repeat;
+    background-size: 100% 100%;
+    right: 10px;
+    top: 10px;
+    z-index: 15;
+  }
+  .staffBox{
+    width: 100%;
+    height: var(--staffBoxHeight);
+    position: absolute;
+    bottom: 10px;
+    visibility: hidden;
+    &.staffBoxShow{
+      visibility: initial;
     }
-
-    .audioPoint {
-      position: absolute;
-      z-index: 9;
-      left: 50%;
-      top: 50%;
-      transform: translate(-50%, -50%);
-      width: 8px;
-      height: 8px;
-      background: url('./images/audio-point.png') no-repeat center;
-      background-size: contain;
+    .staff{
+      width: 100%;
+      height: 100%;
+      padding-left: 10px;
     }
-
-    .audioZhen {
+    .mask{
       position: absolute;
-      z-index: 9;
-      right: -4px;
-      top: -33px;
-      width: 26px;
-      height: 87px;
-      background: url('./images/audio-zhen.png') no-repeat center;
-      background-size: contain;
-      transition: transform .5s ease-in-out;
-
-      &.active {
-        transform: rotate(92deg) translate3d(0, 0, 3px);
-        transition: transform .5s ease-in-out;
-      }
+      z-index: 6;
+      width: 100%;
+      height: 100%;
     }
   }
-
 }
 
-.controls {
-  position: absolute;
-  left: 0;
-  bottom: 0;
-  right: 0;
-  height: 44px;
+.musicSection {
+  width: 100%;
+  min-height: calc(100vh - var(--barheight) - 20px - 14vh - 210px - 55px - 80px);
   display: flex;
   flex-direction: column;
-  justify-content: space-between;
-  flex-direction: row;
-  transition: all 0.5s;
-  padding: 0 12px;
+  justify-content: flex-end;
+  padding: 10px 12px 0;
 
-  &>div {
-    display: flex;
-    align-items: center;
-  }
-
-  &.hide {
-    transform: translateY(100%);
-  }
-
-
-
-  .actionBtn {
-    line-height: 0;
-    margin-right: 4px;
-
-    img {
-      width: 14px;
-      height: 14px;
-      margin-bottom: -2px;
-    }
-  }
-
-  .time {
+  .avatarInfoBox{
     display: flex;
     justify-content: space-between;
-    flex: 1;
-    min-width: 86px;
-    font-size: 12px;
-    color: #131415;
-    line-height: 20px;
-
-    span {
-      font-size: 12px;
-      padding: 0 1px;
-    }
-  }
-
-  .slider {
-    width: 100%;
-    margin: 0 12px;
-    --van-slider-bar-height: 4px;
-    --van-slider-button-width: 13px !important;
-    --van-slider-button-height: 13px !important;
-    --van-slider-inactive-background: #fff;
-    --van-slider-active-background: #269EFE !important;
-
-    :global {
-
-      .van-loading {
-        width: 100%;
-        height: 100%;
-      }
-
-    }
-  }
-
-}
-
-.userSection {
-  padding: 15px 12px;
-  background-color: transparent;
-
-  .userLogo {
-    width: 44px;
-    height: 44px;
-    border: 1px solid #FFFFFF;
-    margin-right: 10px;
-    border-radius: 50%;
-    overflow: hidden;
-  }
-
-  .userInfo {
-    .name {
+    align-items: center;
+    .avatar{
       display: flex;
       align-items: center;
-      font-size: 16px;
-      font-weight: 500;
-      color: #333333;
-      line-height: 22px;
-
-      span {
-        display: inline-block;
-        white-space: nowrap;
+      .userLogo{
+        width: 44px;
+        height: 44px;
+        border: 1px solid #FFFFFF;
+        margin-right: 10px;
+        border-radius: 50%;
         overflow: hidden;
-        text-overflow: ellipsis;
-        max-width: 100px;
+      }
+      .infoCon{
+        .info{
+          display: flex;
+          align-items: center;
+          .userName{
+            font-weight: 500;
+            font-size: 16px;
+            color: #FFFFFF;
+            line-height: 22px;
+          }
+          .iconMember{
+            margin-left: 6px;
+            width: 14px;
+            height: 14px;
+          }
+        }
+        .sub{
+          margin-top: 2px;
+          font-weight: 400;
+          font-size: 12px;
+          color: #FFFFFF;
+          line-height: 17px;
+        }
       }
     }
-
-    .sub {
-      padding-top: 2px;
-      font-size: 12px;
-      color: #777777;
-      line-height: 17px;
-    }
-
-    .iconMember {
-      margin-left: 6px;
-      width: 14px;
-      height: 14px;
-    }
-  }
-
-  .zan {
-    background: #FFFFFF;
-    border-radius: 13px;
-    font-size: 14px;
-    color: #777777;
-    line-height: 20px;
-    padding: 4px 9px 3px;
-    display: inline-flex;
-    align-items: center;
-
-    &.zanActive {
-      background: #F7EEEE;
-      color: #FF6A6A;
-    }
-
-    .iconZan {
-      width: 18px;
-      height: 18px;
-      margin-right: 2px;
-    }
-  }
-}
-
-.musicSection {
-  margin: 0 13px 12px;
-  padding: 14px 12px;
-  background: #FFFFFF;
-  border-radius: 10px;
-
-  .musicName {
-    font-size: 15px;
-    font-weight: 500;
-    color: #333333;
-    line-height: 21px;
-    // display: flex;
-    // align-items: center;
-    overflow: hidden;
-    text-overflow: ellipsis;
-    white-space: nowrap;
-    max-width: 100%;
-
-    .musicTag {
-      margin-right: 6px;
-      padding: 1px 6px;
-      font-size: 12px;
-      color: #FF7B31;
-      line-height: 17px;
-      background: rgba(255, 166, 115, 0.07);
-      border-radius: 9px;
-      border: 1px solid #FFBF9A;
+    .linkes{
+      display: flex;
+      align-items: center;
+      border-radius: 13px;
+      padding: 4px 8px 3px;
+      background-color: rgba(255,255,255,.12);
       font-weight: 400;
-      vertical-align: text-bottom;
+      font-size: 14px;
+      color: #FFFFFF;
+      .iconZan{
+        width: 18px;
+        height: 18px;
+        margin-right: 2px;
+      }
     }
   }
-
-  .musicDesc {
-    padding-top: 8px;
+  .textEllipsis{
+    margin-top: 10px;
+    font-weight: 400;
     font-size: 14px;
-    color: #777777;
+    color: #FFFFFF;
     line-height: 20px;
+    :global{
+      .van-text-ellipsis__action{
+        color: #FFFFFF;
+        font-weight: 500;
+      }
+    }
   }
 }
 
 .likeSection {
-  margin: 0 13px 12px;
-  background: #FFFFFF;
+  margin: 20px 12px;
+  background: rgba(255,255,255,.09);
   border-radius: 10px;
-  padding: 10px 12px;
-
+  padding: 12px 12px 0 12px;
   .likeTitle {
     display: flex;
     align-items: center;
     font-size: 17px;
     font-weight: 600;
-    color: #333333;
+    color: #ffffff;
     line-height: 24px;
-    padding-bottom: 8px;
-
     &::before {
       display: inline-block;
       content: '';
@@ -348,354 +277,312 @@
       margin-right: 6px;
     }
   }
-}
-
-.likeItem {
-  padding: 16px 0;
+  .likeItem {
+    padding: 13px 0 16px;
+    background-color: initial;
+    border-bottom: 1px solid rgba(242,242,242,0.12);
+    &.likeItemLast{
+      border-bottom: none;
+    }
+    .userLogo {
+      border-radius: 50%;
+      overflow: hidden;
+      width: 42px;
+      height: 42px;
+      margin-right: 7px;
+    }
 
-  .userLogo {
-    border-radius: 50%;
-    overflow: hidden;
-    width: 42px;
-    height: 42px;
-    margin-right: 7px;
-  }
+    .userInfo {
+      .name {
+        font-size: 16px;
+        font-weight: 500;
+        color: #ffffff;
+        line-height: 22px;
+      }
 
-  .userInfo {
-    .name {
-      font-size: 16px;
-      font-weight: 500;
-      color: #333333;
-      line-height: 22px;
+      .sub {
+        padding-top: 2px;
+        font-size: 13px;
+        color: #ffffff;
+        line-height: 18px;
+      }
     }
 
-    .sub {
-      padding-top: 2px;
+    .time {
+      font-weight: 400;
       font-size: 13px;
-      color: #777777;
+      color: #FFFFFF;
       line-height: 18px;
     }
   }
-
-  .time {
-    font-size: 13px;
-    color: #777777;
-    line-height: 18px;
+  .mEmpty{
+    padding: 0;
   }
 }
 
-
+.upward{
+  padding-top: 12px;
+  display: flex;
+  justify-content: center;
+  height: 55px;
+  background: linear-gradient( 180deg, rgba(20,39,85,0) 0%, rgba(24,46,85,0.77) 44%, #1B3454 100%);
+  > img{
+    width: 19px;
+    height: 15px;
+  }
+}
 .bottomSection {
   display: flex;
   align-items: center;
   justify-content: space-between;
-  background-color: #fff;
-  padding: 15px 12px;
-
+  padding: 0 12px 0 20px;
+  width: 100%;
+  height: 80px;
+  background: #1F1F1F;
+  box-shadow: 0px -1px 10px 0px rgba(0,0,0,0.05);
   .bottomShare {
     display: flex;
     align-items: center;
-
     p {
-      padding: 0 15px;
-      text-align: center;
-      line-height: 0;
-
-      &:first-child {
-        padding-left: 5px;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      margin-right: 28px;
+      &:last-child{
+        margin-right: 0;
       }
     }
-
     img {
       width: 18px;
       height: 18px;
     }
-
     span {
-      padding-top: 8px;
+      margin-top: 8px;
+      font-weight: 400;
       font-size: 12px;
-      color: #333333;
+      color: #ffffff;
       line-height: 17px;
-      display: block;
     }
   }
 
   .btnEdit {
-    font-size: 14px;
-    font-weight: 500;
-    background: linear-gradient(135deg, #19F1E1 0%, #0094FF 100%);
-    color: #FFFFFF;
-    line-height: 22px;
-    min-width: 80px;
+    width: 80px;
     height: 30px;
-    border: none;
   }
 }
 
 .popupContainer {
-  width: 80%;
+  width: 287px;
+  background: rgba(255,255,255,0.31);
+  border-radius: 12px !important;
+  border: 1px solid rgba(255,252,252,0.53);
+  padding: 9px 8px;
+  overflow: initial;
+  .prompt{
+    width: 151px;
+    height: 32px;
+    position: absolute;
+    top: -7px;
+    left: 50%;
+    transform: translateX(-50%);
+  }
+  .deleteBox{
+    background: linear-gradient( 224deg, #ECF5FF 0%, #D5E8FF 100%);
+    border-radius: 12px;
+    padding: 37px 0 15px;
+  }
 
 
   .popupContent {
-    padding: 29px 0 25px;
     text-align: center;
-    font-size: 18px;
-    font-weight: 500;
-    color: #333333;
-    line-height: 25px;
+    font-weight: 400;
+    font-size: 16px;
+    color: #334A64;
+    line-height: 26px;
   }
 
   .popupBtnGroup {
-    text-align: center;
-    margin-bottom: 22px;
-
-    :global {
-      .van-button {
-        height: 40px;
-        font-size: 16px;
-        font-weight: 400 !important;
-        line-height: 22px;
-        min-width: 122px;
-
-        &:last-child {
-          margin-left: 10px;
-          background: linear-gradient(to right, #5BECFF, #259CFE);
-          border: none;
-        }
+    margin-top: 18px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    >img{
+      width: 118px;
+      height: 37px;
+      & + img{
+        margin-left: 11px;
       }
     }
   }
 }
-
-.cellGroup {
-  display: flex;
-  flex-wrap: wrap;
-}
-
-.cell {
-  // display: flex;
-  // flex-direction: column;
-  width: 96px;
-  margin-right: 18px;
-  margin-bottom: 18px;
-
-  &:nth-child(3n + 3) {
-    margin-right: 0;
-  }
-
-  .cellImg {
-    position: relative;
-
-    &::before {
-      content: '';
-      position: absolute;
-      right: -8px;
-      top: 3px;
-      z-index: 8;
-      width: 84px;
-      height: 84px;
-      background: url('./images/audio-pan.png') no-repeat center;
-      background-size: contain;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-    }
-
-    .iconZan {
-      position: absolute;
-      bottom: 4px;
-      left: 4px;
-      z-index: 10;
-      padding: 3px;
-      background: rgba(67, 67, 67, 0.3);
-      border-radius: 8px;
-      backdrop-filter: blur(4px);
-
-      font-size: 9px;
-      font-weight: 500;
-      color: #FFFFFF;
-      line-height: 13px;
-      display: flex;
-      align-items: center;
-
-      &::before {
-        content: '';
-        display: inline-block;
-        width: 12px;
-        height: 12px;
-        background: url('./images/icon-z.png') no-repeat center;
-        background-size: contain;
-      }
-    }
-  }
-
-  .cellImage {
-    position: relative;
-    width: 88px;
-    height: 88px;
-    border-radius: 12px;
-    z-index: 9;
-
-    :global {
-      img {
-        border-radius: 12px;
-      }
+// 分享样式
+.playSection.notLoaded{
+  :global{
+    .plyr .plyr__controls {
+      display: none;
     }
   }
-
-  .cellTitle {
-    font-size: 13px;
-    color: #131415;
-    line-height: 18px;
-    margin: 8px 0 6px;
+}
+.logoDownload{
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 10px 13px;
+  position: relative;
+  &::after{
+    content: "";
+    position: absolute;
+    bottom: 0;
+    left: 13px;
+    width: calc(100% - 26px);
+    height: 1px;
+    background-color: rgba(255, 255, 255, 0.5) ;
+  }
+  .logoImg{
+    width: 159px;
+    height: 29px;
+  }
+  .logTit{
+    font-weight: 400;
+    font-size: 14px;
+    color: #FFFFFF;
+    line-height: 20px;
+    padding: 2px 10px;
+    border-radius: 20px;
+    border: 1px solid rgba(255, 255, 255, 0.5);
   }
-
-  .users {
-    display: flex;
-    align-items: center;
-
-    .userImg {
-      width: 20px;
-      height: 20px;
-      border-radius: 50%;
-      overflow: hidden;
-      margin-right: 4px;
-      flex-shrink: 0;
-    }
-
-    .name {
-      font-size: 12px;
-      color: #402424;
-      line-height: 14px;
+}
+.isShareScreenScroll{
+  .logoDownload{
+    background-color: #ffffff;
+    .logTit{
+      background: linear-gradient( 90deg, #44C9FF 0%, #259CFE 100%);
+      border: none;
+      padding: 3px 11px;
     }
   }
 }
-
-// 平板样式
-.creationTablet {
-
-  .videoSection {
-    height: 390px;
+.singerBox{
+  height: 20vh;
+  display: flex;
+  flex-direction: column;
+  justify-content: end;
+  .musicSheetName{
+    text-align: center;
+    font-weight: 600;
+    font-size: 20px;
+    color: #FFFFFF;
+    line-height: 28px;
+    margin-bottom: 10px;
   }
-
-  .audioSection {
-    height: 300px;
-
-    .audioContainer {
-      width: 400px;
-      height: 70px;
-      transform: translate(-50%, 120px);
-    }
-
-    .audioBox {
-      width: 152px;
-      height: 155px;
-
-      .audioPan {
-        left: 17px;
-        top: 14px;
-        width: 119px;
-        height: 120px;
-      }
-
-      .audioImg {
-        width: 65px;
-        height: 65px;
-      }
-
-      .audioPoint {
-        width: 14px;
-        height: 14px;
-      }
-
-      .audioZhen {
-        width: 52px;
-        height: 176px;
-        top: -60px;
-      }
-    }
+  .singerName{
+    text-align: center;
+    font-weight: 400;
+    font-size: 14px;
+    color: rgba(255,255,255,0.7);
+    line-height: 20px;
+    margin-bottom: 10px;
   }
-
-  .bottomSection {
-    .btnEdit {
-      height: 42px;
-      font-size: 18px;
-      padding: 0 32px
-    }
+}
+.musicShareSection{
+  min-height: calc(100vh - var(--barheight) - 20vh - 210px - 55px);
+}
+.likeShareItem{
+  background-color: initial;
+  padding: 0;
+  margin-top: 25px;
+  &:first-child{
+    margin-top: 20px;
   }
-
-  .bottomShare {
-    img {
-      width: 24px;
-      height: 24px;
-    }
-
-    span {
-      font-size: 15px;
-    }
+  &.likeShareItemLast{
+    padding-bottom: 20px;
   }
-
-  .controls {
-    .slider {
+  .audioImgBox{
+    position: relative;
+    width: 51px;
+    height: 51px;
+    margin-right: 14px;
+    .audioPan{
+      position: absolute;
       width: 100%;
-      margin: 0 12px;
-      --van-slider-bar-height: 8px;
-      --van-slider-button-width: 20px !important;
-      --van-slider-button-height: 20px !important;
-      --van-slider-inactive-background: #fff;
-      --van-slider-active-background: #269EFE !important;
+      height: 100%;
+      right: -6px;
+      top: 0;
     }
-
-    .actionBtn {
-      img {
-        width: 20px;
-        height: 20px;
-      }
+    .muploader{
+      position: relative;
+      z-index: 1;
+      width: 100%;
+      height: 100%;
+      border-radius: 6px;
     }
-
-    .time {
-      font-size: 16px;
+    .imgLabel{
+      position: absolute;
+      right: 0;
+      top: 0;
+      width: 28px;
+      height: 14px;
+      z-index: 10;
     }
   }
-
-  .cell {
-    .cellImage {
-      width: 99px;
-      height: 99px;
-
-
+  .userInfo{
+    .musicSheetName{
+      font-weight: 600;
+      font-size: 16px;
+      color: #FFFFFF;
+      line-height: 22px;
+      width: 200px;
     }
-
-    .cellImg {
-      &::before {
-        top: 2px;
-        width: 95px;
-        height: 95px;
-        right: -12px;
-      }
-
-      .iconZan {
-        font-size: 12px;
-        bottom: 8px;
-        left: 5px;
-        border-radius: 20px;
-        padding-right: 5px;
-
-
-        &::before {
-          padding-right: 3px;
+    .usernameCon{
+      display: flex;
+      align-items: center;
+      margin-top: 6px;
+      .likeNum{
+        display: flex;
+        border-radius: 3px;
+        background-color: rgba(255,255,255,.22);
+        padding: 1px 2px 1px 1px;
+        img{
+          width: 14px;
+          height: 15px;
+        }
+        span{
+          font-weight: 400;
+          font-size: 10px;
+          color: #FFFFFF;
+          line-height: 14px;
+          margin-left: 2px;
         }
       }
+      .username{
+        margin-left: 4px;
+        font-weight: 400;
+        font-size: 13px;
+        color: #DEDEDE;
+        line-height: 13px;
+      }
     }
-
-    margin-right: 34px;
-
-    &:nth-child(3n + 3) {
-      margin-right: 30px;
-    }
-
-    &:nth-child(5n + 5) {
-      margin-right: 0;
+  }
+  :global{
+    .van-cell__value{
+      display: flex;
+      align-items: center;
+      justify-content: end;
     }
   }
-}
+  .playImg{
+    width: 20px;
+    height: 20px;
+  }
+}
+.isEmpty{
+  height: calc(100vh - var(--barheight));
+  display: flex;
+  align-items: center;
+}
+
+// 平板样式
+.creationTablet{
+
+}

+ 441 - 242
src/views/creation/index.tsx

@@ -4,7 +4,8 @@ import {
   onMounted,
   onUnmounted,
   reactive,
-  watch
+  watch,
+  ref
 } from 'vue';
 // import WaveSurfer from 'wavesurfer.js';
 import styles from './index.module.less';
@@ -18,18 +19,25 @@ import {
   Popup,
   Slider,
   showDialog,
-  showToast
+  showToast,
+  Sticky,
+  TextEllipsis
 } from 'vant';
 import iconDownload from './images/icon-download.png';
 import iconShare from './images/icon-share.png';
 import iconDelete from './images/icon-delete.png';
+import iconEdit from './images/edit.png';
+import iconUpward from './images/upward.png';
 import iconMember from './images/icon-member.png';
 import iconZan from './images/icon-zan.png';
+import promptImg from './images/prompt.png';
+import confirmImg from './images/confirm.png';
+import canceImg from './images/cance.png';
 import iconZanActive from './images/icon-zan-active.png';
 import iconPlay from './images/icon-play.png';
 import iconPause from './images/icon-pause.png';
 import { postMessage, promisefiyPostMessage } from '@/helpers/native-message';
-import { browser, getGradeCh, getSecondRPM } from '@/helpers/utils';
+import { browser, getGradeCh, getSecondRPM, vaildMusicScoreUrl } from '@/helpers/utils';
 import { useRoute, useRouter } from 'vue-router';
 import {
   api_userMusicDetail,
@@ -40,29 +48,26 @@ import MEmpty from '@/components/m-empty';
 import dayjs from 'dayjs';
 import MVideo from '@/components/m-video';
 import ShareModel from './share-model';
-import { usePageVisibility } from '@vant/use';
-import videoBg from './images/video-bg.png';
+import { usePageVisibility, useEventListener } from '@vant/use';
+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";
 
 export default defineComponent({
   name: 'creation-detail',
   setup() {
     const route = useRoute();
     const router = useRouter();
-    const audioId = 'a' + +Date.now() + Math.floor(Math.random() * 100);
-
+    const isScreenScroll = ref(false)
+    const lottieDom = ref()
     const state = reactive({
       id: route.query.id,
       deleteStatus: false,
       shareStatus: false,
       playType: '' as 'Audio' | 'Video' | '', // 播放类型
       musicDetail: {} as any,
-      timer: null as any,
-      audioWidth: 0,
-      paused: true,
-      currentTime: 0,
-      duration: 0.1,
-      loop: false,
-      dragStatus: false, // 是否开始拖动
       isClick: false,
       list: [] as any,
       listState: {
@@ -73,36 +78,25 @@ export default defineComponent({
       params: {
         page: 1,
         rows: 20
-      }
+      },
+      _plrl: null as any,
+      heightV:0,
+      heightB:0
     });
-    const audioDom = new Audio();
-    audioDom.controls = true;
-    audioDom.style.width = '100%';
-    audioDom.className = styles.audio;
-
-    /** 改变播放时间 */
-    const handleChangeTime = (val: number) => {
-      state.currentTime = val;
-      clearTimeout(state.timer);
-      state.timer = setTimeout(() => {
-        // audioRef.value.currentTime = val;
-        audioDom.currentTime = val;
-        state.timer = null;
-      }, 60);
-    };
-
-    // 切换音频播放
-    const onToggleAudio = (e: any) => {
-      e.stopPropagation();
-      if (audioDom.paused) {
-        audioDom.play();
-      } else {
-        audioDom.pause();
-      }
-
-      state.paused = audioDom.paused;
-    };
-
+    const plyrState = reactive({
+      duration: 0,
+      currentTime: 0,
+      mediaTimeShow: false,
+      playIngShow: true
+    })
+    // 谱面
+    const staffState = reactive({
+      staffSrc: "",
+      isShow: false,
+      height:"initial"
+    })
+    const staffDom= ref<HTMLIFrameElement>()
+    const {playStaff, pauseStaff, updateProgressStaff} = staffMoveInstance()
     // 获取列表
     const getStarList = async () => {
       try {
@@ -129,70 +123,10 @@ export default defineComponent({
         state.isClick = false;
       }
     };
-
-    const initAudio = () => {
-      audioDom.src = state.musicDetail.videoUrl;
-      audioDom.load();
-      audioDom.oncanplaythrough = () => {
-        state.paused = audioDom.paused;
-        state.duration = audioDom.duration;
-      };
-      // 播放时监听
-      audioDom.addEventListener('timeupdate', () => {
-        state.duration = audioDom.duration;
-        state.currentTime = audioDom.currentTime;
-        const rate = (state.currentTime / state.duration) * 100;
-        state.audioWidth = rate > 100 ? 100 : rate;
-      });
-      audioDom.addEventListener('ended', () => {
-        state.paused = audioDom.paused;
-      });
-      // const wavesurfer = WaveSurfer.create({
-      //   container: document.querySelector(`#${audioId}`) as HTMLElement,
-      //   waveColor: '#fff',
-      //   progressColor: '#2FA1FD',
-      //   url: state.musicDetail.videoUrl,
-      //   cursorWidth: 0,
-      //   height: 35,
-      //   width: 'auto',
-      //   normalize: true,
-      //   // Set a bar width
-      //   barWidth: 2,
-      //   // Optionally, specify the spacing between bars
-      //   barGap: 2,
-      //   // And the bar radius
-      //   barRadius: 4,
-      //   barHeight: 0.6,
-      //   autoScroll: true,
-      //   /** If autoScroll is enabled, keep the cursor in the center of the waveform during playback */
-      //   autoCenter: true,
-      //   hideScrollbar: false,
-      //   media: audioDom
-      // });
-
-      // wavesurfer.once('interaction', () => {
-      //   // wavesurfer.play();
-      // });
-      // wavesurfer.once('ready', () => {
-      //   state.paused = audioDom.paused;
-      //   state.duration = audioDom.duration;
-      // });
-
-      // wavesurfer.on('finish', () => {
-      //   state.paused = true;
-      // });
-
-      // // 播放时监听
-      // audioDom.addEventListener('timeupdate', () => {
-      //   state.currentTime = audioDom.currentTime;
-      // });
-    };
-
     // 删除作品
     const onDelete = async () => {
       try {
         await api_userMusicRemove({ id: state.id });
-
         setTimeout(() => {
           state.deleteStatus = false;
           showToast('删除成功');
@@ -221,17 +155,281 @@ export default defineComponent({
         }
       });
     };
-
-    const pageVisibility = usePageVisibility();
-    watch(pageVisibility, value => {
-      if (value === 'hidden') {
-        if (audioDom) {
-          audioDom.pause();
-          state.paused = audioDom.paused;
+    // 滚动事件
+    const cleanScrollEvent = useEventListener('scroll', () => {
+      const height =
+        window.scrollY ||
+        document.documentElement.scrollTop
+        // 防止多次调用
+        if(height > 0 && isScreenScroll.value === false){
+          isScreenScroll.value = true
+          setStatusBarTextColor(false)
+        }
+        if(height <= 0){
+          isScreenScroll.value = false
+          setStatusBarTextColor(true)
         }
+    })
+    // 设置导航栏颜色
+    function setStatusBarTextColor(isWhite:boolean){
+      postMessage({
+        api: 'setStatusBarTextColor',
+        content: { statusBarTextColor: isWhite }
+      })
+    }
+    // 初始化 媒体播放
+    function initMediaPlay(){
+      const id = state.playType === "Audio" ? "#audioMediaSrc" : "#videoMediaSrc";
+      state._plrl = new Plyr(id, {
+        controls: ["progress"],
+        fullscreen: { enabled: false },
+      });
+      const player = state._plrl
+        // 创建音波数据
+      if(state.playType === "Audio"){
+        const audioDom = document.querySelector("#audioMediaSrc") as HTMLAudioElement
+        const canvasDom = document.querySelector("#audioVisualizer") as HTMLCanvasElement
+        const { pauseVisualDraw, playVisualDraw } = audioVisualDraw(audioDom, canvasDom)
+        player.on('play', () => {
+          lottieDom.value.play()
+          playVisualDraw()
+        });
+        player.on('pause', () => {
+          lottieDom.value.pause()
+          pauseVisualDraw()
+        });
       }
-    });
+      player.on("timeupdate", ()=>{
+        plyrState.currentTime = player.currentTime
+      })
+      player.on('play', () => {
+        plyrState.playIngShow = false
+        playStaff()
+      });
+      player.on('pause', () => {
+        plyrState.playIngShow = true
+        pauseStaff()
+      });
+      // 处理按压事件
+      const handleStart = () => {
+        plyrState.duration = player.duration
+        plyrState.mediaTimeShow = true
+      };
+      // 处理松开事件
+      const handleEnd = () => {
+        plyrState.mediaTimeShow = false
+        // 暂停的时候调用
+        if(!player.playing){
+          updateProgressStaff(player.currentTime)
+        }
+      };
+      const progressDom = document.querySelector("#playMediaSection .plyr__controls .plyr__progress__container") as HTMLElement
+      progressDom.addEventListener('mousedown', handleStart);
+      progressDom.addEventListener('touchstart', handleStart);
+      progressDom.addEventListener('mouseup', handleEnd);
+      progressDom.addEventListener('touchend', handleEnd);
+    }
+    //点击改变播放状态
+    function handlerClickPlay(){
+      if (state._plrl.playing) {
+        state._plrl.pause();
+      } else {
+        state._plrl.play();
+      }
+    }
+    /**
+		 * 音频可视化
+		 * @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.7)"
+          })
+          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
+      }
+    }
+    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),
+          musicSheetId:encodeURIComponent(state.musicDetail?.musicSheetId)
+        }
+      })
+    }
+    // 初始化五线谱
+    function initStaff(){
+      const src = `${vaildMusicScoreUrl()}/instrument/#/simple-detail?id=${state.musicDetail.musicSheetId}&musicRenderType=staff`;
+      //const src = `http://192.168.3.68:3000/instrument.html#/simple-detail?id=${state.musicDetail.musicSheetId}&musicRenderType=staff`;
+      staffState.staffSrc = src
+      window.addEventListener('message', (event) => {
+        const { api, height } = event.data;
+        if (api === 'api_musicPage') {
+          staffState.isShow = true
+          staffState.height = height + "px"
+          // 如果是播放中自动开始 播放
+          if(state._plrl.playing){
+            playStaff()
+          }
+        }
+      });
+    }
+    function staffMoveInstance(){
+      let isPause = true
+      const requestAnimationFrameFun = () => {
+				requestAnimationFrame(() => {
+          staffDom.value?.contentWindow?.postMessage(
+            {
+              api: 'api_playProgress',
+              content: {
+                currentTime: state._plrl.currentTime
+              }
+            },
+            "*"
+          )
+					if (!isPause) {
+						requestAnimationFrameFun()
+					}
+				})
+			}
+      const playStaff = () => {
+        // 没渲染不执行
+        if(!staffState.isShow) return
+				isPause = false
+        staffDom.value?.contentWindow?.postMessage(
+          {
+            api: 'api_play'
+          },
+          "*"
+        )
+				requestAnimationFrameFun()
+			}
+			const pauseStaff = () => {
+        // 没渲染不执行
+        if(!staffState.isShow) return
+				isPause = true
+        staffDom.value?.contentWindow?.postMessage(
+          {
+            api: 'api_paused'
+          },
+          "*"
+        )
+			}
+      const updateProgressStaff = (currentTime: string) => {
+        // 没渲染不执行
+        if(!staffState.isShow) return
+        staffDom.value?.contentWindow?.postMessage(
+          {
+            api: 'api_updateProgress',
+            content: {
+              currentTime: state._plrl.currentTime
+            }
+          },
+          "*"
+        )
+      }
+			return {
+				playStaff,
+				pauseStaff,
+        updateProgressStaff
+			}
+    }
     onMounted(async () => {
+      setStatusBarTextColor(true)
       try {
         const res = await api_userMusicDetail(state.id);
         // console.log(res);
@@ -253,167 +451,156 @@ export default defineComponent({
           return;
         }
         state.musicDetail = res.data || {};
+        // 五线谱
+        initStaff()
         getStarList();
         // 判断是视频还是音频
         if (res.data.videoUrl.lastIndexOf('mp4') !== -1) {
           state.playType = 'Video';
         } else {
           state.playType = 'Audio';
-          // 初始化
-          nextTick(() => {
-            initAudio();
-          });
         }
+        nextTick(()=>{
+          initMediaPlay()
+        })
       } catch {
         //
       }
     });
 
     onUnmounted(() => {
-      if (audioDom) {
-        audioDom.pause();
-        state.paused = audioDom.paused;
-      }
+      setStatusBarTextColor(false)
+      cleanScrollEvent()
     });
     return () => (
       <div
+        style={
+          {
+            '--barheight':state.heightV + "px"
+          }
+        }
         class={[
           styles.creation,
-          browser().isTablet ? styles.creationTablet : ''
+          browser().isTablet && styles.creationTablet,
+          isScreenScroll.value && styles.isScreenScroll
         ]}>
-        <MSticky position="top">
+        <div class={styles.creationBg}></div>
+        <MSticky position="top"
+          onBarHeight={(height: any) => {
+            console.log(height, 'height', height)
+            state.heightV = height
+          }}
+        >
           <MHeader
+            color={isScreenScroll.value ? "#333333" : "#ffffff"}
+            background={isScreenScroll.value ? `rgb(255,255,255` : "transparent"}
+            title={state.musicDetail?.musicSheetName}
             border={false}
             isBack={route.query.platformType != 'ANALYSIS'}
           />
         </MSticky>
-        <div class={styles.playSection}>
-          {state.playType === 'Video' && (
-            <MVideo
-              class={styles.videoSection}
-              src={state.musicDetail?.videoUrl}
-              poster={state.musicDetail?.videoImg || videoBg}
-            />
-          )}
-          {state.playType === 'Audio' && (
-            <div class={styles.audioSection}>
-              <div class={styles.audioContainer}>
-                {/* <div
-                  id={audioId}
-                  onClick={(e: MouseEvent) => {
-                    e.stopPropagation();
-                  }}></div> */}
-                <div
-                  class={styles.waveActive}
-                  style={{
-                    width: state.audioWidth + '%'
-                  }}></div>
-                <div class={styles.waveDefault}></div>
-              </div>
-
+        <div class={styles.singer}>
+          演奏:{state.musicDetail?.username}
+        </div>
+        <Sticky offsetTop={state.heightV - 1 + "px"}>
+          <div class={[styles.playSection, plyrState.mediaTimeShow && styles.mediaTimeShow]} id="playMediaSection" onClick={handlerClickPlay}>
+            {
+              state.playType === 'Audio' &&
               <div class={styles.audioBox}>
-                <div
-                  class={[styles.audioPan, state.paused && styles.imgRotate]}>
-                  <Image class={styles.audioImg} src={state.musicDetail?.img} />
-                </div>
-                <i class={styles.audioPoint}></i>
-                <i
-                  class={[styles.audioZhen, state.paused && styles.active]}></i>
+                <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={state.musicDetail?.videoUrl}
+                  controls="false"
+                  preload="metadata"
+                  playsinline
+                />
               </div>
-              <div
-                class={[styles.controls]}
-                onClick={(e: Event) => {
-                  e.stopPropagation();
-                }}
-                onTouchmove={(e: TouchEvent) => {
-                  // emit('close');
-                }}>
-                <div class={styles.actions}>
-                  <div class={styles.actionBtn} onClick={onToggleAudio}>
-                    <img src={state.paused ? iconPlay : iconPause} />
-                  </div>
-                </div>
-                <div class={[styles.slider]}>
-                  <Slider
-                    step={0.01}
-                    class={styles.timeProgress}
-                    v-model={state.currentTime}
-                    max={state.duration}
-                    onUpdate:modelValue={val => {
-                      handleChangeTime(val);
-                    }}
-                    onDragStart={() => {
-                      state.dragStatus = true;
-                      console.log('onDragStart');
-                    }}
-                    onDragEnd={() => {
-                      state.dragStatus = false;
-                      console.log('onDragEnd');
-                    }}
-                  />
-                </div>
-                <div class={styles.time}>
-                  <div>{getSecondRPM(state.currentTime)}</div>
-                  <span>/</span>
-                  <div>{getSecondRPM(state.duration)}</div>
-                </div>
+            }
+            {
+              state.playType === 'Video' &&
+              <video
+                id="videoMediaSrc"
+                class={styles.videoBox}
+                src={state.musicDetail?.videoUrl}
+                data-poster={videobg}
+                preload="metadata"
+                playsinline
+              />
+            }
+            <div class={[styles.playLarge, plyrState.playIngShow && styles.playIngShow]}></div>
+            <div class={styles.mediaTime}>
+              <div>
+                {getSecondRPM(plyrState.currentTime)}
+              </div>
+              <div class={styles.note}>/</div>
+              <div class={styles.duration}>
+                {getSecondRPM(plyrState.duration)}
               </div>
             </div>
-          )}
-        </div>
-
-        <Cell class={styles.userSection} center border={false}>
-          {{
-            icon: () => (
+            <div class={styles.landscapeScreen} onClick={handlerLandscapeScreen}></div>
+            {/* 谱面 */}
+            {
+              staffState.staffSrc &&
+              <div
+                class={[styles.staffBox, staffState.isShow && styles.staffBoxShow]}
+                style={
+                  {
+                    '--staffBoxHeight':staffState.height
+                  }
+                }
+              >
+                <div class={styles.mask}></div>
+                <iframe
+                  ref={staffDom}
+                  class={styles.staff}
+                  frameborder="0"
+                  src={staffState.staffSrc}>
+                </iframe>
+              </div>
+            }
+          </div>
+        </Sticky>
+        <div class={styles.musicSection}>
+          <div class={styles.avatarInfoBox}>
+            <div class={styles.avatar}>
               <Image class={styles.userLogo} src={state.musicDetail.avatar} />
-            ),
-            title: () => (
-              <div class={styles.userInfo}>
-                <p class={styles.name}>
-                  <span>{state.musicDetail?.username}</span>
+              <div class={styles.infoCon}>
+                <div class={styles.info}>
+                  <span class={styles.userName}>{state.musicDetail?.username}</span>
                   {state.musicDetail.vipFlag && (
                     <img src={iconMember} class={styles.iconMember} />
                   )}
-                </p>
-                <p class={styles.sub}>
+                </div>
+                <div class={styles.sub}>
                   {state.musicDetail.subjectName}{' '}
                   {getGradeCh(state.musicDetail.currentGradeNum - 1)}
-                </p>
-              </div>
-            ),
-            value: () => (
-              <div class={[styles.zan, styles.zanActive]}>
-                <img src={iconZanActive} class={styles.iconZan} />
-                {state.musicDetail.likeNum}
+                </div>
               </div>
-            )
-          }}
-        </Cell>
-
-        <div class={styles.musicSection}>
-          <div class={styles.musicName}>
-            <span class={styles.musicTag}>曲目名称</span>
-            {state.musicDetail?.musicSheetName}
+            </div>
+            <div class={styles.linkes}>
+              <img src={iconZan} class={styles.iconZan} />
+              <span>{state.musicDetail.likeNum}</span>
+            </div>
           </div>
-          {state.musicDetail.desc && (
-            <div class={styles.musicDesc}>{state.musicDetail.desc}</div>
-          )}
+          <TextEllipsis class={styles.textEllipsis} rows={2} content={state.musicDetail?.desc} expand-text="展开" collapse-text="收起" />
         </div>
 
         <div class={styles.likeSection}>
           <div class={styles.likeTitle}>点赞记录</div>
-
           {state.listState.dataShow ? (
             <List
               finished={state.listState.finished}
               finishedText=" "
-              class={[styles.container, styles.containerInformation]}
               onLoad={getStarList}
               immediateCheck={false}>
               {state.list.map((item: any, index: number) => (
                 <Cell
-                  class={styles.likeItem}
-                  border={state.list.length - 1 == index ? false : true}>
+                  class={[styles.likeItem, index===state.list.length-1&&styles.likeItemLast]}
+                  border={false}
+                >
                   {{
                     icon: () => (
                       <Image src={item.userAvatar} class={styles.userLogo} />
@@ -437,11 +624,21 @@ export default defineComponent({
               ))}
             </List>
           ) : (
-            <MEmpty description="暂无数据" />
+            <MEmpty class={styles.mEmpty} description="暂无数据" />
           )}
         </div>
-
-        <MSticky position="bottom">
+        {
+          !isScreenScroll.value &&
+          <MSticky position="bottom" offsetBottom={state.heightB - 1 + "px"} >
+            <div class={styles.upward}>
+              <img src={iconUpward} />
+            </div>
+          </MSticky>
+        }
+        <MSticky position="bottom" onBarHeight={(height: any) => {
+            console.log(height, 'height', height)
+            state.heightB = height
+        }}>
           <div class={styles.bottomSection}>
             <div class={styles.bottomShare}>
               <p onClick={onDownload}>
@@ -457,10 +654,8 @@ export default defineComponent({
                 <span>删除</span>
               </p>
             </div>
-            <Button
-              round
+            <img src={iconEdit}
               class={styles.btnEdit}
-              type="primary"
               onClick={() => {
                 router.push({
                   path: '/creation-edit',
@@ -468,25 +663,28 @@ export default defineComponent({
                     id: state.id
                   }
                 });
-              }}>
-              编辑
-            </Button>
+              }}
+            />
           </div>
         </MSticky>
 
         <Popup
           v-model:show={state.deleteStatus}
+          overlay-style={
+            {
+              backgroundColor:"rgba(0,0,0,.5)"
+            }
+          }
           round
           class={styles.popupContainer}>
-          <p class={styles.popupContent}>确定删除吗?</p>
-          <div class={styles.popupBtnGroup}>
-            <Button round onClick={() => (state.deleteStatus = false)}>
-              取消
-            </Button>
-            <Button round type="primary" onClick={onDelete}>
-              确定
-            </Button>
-          </div>
+            <img class={styles.prompt} src={promptImg} />
+            <div class={styles.deleteBox}>
+              <p class={styles.popupContent}>确定删除吗?</p>
+              <div class={styles.popupBtnGroup}>
+                <img src={canceImg} onClick={() => (state.deleteStatus = false)} />
+                <img src={confirmImg} onClick={onDelete} />
+              </div>
+            </div>
         </Popup>
 
         <Popup
@@ -494,6 +692,7 @@ export default defineComponent({
           v-model:show={state.shareStatus}
           style={{ background: 'transparent' }}>
           <ShareModel
+            playType={state.playType}
             musicDetail={state.musicDetail}
             onClose={() => (state.shareStatus = false)}
           />

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

@@ -0,0 +1,192 @@
+.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 .plyr__controls__item.plyr__progress__container input{
+        pointer-events: none;
+      }
+    }
+  }
+  &.notLoaded{
+    :global{
+      .plyr .plyr__controls {
+        display: none;
+      }
+    }
+  }
+  :global {
+      .plyr {
+          width: 100%;
+          height: 100%;
+          .plyr__controls{
+              background: initial;
+              padding: 0 20px 20px;
+              .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;
+                  }
+              }
+
+          }
+          .plyr__tooltip{
+            display: none;
+          }
+      }
+  }
+  .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;
+      }
+  }
+  .playLarge{
+    position: absolute;
+    left: 50%;
+    top: 50%;
+    transform: translate(-50%, -50%);
+    width: 48px;
+    height: 48px;
+    background: url("../images/midPlay.png") no-repeat;
+    background-size: 100% 100%;
+    z-index: 12;
+    display: none;
+    &.playIngShow{
+      display: initial;
+    }
+  }
+  .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
+      }
+    }
+  }
+  .staffBox{
+    width: 100%;
+    height: var(--staffBoxHeight);
+    position: absolute;
+    bottom: 44px;
+    visibility: hidden;
+    &.staffBoxShow{
+      visibility: initial;
+    }
+    .staff{
+      width: 100%;
+      height: 100%;
+      padding-left: 10px;
+    }
+    .mask{
+      position: absolute;
+      z-index: 6;
+      width: 100%;
+      height: 100%;
+    }
+  }
+}

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

@@ -0,0 +1,441 @@
+import { defineComponent, onMounted, reactive, ref, onUnmounted, watch } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import { browser, vaildMusicScoreUrl } 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';
+import { usePageVisibility } from '@vant/use';
+
+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 musicSheetId = decodeURIComponent(route.query.musicSheetId as string || '');
+    const playType = resourceUrl.lastIndexOf('mp4') !== -1 ? 'Video' : 'Audio'
+    const lottieDom = ref()
+    const landscapeScreen = ref(false)
+    let _plrl:any
+    const playIngShow = ref(true)
+    const loaded = ref(false)
+    const { registerDrag, unRegisterDrag } = landscapeScreenDrag()
+    watch(landscapeScreen, ()=>{
+      if(landscapeScreen.value){
+        registerDrag()
+      }else{
+        unRegisterDrag()
+      }
+    })
+    // 谱面
+    const staffState = reactive({
+      staffSrc: "",
+      isShow: false,
+      height:"initial"
+    })
+    const staffDom= ref<HTMLIFrameElement>()
+    const {playStaff, pauseStaff, updateProgressStaff} = staffMoveInstance()
+    function initPlay(){
+      const id = playType === "Audio" ? "#audioMediaSrc" : "#videoMediaSrc";
+      _plrl = new Plyr(id, {
+        controls: ["play", "progress", "current-time", "duration"]
+      });
+      // 在微信中运行的时候,微信没有开放自动加载资源的权限,所以要等播放之后才显示播放控制器
+      _plrl.on('loadedmetadata', () => {
+        loaded.value = true
+      });
+      // 创建音波数据
+      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()
+        });
+      }
+      _plrl.on('play', () => {
+        playIngShow.value = false
+        playStaff()
+      });
+      _plrl.on('pause', () => {
+        playIngShow.value = true
+        pauseStaff()
+      });
+      _plrl.on('seeked', () => {
+        // 暂停的时候调用
+        if(!_plrl.playing){
+          updateProgressStaff(_plrl.currentTime)
+        }
+      });
+    }
+    // 注册 横屏时候的自定义拖动事件 解决旋转时候拖动进度条坐标的问题
+    function landscapeScreenDrag() {
+      let progressBar: HTMLElement
+      function onDown(e: MouseEvent | TouchEvent) {
+        e.preventDefault();
+        e.stopPropagation();
+        // 记录拖动之前的状态
+        const playing = _plrl.playing
+        _plrl.pause()
+        const isTouchEv = isTouchEvent(e);
+        const event = isTouchEv ? e.touches[0] : e;
+        setProgress(event)
+        function onUp() {
+          document.removeEventListener(
+            isTouchEv ? 'touchmove' : 'mousemove',
+            onMove
+          );
+          document.removeEventListener(isTouchEv ? 'touchend' : 'mouseup', onUp);
+          playing && _plrl.play()  // 之前是播放状态  就播放
+        }
+        function onMove(e: MouseEvent | TouchEvent){
+          e.preventDefault();
+          e.stopPropagation();
+          const event = isTouchEvent(e) ? e.touches[0] : e;
+          setProgress(event)
+        }
+        function setProgress(event: MouseEvent | Touch) {
+          const {top, height} = progressBar.getBoundingClientRect();
+          const progress = (event.clientY - top) / height;
+          const progressNum = Math.min(Math.max(progress, 0), 1);
+          _plrl.currentTime = progressNum * _plrl.duration
+        }
+        document.addEventListener(isTouchEv ? 'touchmove' : 'mousemove', onMove);
+        document.addEventListener(isTouchEv ? 'touchend' : 'mouseup', onUp);
+      }
+      function registerDrag() {
+        if(!progressBar){
+          progressBar = document.querySelector('#landscapeScreenPlay .plyr__progress__container') as HTMLElement;
+        }
+        progressBar.addEventListener('mousedown', onDown);
+        progressBar.addEventListener('touchstart', onDown);
+      }
+      function unRegisterDrag() {
+        progressBar.removeEventListener('mousedown', onDown);
+        progressBar.removeEventListener('touchstart', onDown);
+      }
+      return {
+        registerDrag,
+        unRegisterDrag
+      }
+    }
+    function isTouchEvent(e: MouseEvent | TouchEvent): e is TouchEvent {
+      return window.TouchEvent && e instanceof window.TouchEvent;
+    }
+    /**
+     * 音频可视化
+     * @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.7)"
+          })
+          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
+      }
+    }
+    function handlerBack(event:MouseEvent){
+      event.stopPropagation()
+      router.back()
+    }
+    //点击改变播放状态
+    function handlerClickPlay(event:MouseEvent){
+      // 原生 播放暂停按钮 点击的时候 不触发
+      // @ts-ignore
+      if(event.target?.matches('button.plyr__control')){
+        return
+      }
+      if (_plrl.playing) {
+        _plrl.pause();
+      } else {
+        _plrl.play();
+      }
+    }
+    function handlerLandscapeScreen(){
+      // app端调用app的横屏
+      if(isApp){
+        postMessage({
+          api: "setRequestedOrientation",
+          content: {
+            orientation: 0,
+          },
+        });
+      }else{
+        // web端使用旋转的方式
+        updateLandscapeScreenState()
+        window.addEventListener('resize', updateLandscapeScreenState)
+      }
+    }
+    function updateLandscapeScreenState(){
+      // 下一帧 计算  横竖屏切换太快  获取的宽高不准
+      requestAnimationFrame(()=>{
+        if(window.innerWidth > window.innerHeight){
+          landscapeScreen.value = false
+        }else{
+          landscapeScreen.value = true
+        }
+      })
+    }
+    const pageVisibility = usePageVisibility();
+    watch(pageVisibility, value => {
+      if (value === 'hidden') {
+        _plrl?.pause();
+      }
+    });
+    // 初始化五线谱
+    function initStaff(){
+      const src = `${vaildMusicScoreUrl()}/instrument/#/simple-detail?id=${musicSheetId}&musicRenderType=staff`;
+      //const src = `http://192.168.3.68:3000/instrument.html#/simple-detail?id=${musicSheetId}&musicRenderType=staff`;
+      staffState.staffSrc = src
+      window.addEventListener('message', (event) => {
+        const { api, height } = event.data;
+        if (api === 'api_musicPage') {
+          staffState.isShow = true
+          staffState.height = height + "px"
+          // 如果是播放中自动开始 播放
+          if(_plrl.playing){
+            playStaff()
+          }
+        }
+      });
+    }
+    function staffMoveInstance(){
+      let isPause = true
+      const requestAnimationFrameFun = () => {
+        requestAnimationFrame(() => {
+          staffDom.value?.contentWindow?.postMessage(
+            {
+              api: 'api_playProgress',
+              content: {
+                currentTime: _plrl.currentTime
+              }
+            },
+            "*"
+          )
+          if (!isPause) {
+            requestAnimationFrameFun()
+          }
+        })
+      }
+      const playStaff = () => {
+        // 没渲染不执行
+        if(!staffState.isShow) return
+        isPause = false
+        staffDom.value?.contentWindow?.postMessage(
+          {
+            api: 'api_play'
+          },
+          "*"
+        )
+        requestAnimationFrameFun()
+      }
+      const pauseStaff = () => {
+        // 没渲染不执行
+        if(!staffState.isShow) return
+        isPause = true
+        staffDom.value?.contentWindow?.postMessage(
+          {
+            api: 'api_paused'
+          },
+          "*"
+        )
+      }
+      const updateProgressStaff = (currentTime: string) => {
+        // 没渲染不执行
+        if(!staffState.isShow) return
+        staffDom.value?.contentWindow?.postMessage(
+          {
+            api: 'api_updateProgress',
+            content: {
+              currentTime
+            }
+          },
+          "*"
+        )
+      }
+      return {
+        playStaff,
+        pauseStaff,
+        updateProgressStaff
+      }
+    }
+    onMounted(()=>{
+      // 五线谱
+      initStaff()
+      initPlay()
+      handlerLandscapeScreen()
+    })
+    onUnmounted(()=>{
+      if(isApp){
+        postMessage({
+          api: "setRequestedOrientation",
+          content: {
+            orientation: 1,
+          },
+        });
+      }else{
+        window.removeEventListener('resize', updateLandscapeScreenState)
+      }
+    })
+    return () =>
+    <div id="landscapeScreenPlay" class={[styles.playCreation,landscapeScreen.value && styles.landscapeScreen,!loaded.value && styles.notLoaded]} onClick={handlerClickPlay}>
+      <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 class={[styles.playLarge, playIngShow.value && styles.playIngShow]}></div>
+      {/* 谱面 */}
+      {
+        staffState.staffSrc &&
+        <div
+          class={[styles.staffBox, staffState.isShow && styles.staffBoxShow]}
+          style={
+            {
+              '--staffBoxHeight':staffState.height
+            }
+          }
+        >
+          <div class={styles.mask}></div>
+          <iframe
+            ref={staffDom}
+            class={styles.staff}
+            frameborder="0"
+            src={staffState.staffSrc}>
+          </iframe>
+        </div>
+      }
+    </div>;
+  }
+});

BIN
src/views/creation/share-model/images/audio-share-bg.png


BIN
src/views/creation/share-model/images/audioLabel.png


BIN
src/views/creation/share-model/images/share-bg.png


BIN
src/views/creation/share-model/images/video-share-bg.png


BIN
src/views/creation/share-model/images/videoLabel.png


+ 16 - 7
src/views/creation/share-model/index.module.less

@@ -68,7 +68,14 @@
       width: 56px;
       height: 56px;
     }
-
+    .imgLabel{
+      position: absolute;
+      right: 0;
+      top: 0;
+      width: 28px;
+      height: 14px;
+      z-index: 10;
+    }
 
   }
 
@@ -96,7 +103,6 @@
 
 .downloadSection {
   display: flex;
-  align-items: center;
   margin: 0 20px;
   position: relative;
   z-index: 3;
@@ -135,17 +141,20 @@
     margin-left: 10px;
     padding-left: 9px;
     border-left: 1px dashed #D8D8D8;
-
+    font-weight: 500;
+    font-size: 12px;
+    color: #28A0FE;
+    line-height: 18px;
+    padding-top: 5px;
     .tip {
-      font-size: 12px;
       color: #777777;
-      line-height: 17px;
     }
 
     .iconLogo {
       width: 102px;
       height: 20px;
-      margin: 12px 0 4px;
+      margin: 5px 0 0;
+      display: block;
     }
 
     .downTip {
@@ -192,4 +201,4 @@
   color: #AAAAAA;
   line-height: 55px;
   text-align: center;
-}
+}

+ 12 - 3
src/views/creation/share-model/index.tsx

@@ -18,7 +18,10 @@ import iconWeChat from './images/icon-wechat.png';
 import iconFriendRing from './images/icon-friend-ring.png';
 import iconLink from './images/icon-link.png';
 import iconLogo from './images/icon-logo.png';
-import shareBg from './images/share-bg.png';
+import audioLabel from './images/audioLabel.png';
+import videoLabel from './images/videoLabel.png';
+import videoShareBg from './images/video-share-bg.png';
+import audioShareBg from './images/audio-share-bg.png';
 import audioPan from '../images/audio-pan.png';
 import smallLogo from '@common/images/logo.png';
 import musicBg from './images/music-bg.png';
@@ -32,6 +35,10 @@ export default defineComponent({
     musicDetail: {
       type: Object,
       default: () => {}
+    },
+    playType:{
+      type: String,
+      default: ''
     }
   },
   emits: ['close'],
@@ -191,7 +198,7 @@ export default defineComponent({
     return () => (
       <div class={styles.shareModel}>
         <div class={styles.shareContent} id="shareContent">
-          <img src={shareBg} class={styles.shareBg} />
+          <img src={props.playType === "Audio" ? audioShareBg : videoShareBg} class={styles.shareBg} />
           <div class={styles.share_content__title}>
             <p class={styles.large}>我在音乐数字课堂发布了演奏作品</p>
             <p class={styles.small}>赶快扫码看看吧!</p>
@@ -213,6 +220,7 @@ export default defineComponent({
                 class={styles.muploader}
                 crossorigin="anonymous"
               />
+              <img class={styles.imgLabel} src={props.playType === "Audio" ? audioLabel : videoLabel} />
             </div>
             <div class={styles.musicDetail}>
               <p class={[styles.musicName, 'van-ellipsis']}>
@@ -230,8 +238,9 @@ export default defineComponent({
               <img src={smallLogo} class={styles.qrcodeLogo} />
             </div>
             <div class={styles.qrtips}>
+              <div>温馨提示:</div>
               <p class={styles.tip}>
-                温馨提示:保存图片到相册或长按识别二维码进入查看喔
+                保存图片到相册后,请在微信里扫码查看
               </p>
               <img src={iconLogo} class={styles.iconLogo} />
               {/* <p class={styles.downTip}>扫码下载音乐数字课堂App</p> */}

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است