Procházet zdrojové kódy

fix: 完善canvas处理

shuisheng před 2 roky
rodič
revize
b070b437be

+ 1 - 0
package.json

@@ -45,6 +45,7 @@
     "m3u8-parser": "^6.2.0",
     "mediainfo.js": "^0.1.9",
     "mediasoup-client": "^3.6.84",
+    "mpegts.js": "^1.7.3",
     "msr": "^1.3.4",
     "naive-ui": "^2.34.3",
     "pinia": "^2.0.33",

+ 10 - 0
pnpm-lock.yaml

@@ -44,6 +44,9 @@ dependencies:
   mediasoup-client:
     specifier: ^3.6.84
     version: 3.6.84
+  mpegts.js:
+    specifier: ^1.7.3
+    version: 1.7.3
   msr:
     specifier: ^1.3.4
     version: 1.3.4
@@ -7226,6 +7229,13 @@ packages:
       global: 4.4.0
     dev: false
 
+  /mpegts.js@1.7.3:
+    resolution: {integrity: sha512-kqZ1C1IsbAQN72cK8vMrzKeM7hwrwSBbFAwVAc7PPweOeoZxCANrc7fAVDKMfYUzxdNkMTnec9tVmlxmKZB0TQ==}
+    dependencies:
+      es6-promise: 4.2.8
+      webworkify-webpack: 2.1.5
+    dev: false
+
   /mrmime@1.0.1:
     resolution: {integrity: sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==}
     engines: {node: '>=10'}

+ 8 - 0
src/App.vue

@@ -3,6 +3,7 @@
 </template>
 
 <script lang="ts" setup>
+import { isMobile } from 'billd-utils';
 import { onMounted } from 'vue';
 
 import { loginMessage } from '@/hooks/use-login';
@@ -25,6 +26,13 @@ onMounted(() => {
   //     new VConsole.default();
   //   })
   //   .catch(() => {});
+  if (isMobile()) {
+    const metaEl = document.querySelector('meta[name="viewport"]');
+    metaEl?.setAttribute(
+      'content',
+      'width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no'
+    );
+  }
 });
 </script>
 

+ 72 - 40
src/hooks/use-play.ts

@@ -1,5 +1,5 @@
 import '@/assets/css/videojs.scss';
-import flvJs from 'flv.js';
+import mpegts from 'mpegts.js';
 import videoJs from 'video.js';
 import Player from 'video.js/dist/types/player';
 import { onMounted, onUnmounted, ref, watch } from 'vue';
@@ -10,7 +10,8 @@ import { createVideo } from '@/utils';
 export * as flvJs from 'flv.js';
 
 export function useFlvPlay() {
-  const flvPlayer = ref<flvJs.Player>();
+  // const flvPlayer = ref<flvJs.Player>();
+  const flvPlayer = ref<mpegts.Player>();
   const flvVideoEl = ref<HTMLVideoElement>();
   const appStore = useAppStore();
 
@@ -22,9 +23,10 @@ export function useFlvPlay() {
 
   function destroyFlv() {
     if (flvPlayer.value) {
+      console.log(flvPlayer.value.destroy);
       flvPlayer.value.destroy();
     }
-    flvVideoEl.value?.remove();
+    // flvVideoEl.value?.remove();
   }
 
   watch(
@@ -46,47 +48,77 @@ export function useFlvPlay() {
   function startFlvPlay(data: { flvurl: string }) {
     destroyFlv();
     return new Promise<{ width: number; height: number }>((resolve) => {
-      setTimeout(() => {
-        if (flvJs.isSupported()) {
-          flvPlayer.value = flvJs.createPlayer({
-            type: 'flv',
-            url: data.flvurl,
+      if (mpegts.getFeatureList().mseLivePlayback) {
+        flvPlayer.value = mpegts.createPlayer({
+          type: 'flv', // could also be mpegts, m2ts, flv
+          isLive: true,
+          url: data.flvurl,
+        });
+        const videoEl = createVideo({ muted: true, autoplay: true });
+        flvVideoEl.value = videoEl;
+        flvVideoEl.value.addEventListener('play', () => {
+          console.log('flv-play');
+        });
+        flvVideoEl.value.addEventListener('playing', () => {
+          console.log('flv-playing');
+          // setMuted(false);
+          setMuted(appStore.muted);
+          resolve({
+            width: flvVideoEl.value?.videoWidth || 0,
+            height: flvVideoEl.value?.videoHeight || 0,
           });
-          const videoEl = createVideo({ muted: true, autoplay: true });
-          flvVideoEl.value = videoEl;
-          flvVideoEl.value.addEventListener('play', () => {
-            console.log('flv-play');
-          });
-          flvVideoEl.value.addEventListener('playing', () => {
-            console.log(
-              'flv-playing',
-              flvVideoEl.value?.videoWidth,
-              flvVideoEl.value?.videoHeight
-            );
-            // setMuted(false);
-            setMuted(appStore.muted);
-            resolve({
-              width: flvVideoEl.value?.videoWidth || 0,
-              height: flvVideoEl.value?.videoHeight || 0,
-            });
-          });
-          flvPlayer.value.attachMediaElement(flvVideoEl.value);
-          flvPlayer.value.load();
-          try {
-            console.log('开始播放flv');
-            flvPlayer.value.play();
-          } catch (err) {
-            console.error('flv播放失败');
-            console.log(err);
-          }
-        } else {
-          console.error('不支持flv');
+        });
+        flvPlayer.value.attachMediaElement(flvVideoEl.value);
+        flvPlayer.value.load();
+        try {
+          console.log('开始播放flv');
+          flvPlayer.value.play();
+        } catch (err) {
+          console.error('flv播放失败');
+          console.log(err);
         }
-      }, 500);
+      } else {
+        console.error('不支持flv');
+      }
+      // if (flvJs.isSupported()) {
+      //   flvPlayer.value = flvJs.createPlayer({
+      //     type: 'flv',
+      //     url: data.flvurl,
+      //   });
+      //   const videoEl = createVideo({ muted: true, autoplay: true });
+      //   flvVideoEl.value = videoEl;
+      //   flvVideoEl.value.addEventListener('play', () => {
+      //     console.log('flv-play');
+      //   });
+      //   flvVideoEl.value.addEventListener('playing', () => {
+      //     console.log(
+      //       'flv-playing',
+      //       flvVideoEl.value?.videoWidth,
+      //       flvVideoEl.value?.videoHeight
+      //     );
+      //     // setMuted(false);
+      //     setMuted(appStore.muted);
+      //     resolve({
+      //       width: flvVideoEl.value?.videoWidth || 0,
+      //       height: flvVideoEl.value?.videoHeight || 0,
+      //     });
+      //   });
+      //   flvPlayer.value.attachMediaElement(flvVideoEl.value);
+      //   flvPlayer.value.load();
+      //   try {
+      //     console.log('开始播放flv');
+      //     flvPlayer.value.play();
+      //   } catch (err) {
+      //     console.error('flv播放失败');
+      //     console.log(err);
+      //   }
+      // } else {
+      //   console.error('不支持flv');
+      // }
     });
   }
 
-  return { flvVideoEl, startFlvPlay, destroyFlv };
+  return { flvPlayer, flvVideoEl, startFlvPlay, destroyFlv };
 }
 
 export function useHlsPlay() {
@@ -173,5 +205,5 @@ export function useHlsPlay() {
     });
   }
 
-  return { hlsVideoEl, startHlsPlay, destroyHls };
+  return { hlsPlayer, hlsVideoEl, startHlsPlay, destroyHls };
 }

+ 36 - 21
src/hooks/use-pull.ts

@@ -1,4 +1,5 @@
-import { Ref, nextTick, ref, watch } from 'vue';
+import mpegts from 'mpegts.js';
+import { Ref, nextTick, onUnmounted, ref, watch } from 'vue';
 import { useRoute } from 'vue-router';
 
 import { useFlvPlay, useHlsPlay } from '@/hooks/use-play';
@@ -38,7 +39,6 @@ export function usePull({
     }[]
   >([]);
   const videoElArr = ref<HTMLVideoElement[]>([]);
-
   const {
     getSocketId,
     initWs,
@@ -59,20 +59,43 @@ export function usePull({
     delTrack,
   } = useWs();
 
-  const { flvVideoEl, startFlvPlay } = useFlvPlay();
-  const { hlsVideoEl, startHlsPlay } = useHlsPlay();
+  const { flvPlayer, flvVideoEl, startFlvPlay, destroyFlv } = useFlvPlay();
+  const { hlsVideoEl, startHlsPlay, destroyHls } = useHlsPlay();
+  const stopDrawingArr = ref<any[]>([]);
+
+  onUnmounted(() => {
+    stopDrawingArr.value.forEach((cb) => cb());
+  });
 
   async function handleHlsPlay() {
     console.log('handleHlsPlay');
+    await startHlsPlay({ hlsurl: hlsurl.value });
     videoLoading.value = true;
-    const { width, height } = await startHlsPlay({
-      hlsurl: hlsurl.value,
-    });
-    videoToCanvas({
+    const { canvas, stopDrawing } = videoToCanvas({
       videoEl: hlsVideoEl.value!,
       targetEl: canvasRef.value!,
-      width,
-      height,
+    });
+    stopDrawingArr.value.push(stopDrawing);
+    canvasRef.value!.appendChild(canvas);
+    videoLoading.value = false;
+  }
+
+  async function handleFlvPlay() {
+    console.log('handleFlvPlay');
+    await startFlvPlay({ flvurl: flvurl.value });
+    const initCanvas = videoToCanvas({
+      videoEl: flvVideoEl.value!,
+      targetEl: canvasRef.value!,
+    });
+    stopDrawingArr.value.push(initCanvas.stopDrawing);
+    canvasRef.value!.appendChild(initCanvas.canvas);
+    flvPlayer.value!.on(mpegts.Events.MEDIA_INFO, () => {
+      const newCanvas = videoToCanvas({
+        videoEl: flvVideoEl.value!,
+        targetEl: canvasRef.value!,
+      });
+      initCanvas.canvas = newCanvas.canvas;
+      stopDrawingArr.value.push(newCanvas.stopDrawing);
     });
     videoLoading.value = false;
   }
@@ -81,20 +104,11 @@ export function usePull({
     if (roomLiveType.value === liveTypeEnum.srsFlvPull) {
       console.log('srsFlvPull', autoplayVal.value);
       if (!autoplayVal.value) return;
-      const { width, height } = await startFlvPlay({
-        flvurl: flvurl.value,
-      });
-      videoToCanvas({
-        videoEl: flvVideoEl.value!,
-        targetEl: canvasRef.value!,
-        width,
-        height,
-      });
-      videoLoading.value = false;
+      await handleFlvPlay();
     } else if (roomLiveType.value === liveTypeEnum.srsHlsPull) {
       console.log('srsHlsPull', autoplayVal.value);
       if (!autoplayVal.value) return;
-      handleHlsPlay();
+      await handleHlsPlay();
     }
   }
 
@@ -284,6 +298,7 @@ export function usePull({
     sendDanmu,
     addVideo,
     handleHlsPlay,
+    handleFlvPlay,
     roomLiveType,
     roomLiveing,
     autoplayVal,

+ 0 - 6
src/layout/mobile/index.vue

@@ -9,12 +9,6 @@
 
 <script lang="ts" setup>
 import HeadCpt from './head/index.vue';
-
-const metaEl = document.querySelector('meta[name="viewport"]');
-metaEl?.setAttribute(
-  'content',
-  'width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no'
-);
 </script>
 
 <style lang="scss" scoped></style>

+ 16 - 9
src/utils/index.ts

@@ -61,33 +61,40 @@ export const createVideo = ({ muted = true, autoplay = true }) => {
 export function videoToCanvas(data: {
   videoEl: HTMLVideoElement;
   targetEl: Element;
-  width: number;
-  height: number;
 }) {
-  const { videoEl, targetEl, width, height } = data;
+  const { videoEl, targetEl } = data;
   if (!videoEl || !targetEl) {
-    return;
+    throw new Error('videoEl或targetEl为空');
   }
   const canvas = document.createElement('canvas');
-  canvas.width = width;
-  canvas.height = height;
+
   const ctx = canvas.getContext('2d')!;
 
   let timer;
 
   function drawCanvas() {
-    ctx.drawImage(videoEl, 0, 0, width, height);
+    const videoTrack = videoEl
+      // @ts-ignore
+      .captureStream()
+      .getVideoTracks()[0];
+    if (videoTrack) {
+      const { width, height } = videoTrack.getSettings();
+      canvas.width = width!;
+      canvas.height = height!;
+      ctx.drawImage(videoEl, 0, 0, width!, height!);
+    }
+    console.log(performance.now());
     timer = requestAnimationFrame(drawCanvas);
   }
 
   function stopDrawing() {
     cancelAnimationFrame(timer);
   }
-  targetEl.appendChild(canvas);
+  // targetEl.appendChild(canvas);
   // document.body.appendChild(videoEl);
   // targetEl.parentNode?.replaceChild(canvas, targetEl);
 
   drawCanvas();
 
-  return { drawCanvas, stopDrawing };
+  return { drawCanvas, stopDrawing, canvas };
 }

+ 0 - 1
src/views/h5/index.vue

@@ -104,7 +104,6 @@ function showAll(item: IArea) {
   router.push({
     name: mobileRouterName.h5Area,
     params: { id: item.id },
-    query: { name: item.name },
   });
 }
 

+ 4 - 7
src/views/h5/room/index.vue

@@ -104,7 +104,6 @@ import { onMounted, ref, watch } from 'vue';
 import { useRoute } from 'vue-router';
 
 import { fetchFindLiveRoom } from '@/api/liveRoom';
-import { useHlsPlay } from '@/hooks/use-play';
 import { usePull } from '@/hooks/use-pull';
 import { DanmuMsgTypeEnum, LiveRoomTypeEnum, liveTypeEnum } from '@/interface';
 import router, { mobileRouterName } from '@/router';
@@ -120,8 +119,6 @@ const localVideoRef = ref<HTMLVideoElement[]>([]);
 const showPlayBtn = ref(false);
 const height = ref(0);
 
-const { hlsVideoEl, startHlsPlay } = useHlsPlay();
-
 const {
   initPull,
   closeWs,
@@ -164,7 +161,6 @@ async function getLiveRoomInfo() {
     videoLoading.value = true;
     const res = await fetchFindLiveRoom(route.params.roomId as string);
     if (res.code === 200) {
-      console.log('kkkkk');
       if (res.data.type === LiveRoomTypeEnum.user_wertc) {
         autoplayVal.value = true;
         roomLiveType.value = liveTypeEnum.webrtcPull;
@@ -194,7 +190,6 @@ onMounted(() => {
       bottomRef.value.getBoundingClientRect().top -
       containerRef.value.getBoundingClientRect().top;
     height.value = res;
-    // containerRef.value.style.height = `${res}px`;
   }
 });
 </script>
@@ -245,14 +240,16 @@ onMounted(() => {
       inset: 0;
     }
     .media-list {
-      height: 230px;
+      position: relative;
       overflow-y: scroll;
-      position: absolute;
+      height: 230px;
       :deep(video) {
+        display: block;
         width: 100%;
         height: 100%;
       }
       :deep(canvas) {
+        display: block;
         width: 100%;
         height: 100%;
       }

+ 21 - 50
src/views/home/index.vue

@@ -166,11 +166,11 @@
 
 <script lang="ts" setup>
 import { isMobile } from 'billd-utils';
-import { nextTick, onMounted, ref } from 'vue';
+import { nextTick, onMounted, onUnmounted, ref } from 'vue';
 import { useRouter } from 'vue-router';
 
 import { fetchLiveList } from '@/api/live';
-import { useFlvPlay, useHlsPlay } from '@/hooks/use-play';
+import { useHlsPlay } from '@/hooks/use-play';
 import { ILive, ILiveRoom, LiveRoomTypeEnum, liveTypeEnum } from '@/interface';
 import { routerName } from '@/router';
 import { videoToCanvas } from '@/utils';
@@ -181,11 +181,24 @@ const showControls = ref(false);
 const topLiveRoomList = ref<ILive[]>([]);
 const otherLiveRoomList = ref<ILive[]>([]);
 const currentLiveRoom = ref<ILive>();
-
-const { flvVideoEl, startFlvPlay, destroyFlv } = useFlvPlay();
+const stopDrawingArr = ref<any[]>([]);
 const { hlsVideoEl, startHlsPlay, destroyHls } = useHlsPlay();
 
-async function changeLiveRoom(item: ILive) {
+onUnmounted(() => {
+  stopDrawingArr.value.forEach((cb) => cb());
+});
+
+async function handleHlsPlay(hlsurl: string) {
+  await startHlsPlay({ hlsurl });
+  const { canvas, stopDrawing } = videoToCanvas({
+    videoEl: hlsVideoEl.value!,
+    targetEl: canvasRef.value!,
+  });
+  stopDrawingArr.value.push(stopDrawing);
+  canvasRef.value!.appendChild(canvas);
+}
+
+function changeLiveRoom(item: ILive) {
   if (item.id === currentLiveRoom.value?.id) return;
   currentLiveRoom.value = item;
   canvasRef.value?.childNodes?.forEach((item) => {
@@ -196,31 +209,9 @@ async function changeLiveRoom(item: ILive) {
     item.live_room?.type === LiveRoomTypeEnum.user_obs ||
     item.live_room?.type === LiveRoomTypeEnum.system
   ) {
-    // @ts-ignore
-    // if (flvJs.isSupported()) {
-    //   const { width, height } = await startFlvPlay({
-    //     flvurl: item.live_room.flv_url!,
-    //   });
-    //   videoToCanvas({
-    //     videoEl: flvVideoEl.value!,
-    //     targetEl: canvasRef.value!,
-    //     width,
-    //     height,
-    //   });
-    // } else {
     destroyHls();
-    const { width, height } = await startHlsPlay({
-      hlsurl: item.live_room.hls_url!,
-    });
-    videoToCanvas({
-      videoEl: hlsVideoEl.value!,
-      targetEl: canvasRef.value!,
-      width,
-      height,
-    });
-    // }
+    handleHlsPlay(item.live_room.hls_url!);
   } else {
-    destroyFlv();
     destroyHls();
   }
 }
@@ -237,7 +228,7 @@ async function getLiveRoomList() {
       otherLiveRoomList.value = res.data.rows.slice(top);
       if (res.data.total) {
         currentLiveRoom.value = topLiveRoomList.value[0];
-        nextTick(async () => {
+        nextTick(() => {
           if (
             currentLiveRoom.value?.live_room?.type ===
               LiveRoomTypeEnum.user_srs ||
@@ -245,27 +236,7 @@ async function getLiveRoomList() {
               LiveRoomTypeEnum.user_obs ||
             currentLiveRoom.value?.live_room?.type === LiveRoomTypeEnum.system
           ) {
-            // if (judgeDevice().isIphone) {
-            const { width, height } = await startHlsPlay({
-              hlsurl: currentLiveRoom.value.live_room.hls_url!,
-            });
-            videoToCanvas({
-              videoEl: hlsVideoEl.value!,
-              targetEl: canvasRef.value!,
-              width,
-              height,
-            });
-            // } else {
-            //   const { width, height } = await startFlvPlay({
-            //     flvurl: currentLiveRoom.value.live_room.flv_url!,
-            //   });
-            //   videoToCanvas({
-            //     videoEl: flvVideoEl.value!,
-            //     targetEl: canvasRef.value!,
-            //     width,
-            //     height,
-            //   });
-            // }
+            handleHlsPlay(currentLiveRoom.value.live_room.hls_url!);
           }
         });
       }

+ 7 - 6
src/views/pull/index.vue

@@ -42,7 +42,6 @@
                 })`,
               }"
             ></div>
-            <!-- <div ref="canvasRef"></div> -->
             <div
               ref="canvasRef"
               class="media-list"
@@ -400,13 +399,14 @@ onMounted(() => {
           inset: 0;
         }
         .media-list {
-          position: absolute;
-          overflow-y: scroll;
+          position: relative;
           :deep(video) {
+            display: block;
             width: 100%;
             height: 100%;
           }
           :deep(canvas) {
+            display: block;
             width: 100%;
             height: 100%;
           }
@@ -465,20 +465,21 @@ onMounted(() => {
     }
 
     .gift-list {
+      position: relative;
       display: flex;
       align-items: center;
       justify-content: space-around;
       box-sizing: border-box;
-      height: 100px;
       margin: 5px 0;
+      height: 100px;
       .item {
         display: flex;
         align-items: center;
-        width: 100px;
-        height: 100px;
         flex-direction: column;
         justify-content: center;
         box-sizing: border-box;
+        width: 100px;
+        height: 100px;
         text-align: center;
         cursor: pointer;
         &:hover {

+ 0 - 1
src/views/push/index.vue

@@ -309,7 +309,6 @@ const {
   remoteVideoRef,
   isSRS,
 });
-const drawCanvasFn = ref();
 watch(
   () => damuList.value.length,
   () => {