Ver Fonte

feat: msr推流

shuisheng há 2 anos atrás
pai
commit
28b44ec2d4

+ 1 - 1
package.json

@@ -37,12 +37,12 @@
     "billd-html-webpack-plugin": "^1.0.5",
     "billd-scss": "^0.0.7",
     "billd-utils": "^0.0.15",
-    "browser-tool": "^1.0.5",
     "fabric": "^5.3.0",
     "flv.js": "^1.6.2",
     "js-cookie": "^3.0.5",
     "localforage": "^1.10.0",
     "mpegts.js": "^1.7.3",
+    "msr": "^1.3.4",
     "naive-ui": "^2.34.4",
     "pinia": "^2.0.33",
     "pinia-plugin-persistedstate": "^3.2.0",

+ 7 - 7
pnpm-lock.yaml

@@ -20,9 +20,6 @@ dependencies:
   billd-utils:
     specifier: ^0.0.15
     version: 0.0.15(typescript@5.1.6)
-  browser-tool:
-    specifier: ^1.0.5
-    version: 1.0.5
   fabric:
     specifier: ^5.3.0
     version: 5.3.0
@@ -38,6 +35,9 @@ dependencies:
   mpegts.js:
     specifier: ^1.7.3
     version: 1.7.3
+  msr:
+    specifier: ^1.3.4
+    version: 1.3.4
   naive-ui:
     specifier: ^2.34.4
     version: 2.34.4(vue@3.3.4)
@@ -3765,10 +3765,6 @@ packages:
     dev: false
     optional: true
 
-  /browser-tool@1.0.5:
-    resolution: {integrity: sha512-iuBYclNsu9LI5CeV7AEh9/PGyCUAsjs51oIlfAAg/BvOIeVx9uizgzIFA+COEyPJhgbciIHhQqpUL0ib1tGRVg==}
-    dev: false
-
   /browserslist@4.21.5:
     resolution: {integrity: sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==}
     engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
@@ -7737,6 +7733,10 @@ packages:
   /ms@2.1.3:
     resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
 
+  /msr@1.3.4:
+    resolution: {integrity: sha512-vx1gmfKCacOylK5YNSlWWrWcqMfOjSgQQn3rb4NA830LlBrXyWVHJQdbILG4xiK9Xz77K35ve2CLodX2gEzlKA==}
+    dev: false
+
   /multicast-dns@7.2.5:
     resolution: {integrity: sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==}
     hasBin: true

+ 20 - 0
src/hooks/use-push.ts

@@ -7,6 +7,7 @@ import {
   fetchUserHasLiveRoom,
 } from '@/api/userLiveRoom';
 import { DanmuMsgTypeEnum, ILiveRoom, IMessage } from '@/interface';
+import { WsMsrBlobType } from '@/interface-ws';
 import { WsMsgTypeEnum } from '@/network/webSocket';
 import { useAppStore } from '@/store/app';
 import { useNetworkStore } from '@/store/network';
@@ -292,6 +293,24 @@ export function usePush() {
     closeRtc();
   }
 
+  function sendBlob(data: { blob; blobId: string; chunk }) {
+    roomLiving.value = false;
+    localStream.value = undefined;
+    const instance = networkStore.wsMap.get(roomId.value);
+    if (instance) {
+      instance.send<WsMsrBlobType['data']>({
+        msgType: WsMsgTypeEnum.msrBlob,
+        data: {
+          live_room_id: Number(roomId.value),
+          blob: data.blob,
+          blob_id: data.blobId,
+          chunk: data.chunk,
+        },
+      });
+    }
+    closeRtc();
+  }
+
   function roomNameIsOk() {
     if (!roomName.value.length) {
       window.$message.warning('请输入房间名!');
@@ -349,6 +368,7 @@ export function usePush() {
     endLive,
     sendDanmu,
     keydownDanmu,
+    sendBlob,
     mySocketId,
     lastCoverImg,
     localStream,

+ 3 - 0
src/hooks/use-srs-ws.ts

@@ -148,6 +148,9 @@ export const useSrsWs = () => {
         type,
       },
     });
+    if (type === LiveRoomTypeEnum.user_msr) {
+      return;
+    }
     if (type !== LiveRoomTypeEnum.user_wertc) {
       startNewWebRtc({
         videoEl: createVideo({}),

+ 8 - 0
src/interface-ws.ts

@@ -93,3 +93,11 @@ export type WsCandidateType = IWsFormat<{
   receiver: string;
   sender: string;
 }>;
+
+export type WsMsrBlobType = IWsFormat<{
+  live_room_id: number;
+  blob: any;
+  blob_id: string;
+  /** 每个blob多少秒 */
+  chunk: number;
+}>;

+ 1 - 0
src/interface.ts

@@ -189,6 +189,7 @@ export enum LiveRoomTypeEnum {
   user_wertc, // 主播使用webrtc直播
   user_srs, // 主播使用srs直播
   user_obs, // 主播使用obs/ffmpeg直播
+  user_msr, // 主播使用msr直播
 }
 
 export interface BilldHtmlWebpackPluginLog {

+ 6 - 0
src/layout/pc/head/index.vue

@@ -219,6 +219,12 @@
               >
                 <div class="txt">webrtc开播</div>
               </a>
+              <a
+                class="item"
+                @click.prevent="handleStartLive(LiveRoomTypeEnum.user_msr)"
+              >
+                <div class="txt">msr开播</div>
+              </a>
             </div>
           </template>
         </Dropdown>

+ 2 - 0
src/network/webSocket.ts

@@ -50,6 +50,8 @@ export enum WsMsgTypeEnum {
   offer = 'offer',
   answer = 'answer',
   candidate = 'candidate',
+
+  msrBlob = 'msrBlob',
 }
 
 export function prettierReceiveWsMsg(...arg) {

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

@@ -283,6 +283,7 @@ import {
   VolumeMuteOutline,
 } from '@vicons/ionicons5';
 import { fabric } from 'fabric';
+import MediaStreamRecorder from 'msr';
 import {
   Raw,
   markRaw,
@@ -298,7 +299,7 @@ import * as workerTimers from 'worker-timers';
 import { mediaTypeEnumMap } from '@/constant';
 import { usePush } from '@/hooks/use-push';
 import { useRTCParams } from '@/hooks/use-rtcParams';
-import { DanmuMsgTypeEnum, MediaTypeEnum } from '@/interface';
+import { DanmuMsgTypeEnum, LiveRoomTypeEnum, MediaTypeEnum } from '@/interface';
 import { AppRootState, useAppStore } from '@/store/app';
 import { usePiniaCacheStore } from '@/store/cache';
 import { useNetworkStore } from '@/store/network';
@@ -331,6 +332,7 @@ const {
   endLive,
   sendDanmu,
   keydownDanmu,
+  sendBlob,
   mySocketId,
   lastCoverImg,
   canvasVideoStream,
@@ -369,6 +371,22 @@ const wrapSize = reactive({
 const workerTimerId = ref(-1);
 const bodyAppendChildElArr = ref<HTMLElement[]>([]);
 const liveType = Number(route.query.liveType);
+const mediaRecorder = ref();
+
+function handleMsr(stream: MediaStream) {
+  mediaRecorder.value = new MediaStreamRecorder(stream);
+  setInterval(() => {
+    console.log(stream.getAudioTracks());
+  }, 1000);
+  mediaRecorder.value.mimeType = 'video/webm';
+  const chunk = 1000 * 2;
+  let id = 0;
+  mediaRecorder.value.ondataavailable = function (blob) {
+    id += 1;
+    sendBlob({ blob, blobId: `${id}`, chunk });
+  };
+  mediaRecorder.value.start(chunk);
+}
 
 watch(
   () => damuList.value.length,
@@ -391,6 +409,7 @@ onMounted(() => {
 });
 
 onUnmounted(() => {
+  mediaRecorder.value.stop();
   bodyAppendChildElArr.value.forEach((el) => {
     el.remove();
   });
@@ -503,6 +522,14 @@ function handleStartLive() {
   handleMixedAudio();
   lastCoverImg.value = generateBase64(pushCanvasRef.value!);
   startLive({ type: liveType, receiver: mySocketId.value });
+  if (liveType === LiveRoomTypeEnum.user_msr) {
+    const stream = pushCanvasRef.value!.captureStream();
+    // @ts-ignore
+    const audioTrack = webaudioVideo.value!.captureStream().getAudioTracks()[0];
+    // @ts-ignore
+    stream.addTrack(audioTrack);
+    handleMsr(stream);
+  }
 }
 
 function handleScale({ width, height }: { width: number; height: number }) {