Sfoglia il codice sorgente

feat: 阶段保存

shuisheng 2 anni fa
parent
commit
74b5bdc377

+ 17 - 6
src/assets/constant.scss

@@ -8,11 +8,22 @@
   --light-primary-color: blue; //CSS 自定义属性(变量)
 }
 
-$large-width: 1400px;
-$medium-width: 1200px;
-$small-width: 800px;
+$w-1500: 1500px;
+$w-1475: 1475px;
+$w-1450: 1450px;
+$w-1400: 1400px;
+$w-1375: 1375px;
+$w-1350: 1350px;
+$w-1300: 1300px;
+$w-1275: 1275px;
+$w-1250: 1250px;
+$w-1200: 1200px;
+$w-1100: 1100px;
+$w-1000: 1000px;
+
+$w-350: 350px;
+$w-300: 300px;
+$w-250: 250px;
+$w-200: 200px;
 
-$large-left-width: 1200px;
-$medium-left-width: 900px;
-$small-left-width: 600px;
 $theme-color-gold: #ffd700;

+ 4 - 1
src/hooks/use-push.ts

@@ -41,7 +41,6 @@ export function usePush({
   const roomName = ref('');
   const danmuStr = ref('');
   const isLiving = ref(false);
-
   const videoElArr = ref<HTMLVideoElement[]>([]);
 
   const allMediaTypeList: {
@@ -64,6 +63,8 @@ export function usePush({
   const {
     getSocketId,
     initWs,
+    fabricCanvasEl,
+    canvasVideoStream,
     lastCoverImg,
     heartbeatTimer,
     localStream,
@@ -300,6 +301,8 @@ export function usePush({
     sendDanmu,
     keydownDanmu,
     localStream,
+    fabricCanvasEl,
+    canvasVideoStream,
     isLiving,
     allMediaTypeList,
     currentResolutionRatio,

+ 64 - 22
src/hooks/use-ws.ts

@@ -49,6 +49,8 @@ export const useWs = () => {
   const trackInfo = reactive({ track_audio: 1, track_video: 1 });
   const localVideo = ref<HTMLVideoElement>(document.createElement('video'));
   const localStream = ref<MediaStream>();
+  const fabricCanvasEl = ref<HTMLCanvasElement>();
+  const canvasVideoStream = ref<MediaStream>();
   const lastCoverImg = ref('');
   const maxBitrate = ref([
     {
@@ -174,15 +176,20 @@ export const useWs = () => {
     () => appStore.allTrack,
     (newTrack, oldTrack) => {
       console.log('appStore.allTrack变了');
-      const mixedStream = new MediaStream();
-      newTrack.forEach((item) => {
-        mixedStream.addTrack(item.track);
-      });
-      console.log('新的allTrack音频轨', mixedStream.getAudioTracks());
-      console.log('新的allTrack视频轨', mixedStream.getVideoTracks());
-      console.log('旧的allTrack音频轨', localStream.value?.getAudioTracks());
-      console.log('旧的allTrack视频轨', localStream.value?.getVideoTracks());
-      localStream.value = mixedStream;
+      if (fabricCanvasEl.value) {
+        const mixedStream = fabricCanvasEl.value.captureStream();
+        localStream.value = mixedStream;
+      } else {
+        const mixedStream = new MediaStream();
+        newTrack.forEach((item) => {
+          mixedStream.addTrack(item.track);
+        });
+        console.log('新的allTrack音频轨', mixedStream.getAudioTracks());
+        console.log('新的allTrack视频轨', mixedStream.getVideoTracks());
+        console.log('旧的allTrack音频轨', localStream.value?.getAudioTracks());
+        console.log('旧的allTrack视频轨', localStream.value?.getVideoTracks());
+        localStream.value = mixedStream;
+      }
       if (isSRS.value) {
         if (!isPull.value) {
           networkStore.rtcMap.forEach((rtc) => {
@@ -201,12 +208,23 @@ export const useWs = () => {
   watch(
     () => currentResolutionRatio.value,
     (newVal) => {
-      appStore.allTrack.forEach((info) => {
-        info.track.applyConstraints({
-          frameRate: { max: currentMaxFramerate.value },
-          height: newVal,
+      if (canvasVideoStream.value) {
+        canvasVideoStream.value.getVideoTracks().forEach((track) => {
+          console.log('23ds1', track);
+          track.applyConstraints({
+            frameRate: { max: currentMaxFramerate.value },
+            height: newVal,
+          });
         });
-      });
+      } else {
+        appStore.allTrack.forEach((info) => {
+          info.track.applyConstraints({
+            frameRate: { max: currentMaxFramerate.value },
+            height: newVal,
+          });
+        });
+      }
+
       networkStore.rtcMap.forEach(async (rtc) => {
         const res = await rtc.setResolutionRatio(newVal);
         if (res === 1) {
@@ -222,12 +240,22 @@ export const useWs = () => {
     () => currentMaxFramerate.value,
     (newVal) => {
       console.log(currentMaxFramerate.value, 'currentMaxFramerate.value');
-      appStore.allTrack.forEach((info) => {
-        info.track.applyConstraints({
-          frameRate: { max: newVal },
-          height: currentResolutionRatio.value,
+      if (canvasVideoStream.value) {
+        canvasVideoStream.value.getVideoTracks().forEach((track) => {
+          track.applyConstraints({
+            frameRate: { max: newVal },
+            height: currentResolutionRatio.value,
+          });
         });
-      });
+      } else {
+        appStore.allTrack.forEach((info) => {
+          info.track.applyConstraints({
+            frameRate: { max: newVal },
+            height: currentResolutionRatio.value,
+          });
+        });
+      }
+
       networkStore.rtcMap.forEach(async (rtc) => {
         const res = await rtc.setMaxFramerate(newVal);
         if (res === 1) {
@@ -253,14 +281,14 @@ export const useWs = () => {
     }
   );
 
-  function addTrack(addTrackInfo: AppRootState['allTrack'][0]) {
+  function addTrack(addTrackInfo: { track; stream }) {
     if (isAnchor.value) {
       networkStore.rtcMap.forEach((rtc) => {
         const sender = rtc.peerConnection
           ?.getSenders()
           .find((sender) => sender.track?.id === addTrackInfo.track.id);
         if (!sender) {
-          console.log('ffff', sender);
+          console.log('pc添加track-1');
           rtc.peerConnection?.addTrack(addTrackInfo.track, addTrackInfo.stream);
         }
       });
@@ -319,7 +347,6 @@ export const useWs = () => {
     }
     const mixedStream = new MediaStream();
     appStore.allTrack.forEach((item) => {
-      console.log('xxxx', item.track);
       mixedStream.addTrack(item.track);
     });
     console.log('delTrack后结果的音频轨', mixedStream.getAudioTracks());
@@ -550,15 +577,27 @@ export const useWs = () => {
       //   roomId: `${roomId.value}___${receiver}`,
       //   isSRS: true,
       // });
+      if (fabricCanvasEl.value) {
+        const mixedStream = fabricCanvasEl.value.captureStream();
+        localStream.value = mixedStream;
+      }
       rtc.localStream = localStream.value;
+      const aaaa = document.querySelector('#canvasRef') as HTMLCanvasElement;
+      console.log(
+        localStream.value?.getVideoTracks(),
+        '235252',
+        aaaa.captureStream().getVideoTracks()
+      );
       localStream.value?.getTracks().forEach((track) => {
         console.warn(
           'srs startNewWebRtc,pc插入track',
           track.id,
           localStream.value?.id
         );
+        console.log('pc添加track-2');
         rtc.peerConnection?.addTrack(track, localStream.value!);
       });
+
       sendOffer({
         sender: getSocketId(),
         receiver,
@@ -585,6 +624,7 @@ export const useWs = () => {
           //   streams: [localStream.value!],
           //   direction: 'sendonly',
           // });
+          console.log('pc添加track-3');
           rtc.peerConnection?.addTrack(track, localStream.value!);
         });
       }
@@ -861,6 +901,8 @@ export const useWs = () => {
     initWs,
     addTrack,
     delTrack,
+    fabricCanvasEl,
+    canvasVideoStream,
     lastCoverImg,
     roomLiveing,
     liveRoomInfo,

+ 1 - 0
src/interface.ts

@@ -184,6 +184,7 @@ export enum liveTypeEnum {
   srsHlsPull = 'srsHlsPull',
   srsPush = 'srsPush',
   webrtcPush = 'webrtcPush',
+  canvasPush = 'canvasPush',
 }
 
 /** 直播间类型 */

+ 7 - 1
src/layout/pc/head/index.vue

@@ -176,6 +176,12 @@
         </template>
         <template #list>
           <div class="list">
+            <a
+              class="item"
+              @click.prevent="handleStartLive(liveTypeEnum.canvasPush)"
+            >
+              <div class="txt">canvas混流开播</div>
+            </a>
             <a
               class="item"
               @click.prevent="handleStartLive(liveTypeEnum.webrtcPush)"
@@ -362,7 +368,7 @@ function handleStartLive(key) {
   align-items: center;
   justify-content: space-between;
   padding: 0 30px;
-  min-width: $medium-width;
+  min-width: $w-1300;
   height: 64px;
   font-size: 14px;
   background-color: #fff;

+ 0 - 8
src/layout/pc/index.vue

@@ -17,14 +17,6 @@ import SidebarCpt from './sidebar/index.vue';
 
 <style lang="scss" scoped>
 .layout {
-  // min-width: $large-width;
   min-height: 100vh;
 }
-
-// 屏幕宽度小于$large-width的时候
-@media screen and (max-width: $large-width) {
-  .layout {
-    // min-width: $medium-width;
-  }
-}
 </style>

+ 14 - 11
src/views/home/index.vue

@@ -324,9 +324,10 @@ function joinHlsRoom() {
 
 <style lang="scss" scoped>
 .home-wrap {
+  width: $w-1475;
+
   .play-container {
     padding: 20px 0;
-    // min-width: $large-width;
     background-color: papayawhip;
     text-align: center;
     white-space: nowrap;
@@ -338,7 +339,7 @@ function joinHlsRoom() {
       display: inline-block;
       overflow: hidden;
       box-sizing: border-box;
-      width: $large-left-width;
+      width: $w-1200;
       height: 610px;
       border-radius: 4px;
       background-color: rgba($color: #000000, $alpha: 0.3);
@@ -608,23 +609,25 @@ function joinHlsRoom() {
   }
 }
 
-// 屏幕宽度小于$large-width的时候
-@media screen and (max-width: $large-width) {
+// 屏幕宽度大于1500的时候
+@media screen and (min-width: $w-1500) {
   .home-wrap {
+    width: $w-1475;
+
     .play-container {
       .left {
-        width: $medium-left-width;
+        width: $w-1200;
         height: 460px;
       }
       .right {
         height: 460px;
 
-        .list {
-          .item {
-            width: 150px;
-            height: 80px;
-          }
-        }
+        // .list {
+        //   .item {
+        //     width: 150px;
+        //     height: 80px;
+        //   }
+        // }
       }
     }
     .area-container {

+ 9 - 13
src/views/pull/index.vue

@@ -351,15 +351,14 @@ onMounted(() => {
 <style lang="scss" scoped>
 .pull-wrap {
   margin: 20px auto 0;
-  min-width: $large-width;
+  width: $w-1275;
   height: 700px;
-  text-align: center;
   .left {
     position: relative;
     display: inline-block;
     overflow: hidden;
     box-sizing: border-box;
-    width: $large-left-width;
+    width: $w-1000;
     height: 100%;
     border-radius: 6px;
     background-color: papayawhip;
@@ -570,7 +569,7 @@ onMounted(() => {
     display: inline-block;
     box-sizing: border-box;
     margin-left: 10px;
-    min-width: 300px;
+    width: $w-250;
     height: 100%;
     border-radius: 6px;
     background-color: papayawhip;
@@ -669,19 +668,16 @@ onMounted(() => {
   }
 }
 
-// 屏幕宽度小于$large-width的时候
-@media screen and (max-width: $large-width) {
+// 屏幕宽度大于1500的时候
+@media screen and (min-width: $w-1500) {
   .pull-wrap {
+    width: $w-1475;
+
     .left {
-      width: $medium-left-width;
+      width: $w-1200;
     }
     .right {
-      .list {
-        .item {
-          width: 150px;
-          height: 80px;
-        }
-      }
+      width: $w-250;
     }
   }
 }

+ 10 - 17
src/views/push/index.vue

@@ -490,22 +490,15 @@ function handleStartMedia(item: { type: MediaTypeEnum; txt: string }) {
 <style lang="scss" scoped>
 .push-wrap {
   margin: 20px auto 0;
-  min-width: $large-width;
+  width: $w-1275;
   height: 700px;
   text-align: center;
-  .testRef {
-    // width: 600px;
-    // background-color: red;
-    :deep(canvas) {
-      // width: 100%;
-    }
-  }
   .left {
     position: relative;
     display: inline-block;
     overflow: hidden;
     box-sizing: border-box;
-    width: $large-left-width;
+    width: $w-1000;
     height: 100%;
     border-radius: 6px;
     background-color: white;
@@ -659,7 +652,7 @@ function handleStartMedia(item: { type: MediaTypeEnum; txt: string }) {
     display: inline-block;
     box-sizing: border-box;
     margin-left: 10px;
-    width: 240px;
+    width: $w-250;
     height: 100%;
     border-radius: 6px;
     background-color: white;
@@ -753,17 +746,17 @@ function handleStartMedia(item: { type: MediaTypeEnum; txt: string }) {
     }
   }
 }
-// 屏幕宽度小于$large-width的时候
-@media screen and (max-width: $large-width) {
+
+// 屏幕宽度大于1500的时候
+@media screen and (min-width: $w-1500) {
   .push-wrap {
+    width: $w-1475;
+
     .left {
-      width: $medium-left-width;
+      width: $w-1200;
     }
     .right {
-      .list {
-        .item {
-        }
-      }
+      width: $w-250;
     }
   }
 }

+ 51 - 188
src/views/pushByCanvas/index.vue

@@ -13,26 +13,6 @@
           id="canvasRef"
           ref="canvasRef"
         ></canvas>
-        <!-- <DND
-          v-for="(item, index) in appStore.allTrack.filter(
-            (v) => v.video === 1
-          )"
-          :key="index"
-          @move="handleDNDMove"
-        >
-          <video
-            :id="item.id"
-            :data-track-id="item.trackid"
-            autoplay
-            webkit-playsinline="true"
-            playsinline
-            x-webkit-airplay="allow"
-            x5-video-player-type="h5"
-            x5-video-player-fullscreen="true"
-            x5-video-orientation="portraint"
-            muted
-          ></video>
-        </DND> -->
         <div
           v-if="!appStore.allTrack || appStore.allTrack.length <= 0"
           class="add-wrap"
@@ -282,6 +262,9 @@ const canvasSize = reactive({
   width: 1920,
   height: 1080,
 });
+const ratio = ref(0);
+const videoRatio = ref(16 / 9);
+const canvasVideo = ref<HTMLVideoElement>();
 const {
   confirmRoomName,
   getSocketId,
@@ -290,6 +273,8 @@ const {
   sendDanmu,
   keydownDanmu,
   localStream,
+  fabricCanvasEl,
+  canvasVideoStream,
   isLiving,
   allMediaTypeList,
   currentResolutionRatio,
@@ -309,16 +294,7 @@ const {
   remoteVideoRef,
   isSRS,
 });
-const drawCanvasArr = ref<
-  {
-    id: string;
-    cb: any;
-    left: number;
-    top: number;
-    width: number;
-    height: number;
-  }[]
->([]);
+
 watch(
   () => damuList.value.length,
   () => {
@@ -330,168 +306,63 @@ watch(
   }
 );
 
-function handleDNDMove(val: { top: number; left: number; el: HTMLElement }) {
-  console.log('handleDNDMove', val, val.el.id);
-  const el = drawCanvasArr.value.find((item) => item.id === val.el.id);
-  console.log(el, ratio.value, 3333);
-  if (el) {
-    el.left = val.left / ratio.value;
-    el.top = val.top / ratio.value;
-  }
-  // el?.cb(val.left, val.top);
-}
-
-function drawVideo(video: {
-  el: HTMLVideoElement;
-  width: number;
-  height: number;
-}) {
-  // const ctx = canvasRef.value!.getContext('2d')!;
-  let timer;
-
-  function drawCanvas() {
-    // 清空画布
-    // ctx.clearRect(0, 0, canvasSize.width, canvasSize.height);
-
-    drawCanvasArr.value.forEach((item) => {
-      const video = document.querySelector(`#${item.id}`) as HTMLVideoElement;
-      const videoInstance = new fabric.Image(video, {
-        left: item.left || 0,
-        top: item.top || 0,
-      });
-      console.log(canvasRef.value, video, 222221112);
-      fabricCanvas.value!.add(videoInstance);
-
-      // ctx.drawImage(
-      //   video,
-      //   item.left || 0,
-      //   item.top || 0,
-      //   item.width,
-      //   item.height
-      // );
-    });
-
-    console.log(video.el.id);
-    // timer = requestAnimationFrame(drawCanvas);
-  }
-
-  // function stopDrawing() {
-  //   cancelAnimationFrame(timer);
-  // }
-
-  drawCanvas();
-
-  return { drawCanvas };
-}
-
 function createAutoVideo({ stream, id }: { stream: MediaStream; id }) {
   const video = createVideo({});
   video.srcObject = stream;
-  const w = stream.getVideoTracks()[0].getSettings().width;
-  const h = stream.getVideoTracks()[0].getSettings().height;
-  console.log(w, h, 3333);
-
   video.style.width = `1px`;
   video.style.height = `1px`;
-  containerRef.value!.appendChild(video);
+  video.style.position = 'fixed';
+  video.style.bottom = '0';
+  video.style.left = '0';
+  document.body.appendChild(video);
+  const w = stream.getVideoTracks()[0].getSettings().width;
+  const h = stream.getVideoTracks()[0].getSettings().height;
   video.width = w!;
   video.height = h!;
   const dom = new fabric.Image(video, {
     top: 0,
     left: 0,
   });
-  dom.scale(ratio.value);
+  // dom.scale(ratio.value);
   fabricCanvas.value!.add(dom);
   fabric.util.requestAnimFrame(function render() {
     fabricCanvas.value?.renderAll();
     fabric.util.requestAnimFrame(render);
   });
-  video.style.position = 'absolute';
-  video.style.bottom = '0';
-  video.style.left = '0';
-  // };
 
-  // const video = document.querySelector(`#${id}`) as HTMLVideoElement;
-  // const video = createVideo({});
-  // video.id = id;
-  // video.srcObject = stream;
-  // document.body.appendChild(video);
-  // video.onmousemove = () => {
-  //   video.style.cursor = 'move';
-  // };
-  // video.onmouseout = () => {
-  //   video.style.removeProperty('cursor');
-  // };
-  // video.onloadeddata = () => {
-  //   const rect = document
-  //     .querySelector(`#${video.id}`)
-  //     ?.getBoundingClientRect()!;
-  //   const width = rect.width * ratio.value;
-  //   const height = rect.height * ratio.value;
-  //   console.log(width, height, 21223);
-  //   // video.style.width = `${width}px`;
-  //   // video.style.height = `${height}px`;
-  //   // const video = document.querySelector(`#${item.id}`) as HTMLVideoElement;
-  //   // const videoInstance = new fabric.Image(video, {
-  //   //   left: 10,
-  //   //   top: 10,
-  //   //   width,
-  //   //   height,
-  //   //   stroke: 'lightgreen',
-  //   //   strokeWidth: 4,
-  //   //   // objectCaching: false,
-  //   // });
-  //   // const { drawCanvas } = drawVideo({
-  //   //   width: rect.width,
-  //   //   height: rect.height,
-  //   //   el: video,
-  //   // });
-  //   // drawCanvasArr.value.push({
-  //   //   id,
-  //   //   cb: drawCanvas,
-  //   //   left: 0,
-  //   //   top: 0,
-  //   //   width: rect.width,
-  //   //   height: rect.height,
-  //   // });
-  // };
+  canvasVideoStream.value = canvasRef.value?.captureStream();
+  // canvasVideo.value!.srcObject = canvasRef.value?.captureStream();
+  const mixedStream = new MediaStream();
+  canvasVideoStream.value?.getVideoTracks().forEach((track) => {
+    console.log('----', track, track.id);
+    mixedStream.addTrack(track);
+  });
+  mixedStream.getVideoTracks().forEach((track) => {
+    console.log('23ds1', ratio.value, track, track.id, track.kind);
+    // fabricCanvas.value?.setHeight(200);
+    // fabricCanvas.value?.setWidth(200 * videoRatio.value);
+    // const x = containerRef.value!.getBoundingClientRect().width / 200;
+    // ratio.value = ratio.value * x;
+    // console.log(ratio.value);
+    // track.applyConstraints({
+    //   height: 120,
+    // });
+  });
+  console.log(mixedStream, '23321');
+  canvasVideo.value!.srcObject = mixedStream;
 }
 
 function initCanvas() {
   const ins = markRaw(new fabric.Canvas(canvasRef.value!));
-  // const rect = new fabric.Rect({
-  //   top: 10,
-  //   left: 10,
-  //   width: 200,
-  //   height: 200,
-  //   fill: '#aa96da',
-  // });
   ins.setWidth(containerRef.value!.getBoundingClientRect().width);
   ins.setHeight(canvasSize.height * ratio.value);
   fabricCanvas.value = ins;
-
-  console.log(ins, 111111);
-  console.log(fabricCanvas.value.upperCanvasEl.captureStream());
-  // if (canvasRef.value) {
-  //   const rect = canvasRef.value.getBoundingClientRect();
-  //   ratio.value = rect.width / canvasSize.width;
-  // }
-
-  // const stream = canvas.captureStream();
-  // const canvas = new fabric.Canvas('c1');
-  // canvas.add(
-  //   new fabric.Circle({ radius: 30, fill: '#f55', top: 100, left: 100 })
-  // );
-  // console.log(canvas, 2221);
-  // canvas.selectionColor = 'rgba(0,255,0,0.3)';
-  // canvas.selectionBorderColor = 'red';
-  // canvas.selectionLineWidth = 5;
-  // containerRef.value?.appendChild(canvas);
-  // this.__canvases.push(canvas);
+  fabricCanvasEl.value = canvasRef.value!;
+  const video = createVideo({});
+  canvasVideo.value = video;
+  document.body.appendChild(video);
 }
 
-const ratio = ref(0);
-
 onMounted(() => {
   if (containerRef.value) {
     ratio.value =
@@ -546,11 +417,11 @@ async function addMediaOk(val: {
         trackid: event.getAudioTracks()[0].id,
       };
       appStore.setAllTrack([...appStore.allTrack, videoTrack, audioTrack]);
-      addTrack(videoTrack);
+      // addTrack(videoTrack);
       addTrack(audioTrack);
     } else {
       appStore.setAllTrack([...appStore.allTrack, videoTrack]);
-      addTrack(videoTrack);
+      // addTrack(videoTrack);
     }
     nextTick(() => {
       createAutoVideo({
@@ -581,7 +452,7 @@ async function addMediaOk(val: {
       trackid: event.getVideoTracks()[0].id,
     };
     appStore.setAllTrack([...appStore.allTrack, track]);
-    addTrack(track);
+    // addTrack(track);
     nextTick(() => {
       createAutoVideo({ stream: event, id: track.id });
     });
@@ -630,23 +501,16 @@ function handleStartMedia(item: { type: MediaTypeEnum; txt: string }) {
 
 <style lang="scss" scoped>
 .push-wrap {
+  display: flex;
+  justify-content: space-between;
   margin: 20px auto 0;
-  min-width: $large-width;
-  // height: 700px;
-  text-align: center;
-  .testRef {
-    // width: 600px;
-    // background-color: red;
-    :deep(canvas) {
-      // width: 100%;
-    }
-  }
+  width: $w-1275;
   .left {
     position: relative;
     display: inline-block;
     overflow: hidden;
     box-sizing: border-box;
-    width: $large-left-width;
+    width: $w-1000;
     height: 100%;
     border-radius: 6px;
     background-color: white;
@@ -750,7 +614,7 @@ function handleStartMedia(item: { type: MediaTypeEnum; txt: string }) {
     display: inline-block;
     box-sizing: border-box;
     margin-left: 10px;
-    width: 240px;
+    width: $w-250;
     height: 100%;
     border-radius: 6px;
     background-color: white;
@@ -844,17 +708,16 @@ function handleStartMedia(item: { type: MediaTypeEnum; txt: string }) {
     }
   }
 }
-// 屏幕宽度小于$large-width的时候
-@media screen and (max-width: $large-width) {
+
+// 屏幕宽度大于1500的时候
+@media screen and (min-width: $w-1500) {
   .push-wrap {
+    width: $w-1475;
     .left {
-      width: $medium-left-width;
+      width: $w-1200;
     }
     .right {
-      .list {
-        .item {
-        }
-      }
+      width: $w-250;
     }
   }
 }