shuisheng 1 vuosi sitten
vanhempi
sitoutus
b2c7beac49

+ 2 - 1
package.json

@@ -44,6 +44,7 @@
     "billd-html-webpack-plugin": "^1.0.6",
     "billd-html-webpack-plugin": "^1.0.6",
     "billd-scss": "^0.0.8",
     "billd-scss": "^0.0.8",
     "billd-utils": "^0.0.22",
     "billd-utils": "^0.0.22",
+    "cos-js-sdk-v5": "^1.8.6",
     "fabric": "^5.3.0",
     "fabric": "^5.3.0",
     "flv.js": "^1.6.2",
     "flv.js": "^1.6.2",
     "js-cookie": "^3.0.5",
     "js-cookie": "^3.0.5",
@@ -135,4 +136,4 @@
     "webpackbar": "^5.0.2",
     "webpackbar": "^5.0.2",
     "windicss-webpack-plugin": "^1.7.7"
     "windicss-webpack-plugin": "^1.7.7"
   }
   }
-}
+}

+ 18 - 0
pnpm-lock.yaml

@@ -32,6 +32,9 @@ importers:
       billd-utils:
       billd-utils:
         specifier: ^0.0.22
         specifier: ^0.0.22
         version: 0.0.22(tslib@2.6.1)(typescript@5.1.6)
         version: 0.0.22(tslib@2.6.1)(typescript@5.1.6)
+      cos-js-sdk-v5:
+        specifier: ^1.8.6
+        version: 1.8.6
       fabric:
       fabric:
         specifier: ^5.3.0
         specifier: ^5.3.0
         version: 5.3.0
         version: 5.3.0
@@ -2756,6 +2759,9 @@ packages:
   core-util-is@1.0.3:
   core-util-is@1.0.3:
     resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
     resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
 
 
+  cos-js-sdk-v5@1.8.6:
+    resolution: {integrity: sha512-FCygXb5cNQhlwmsGf5B5WnqFsx+cQiPW+I4SWjVg08IaSA+1Uu8VqpP+FI7+LIA9PoC3GpX6ehgV0olsfEnoHg==}
+
   cos-nodejs-sdk-v5@2.13.1:
   cos-nodejs-sdk-v5@2.13.1:
     resolution: {integrity: sha512-lvmAAUGisiWeiilVkpFQ2Eh9Bl/0aiQuUF4V8GvE0ioSmKQFdb3Z8lrjJNY1CeMtPCtPq9WWZqyevgPwndyEnA==}
     resolution: {integrity: sha512-lvmAAUGisiWeiilVkpFQ2Eh9Bl/0aiQuUF4V8GvE0ioSmKQFdb3Z8lrjJNY1CeMtPCtPq9WWZqyevgPwndyEnA==}
     engines: {node: '>= 6'}
     engines: {node: '>= 6'}
@@ -3622,6 +3628,10 @@ packages:
     resolution: {integrity: sha512-utnwm92SyozgA3hhH2I8qldf2lBqm6qHOICawRNRFu1qMe3+oqr+GcXjGqTmXTMGE5T4eC03kr/rlh5C1IRdZA==}
     resolution: {integrity: sha512-utnwm92SyozgA3hhH2I8qldf2lBqm6qHOICawRNRFu1qMe3+oqr+GcXjGqTmXTMGE5T4eC03kr/rlh5C1IRdZA==}
     hasBin: true
     hasBin: true
 
 
+  fast-xml-parser@4.5.0:
+    resolution: {integrity: sha512-/PlTQCI96+fZMAOLMZK4CWG1ItCbfZ/0jx7UIJFChPNrx7tcEgerUgWbeieCM9MfHInUDyK8DWYZ+YrywDJuTg==}
+    hasBin: true
+
   fastest-levenshtein@1.0.16:
   fastest-levenshtein@1.0.16:
     resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==}
     resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==}
     engines: {node: '>= 4.9.1'}
     engines: {node: '>= 4.9.1'}
@@ -9921,6 +9931,10 @@ snapshots:
 
 
   core-util-is@1.0.3: {}
   core-util-is@1.0.3: {}
 
 
+  cos-js-sdk-v5@1.8.6:
+    dependencies:
+      fast-xml-parser: 4.5.0
+
   cos-nodejs-sdk-v5@2.13.1:
   cos-nodejs-sdk-v5@2.13.1:
     dependencies:
     dependencies:
       conf: 9.0.2
       conf: 9.0.2
@@ -10948,6 +10962,10 @@ snapshots:
     dependencies:
     dependencies:
       strnum: 1.0.5
       strnum: 1.0.5
 
 
+  fast-xml-parser@4.5.0:
+    dependencies:
+      strnum: 1.0.5
+
   fastest-levenshtein@1.0.16: {}
   fastest-levenshtein@1.0.16: {}
 
 
   fastq@1.15.0:
   fastq@1.15.0:

+ 2 - 2
src/App.vue

@@ -25,6 +25,7 @@ import { useRoute } from 'vue-router';
 import { fetchSettingsList } from '@/api/settings';
 import { fetchSettingsList } from '@/api/settings';
 import { THEME_COLOR, appBuildInfo } from '@/constant';
 import { THEME_COLOR, appBuildInfo } from '@/constant';
 import { useCheckUpdate } from '@/hooks/use-common';
 import { useCheckUpdate } from '@/hooks/use-common';
+import { useGoogleAd } from '@/hooks/use-google-ad';
 import { loginMessage } from '@/hooks/use-login';
 import { loginMessage } from '@/hooks/use-login';
 import { useCacheStore } from '@/store/cache';
 import { useCacheStore } from '@/store/cache';
 import { useUserStore } from '@/store/user';
 import { useUserStore } from '@/store/user';
@@ -36,7 +37,6 @@ import { fetchAreaList } from './api/area';
 import { fetchGlobalMsgMyList } from './api/globalMsg';
 import { fetchGlobalMsgMyList } from './api/globalMsg';
 import { useTip } from './hooks/use-tip';
 import { useTip } from './hooks/use-tip';
 import { useAppStore } from './store/app';
 import { useAppStore } from './store/app';
-import { initAdsbygoogle } from './utils/google-ad';
 
 
 const { checkUpdate } = useCheckUpdate();
 const { checkUpdate } = useCheckUpdate();
 const appStore = useAppStore();
 const appStore = useAppStore();
@@ -67,7 +67,7 @@ watch(
 );
 );
 
 
 onMounted(() => {
 onMounted(() => {
-  initAdsbygoogle();
+  useGoogleAd();
   initGlobalData();
   initGlobalData();
   checkUpdate({
   checkUpdate({
     htmlUrl: getHostnameUrl(),
     htmlUrl: getHostnameUrl(),

+ 10 - 0
src/api/other.ts

@@ -1,3 +1,4 @@
+import { ICredential } from '@/interface';
 import request from '@/utils/request';
 import request from '@/utils/request';
 
 
 /**
 /**
@@ -6,3 +7,12 @@ import request from '@/utils/request';
 export function fetchServerInfo() {
 export function fetchServerInfo() {
   return request.get('/other/server_info');
   return request.get('/other/server_info');
 }
 }
+
+export function fetchGetPolicyByRes({ prefix }) {
+  return request.get<{ err; credential: ICredential }>(
+    '/other/get_policy_by_res',
+    {
+      params: { prefix },
+    }
+  );
+}

+ 9 - 13
src/api/srs.ts

@@ -1,21 +1,17 @@
 import request from '@/utils/request';
 import request from '@/utils/request';
 
 
-export function fetchRtcV1Publish(data: {
-  api: string;
-  clientip: string | null;
-  sdp: string;
-  streamurl: string;
-  tid: string;
-}) {
+export function fetchRtcV1Publish(data: { sdp: string; streamurl: string }) {
   return request.post(`/srs/rtcV1Publish`, data);
   return request.post(`/srs/rtcV1Publish`, data);
 }
 }
 
 
-export function fetchRtcV1Play(data: {
-  api: string;
-  clientip: string | null;
+export function fetchRtcV1Play(data: { sdp: string; streamurl: string }) {
+  return request.post(`/srs/rtcV1Play`, data);
+}
+
+export function fetchRtcV1Whep(data: {
+  app: string;
+  stream: string;
   sdp: string;
   sdp: string;
-  streamurl: string;
-  tid: string;
 }) {
 }) {
-  return request.post(`/srs/rtcV1Play`, data);
+  return request.post(`/srs/rtcV1Whep`, data);
 }
 }

+ 6 - 1
src/components/VideoControls/index.vue

@@ -286,7 +286,7 @@ function changeLiveLine(item: LiveLineEnum) {
     window.$message.info('不支持该线路!');
     window.$message.info('不支持该线路!');
     return;
     return;
   }
   }
-  appStore.setLiveLine(item);
+  appStore.liveLine = item;
 }
 }
 </script>
 </script>
 
 
@@ -381,6 +381,11 @@ function changeLiveLine(item: LiveLineEnum) {
         }
         }
       }
       }
     }
     }
+    .line {
+      .list {
+        width: 75px;
+      }
+    }
 
 
     & > :last-child {
     & > :last-child {
       margin-right: 0px;
       margin-right: 0px;

+ 5 - 1
src/utils/google-ad.ts → src/hooks/use-google-ad.ts

@@ -1,4 +1,8 @@
-export const initAdsbygoogle = () => {
+import { useAppStore } from '@/store/app';
+
+export const useGoogleAd = () => {
+  const appStore = useAppStore();
+  if (!appStore.useGoogleAd) return;
   try {
   try {
     window.onload = () => {
     window.onload = () => {
       // @ts-ignore
       // @ts-ignore

+ 11 - 19
src/hooks/use-play.ts

@@ -172,15 +172,6 @@ export function useFlvPlay() {
     }
     }
   );
   );
 
 
-  watch(
-    () => appStore.playing,
-    (newVal) => {
-      if (newVal) {
-        setPlay();
-      }
-    }
-  );
-
   function startFlvPlay(data: { flvurl: string }) {
   function startFlvPlay(data: { flvurl: string }) {
     return new Promise((resolve) => {
     return new Promise((resolve) => {
       function main() {
       function main() {
@@ -196,7 +187,12 @@ export function useFlvPlay() {
             isLive: true,
             isLive: true,
             url: handlePlayUrl(data.flvurl),
             url: handlePlayUrl(data.flvurl),
           });
           });
-          const videoEl = createVideo({});
+          const videoEl = createVideo({
+            appendChild: true,
+            muted: cacheStore.muted,
+            autoplay: true,
+          });
+          // const videoEl = createVideo({});
           videoEl.addEventListener('play', () => {
           videoEl.addEventListener('play', () => {
             console.log('flv-play');
             console.log('flv-play');
           });
           });
@@ -331,24 +327,20 @@ export function useHlsPlay() {
     }
     }
   );
   );
 
 
-  watch(
-    () => appStore.playing,
-    (newVal) => {
-      if (newVal) {
-        setPlay();
-      }
-    }
-  );
-
   function startHlsPlay(data: { hlsurl: string }) {
   function startHlsPlay(data: { hlsurl: string }) {
     return new Promise((resolve) => {
     return new Promise((resolve) => {
       function main() {
       function main() {
         console.log('startHlsPlay', data.hlsurl);
         console.log('startHlsPlay', data.hlsurl);
         destroyHls();
         destroyHls();
         const videoEl = createVideo({
         const videoEl = createVideo({
+          appendChild: true,
           muted: cacheStore.muted,
           muted: cacheStore.muted,
           autoplay: true,
           autoplay: true,
         });
         });
+        // const videoEl = createVideo({
+        //   muted: cacheStore.muted,
+        //   autoplay: true,
+        // });
         hlsPlayer.value = videoJs(
         hlsPlayer.value = videoJs(
           videoEl,
           videoEl,
           {
           {

+ 68 - 81
src/hooks/use-pull.ts

@@ -5,6 +5,7 @@ import { useRoute } from 'vue-router';
 import { commentAuthTip, loginTip } from '@/hooks/use-login';
 import { commentAuthTip, loginTip } from '@/hooks/use-login';
 import { useFlvPlay, useHlsPlay } from '@/hooks/use-play';
 import { useFlvPlay, useHlsPlay } from '@/hooks/use-play';
 import { useWebsocket } from '@/hooks/use-websocket';
 import { useWebsocket } from '@/hooks/use-websocket';
+import { useWebRtcRtmpToRtc } from '@/hooks/webrtc/rtmpToRtc';
 import {
 import {
   DanmuMsgTypeEnum,
   DanmuMsgTypeEnum,
   LiveLineEnum,
   LiveLineEnum,
@@ -21,7 +22,7 @@ import {
   LiveRoomUseCDNEnum,
   LiveRoomUseCDNEnum,
 } from '@/types/ILiveRoom';
 } from '@/types/ILiveRoom';
 import { WsMessageType, WsMsgTypeEnum } from '@/types/websocket';
 import { WsMessageType, WsMsgTypeEnum } from '@/types/websocket';
-import { videoFullBox, videoToCanvas } from '@/utils';
+import { createVideo, videoFullBox, videoToCanvas } from '@/utils';
 
 
 export function usePull() {
 export function usePull() {
   const route = useRoute();
   const route = useRoute();
@@ -44,28 +45,30 @@ export function usePull() {
   const isRemoteDesk = ref(false);
   const isRemoteDesk = ref(false);
   const videoElArr = ref<HTMLVideoElement[]>([]);
   const videoElArr = ref<HTMLVideoElement[]>([]);
   const remoteVideo = ref<HTMLElement[]>([]);
   const remoteVideo = ref<HTMLElement[]>([]);
+  const remoteStream = ref<MediaStream[]>([]);
   const { mySocketId, initWs, roomLiving, anchorInfo, liveUserList, damuList } =
   const { mySocketId, initWs, roomLiving, anchorInfo, liveUserList, damuList } =
     useWebsocket();
     useWebsocket();
+  const { updateWebRtcRtmpToRtcConfig, webRtcRtmpToRtc } = useWebRtcRtmpToRtc();
+
   const { flvVideoEl, flvIsPlaying, startFlvPlay, destroyFlv } = useFlvPlay();
   const { flvVideoEl, flvIsPlaying, startFlvPlay, destroyFlv } = useFlvPlay();
   const { hlsVideoEl, hlsIsPlaying, startHlsPlay, destroyHls } = useHlsPlay();
   const { hlsVideoEl, hlsIsPlaying, startHlsPlay, destroyHls } = useHlsPlay();
   const stopDrawingArr = ref<any[]>([]);
   const stopDrawingArr = ref<any[]>([]);
+  const rtmpToRtcVido = ref<HTMLVideoElement>();
+
   let changeWrapSizeFn;
   let changeWrapSizeFn;
 
 
   onUnmounted(() => {
   onUnmounted(() => {
     handleStopDrawing();
     handleStopDrawing();
+    destroyFlv();
+    destroyHls();
   });
   });
 
 
   function handleStopDrawing() {
   function handleStopDrawing() {
-    destroyFlv();
-    destroyHls();
     changeWrapSizeFn = undefined;
     changeWrapSizeFn = undefined;
     stopDrawingArr.value.forEach((cb) => cb());
     stopDrawingArr.value.forEach((cb) => cb());
     stopDrawingArr.value = [];
     stopDrawingArr.value = [];
     remoteVideo.value.forEach((el) => el.remove());
     remoteVideo.value.forEach((el) => el.remove());
     remoteVideo.value = [];
     remoteVideo.value = [];
-    if (isRemoteDesk.value && videoWrapRef.value) {
-      videoWrapRef.value.removeAttribute('style');
-    }
   }
   }
 
 
   function handleVideoWrapResize() {
   function handleVideoWrapResize() {
@@ -78,8 +81,8 @@ export function usePull() {
   }
   }
 
 
   function videoPlay(videoEl: HTMLVideoElement) {
   function videoPlay(videoEl: HTMLVideoElement) {
-    stopDrawingArr.value = [];
     stopDrawingArr.value.forEach((cb) => cb());
     stopDrawingArr.value.forEach((cb) => cb());
+    stopDrawingArr.value = [];
     if (appStore.videoControls.renderMode === LiveRenderEnum.canvas) {
     if (appStore.videoControls.renderMode === LiveRenderEnum.canvas) {
       if (videoEl && videoWrapRef.value) {
       if (videoEl && videoWrapRef.value) {
         const rect = videoWrapRef.value.getBoundingClientRect();
         const rect = videoWrapRef.value.getBoundingClientRect();
@@ -120,17 +123,25 @@ export function usePull() {
     }
     }
   }
   }
 
 
-  watch(hlsVideoEl, (newval) => {
-    if (newval) {
-      videoPlay(newval);
+  watch(
+    () => hlsVideoEl.value,
+    (newval) => {
+      if (newval) {
+        // @ts-ignore
+        remoteStream.value.push(newval.captureStream());
+      }
     }
     }
-  });
+  );
 
 
-  watch(flvVideoEl, (newval) => {
-    if (newval) {
-      videoPlay(newval);
+  watch(
+    () => flvVideoEl.value,
+    (newval) => {
+      if (newval) {
+        // @ts-ignore
+        remoteStream.value.push(newval.captureStream());
+      }
     }
     }
-  });
+  );
 
 
   watch(
   watch(
     () => appStore.videoControlsValue.pageFullMode,
     () => appStore.videoControlsValue.pageFullMode,
@@ -140,64 +151,17 @@ export function usePull() {
   );
   );
 
 
   watch(
   watch(
-    () => appStore.videoControls.renderMode,
+    [() => appStore.videoControls.renderMode, () => remoteStream.value],
     () => {
     () => {
-      if (appStore.liveRoomInfo) {
-        handlePlay(appStore.liveRoomInfo);
-      }
-    }
-  );
-
-  watch(
-    () => networkStore.rtcMap,
-    (newVal) => {
-      if (newVal.size) {
-        roomLiving.value = true;
-        videoLoading.value = false;
-        appStore.playing = true;
-        // cacheStore.muted = false;
-      }
-      if (
-        isRemoteDesk.value ||
-        appStore.liveRoomInfo?.type === LiveRoomTypeEnum.wertc_meeting_one ||
-        appStore.liveRoomInfo?.type === LiveRoomTypeEnum.wertc_live ||
-        appStore.liveRoomInfo?.type === LiveRoomTypeEnum.pk ||
-        appStore.liveRoomInfo?.type === LiveRoomTypeEnum.tencent_css_pk
-      ) {
-        newVal.forEach((item) => {
-          if (appStore.allTrack.find((v) => v.mediaName === item.receiver)) {
-            return;
-          }
-          const rect = videoWrapRef.value?.getBoundingClientRect();
-          if (rect) {
-            videoFullBox({
-              wrapSize: {
-                width: rect.width,
-                height: rect.height,
-              },
-              videoEl: item.videoEl,
-              videoResize: ({ w, h }) => {
-                videoResolution.value = `${w}x${h}`;
-              },
-            });
-            remoteVideo.value.push(item.videoEl);
-            videoElArr.value.push(item.videoEl);
-          }
-        });
-        nextTick(() => {
-          if (isRemoteDesk.value && videoWrapRef.value) {
-            if (newVal.size) {
-              videoWrapRef.value.style.display = 'inline-block';
-            } else {
-              videoWrapRef.value.style.removeProperty('display');
-            }
-          }
-        });
-      }
+      handleStopDrawing();
+      remoteStream.value.forEach((v) => {
+        const el = createVideo({});
+        el.srcObject = v;
+        videoPlay(el);
+      });
     },
     },
     {
     {
       deep: true,
       deep: true,
-      immediate: true,
     }
     }
   );
   );
 
 
@@ -214,11 +178,34 @@ export function usePull() {
     }
     }
   );
   );
 
 
+  function handleRtmpToRtcPlay() {
+    console.log('handleRtmpToRtcPlay');
+    handleStopDrawing();
+    videoLoading.value = true;
+    appStore.liveLine = LiveLineEnum['rtmp-rtc'];
+    updateWebRtcRtmpToRtcConfig({
+      isPk: false,
+      roomId: roomId.value,
+    });
+    webRtcRtmpToRtc.newWebRtc({
+      sender: mySocketId.value,
+      receiver: 'rtmpToRtc',
+      videoEl: createVideo({}),
+      sucessCb: (stream) => {
+        remoteStream.value.push(stream);
+      },
+    });
+    webRtcRtmpToRtc.sendOffer({
+      sender: mySocketId.value,
+      receiver: 'rtmpToRtc',
+    });
+  }
+
   function handleHlsPlay() {
   function handleHlsPlay() {
     console.log('handleHlsPlay', hlsurl.value);
     console.log('handleHlsPlay', hlsurl.value);
     handleStopDrawing();
     handleStopDrawing();
     videoLoading.value = true;
     videoLoading.value = true;
-    appStore.setLiveLine(LiveLineEnum.hls);
+    appStore.liveLine = LiveLineEnum.hls;
     startHlsPlay({
     startHlsPlay({
       hlsurl: hlsurl.value,
       hlsurl: hlsurl.value,
     });
     });
@@ -228,7 +215,7 @@ export function usePull() {
     console.log('handleFlvPlay', flvurl.value);
     console.log('handleFlvPlay', flvurl.value);
     handleStopDrawing();
     handleStopDrawing();
     videoLoading.value = true;
     videoLoading.value = true;
-    appStore.setLiveLine(LiveLineEnum.flv);
+    appStore.liveLine = LiveLineEnum.flv;
     startFlvPlay({
     startFlvPlay({
       flvurl: flvurl.value,
       flvurl: flvurl.value,
     });
     });
@@ -281,7 +268,7 @@ export function usePull() {
         LiveRoomTypeEnum.wertc_meeting_two,
         LiveRoomTypeEnum.wertc_meeting_two,
       ].includes(data.type!)
       ].includes(data.type!)
     ) {
     ) {
-      appStore.setLiveLine(LiveLineEnum.rtc);
+      appStore.liveLine = LiveLineEnum.rtc;
     }
     }
   }
   }
 
 
@@ -325,6 +312,11 @@ export function usePull() {
     () => appStore.liveLine,
     () => appStore.liveLine,
     (newVal) => {
     (newVal) => {
       console.log('liveLine变了', newVal);
       console.log('liveLine变了', newVal);
+      handleStopDrawing();
+      destroyFlv();
+      destroyHls();
+      remoteStream.value = [];
+
       if (!roomLiving.value) {
       if (!roomLiving.value) {
         return;
         return;
       }
       }
@@ -337,6 +329,9 @@ export function usePull() {
           break;
           break;
         case LiveLineEnum.rtc:
         case LiveLineEnum.rtc:
           break;
           break;
+        case LiveLineEnum['rtmp-rtc']:
+          handleRtmpToRtcPlay();
+          break;
       }
       }
     }
     }
   );
   );
@@ -396,15 +391,6 @@ export function usePull() {
     }
     }
   );
   );
 
 
-  watch(
-    () => appStore.remoteDesk.isClose,
-    (newval) => {
-      if (newval) {
-        handleStopDrawing();
-      }
-    }
-  );
-
   function initRoomId(id: string) {
   function initRoomId(id: string) {
     roomId.value = id;
     roomId.value = id;
   }
   }
@@ -554,5 +540,6 @@ export function usePull() {
     liveRoomInfo,
     liveRoomInfo,
     anchorInfo,
     anchorInfo,
     initRoomId,
     initRoomId,
+    rtmpToRtcVido,
   };
   };
 }
 }

+ 81 - 0
src/hooks/use-upload-tencentcloud.ts

@@ -0,0 +1,81 @@
+import { getRandomString } from 'billd-utils';
+import COS from 'cos-js-sdk-v5';
+
+import { fetchGetPolicyByRes } from '@/api/other';
+import { TENCENTCLOUD_COS } from '@/spec-config';
+import { getHash } from '@/utils';
+
+export const useUpload = async ({
+  prefix,
+  file,
+  onProgress,
+}: {
+  prefix: string;
+  file: File;
+  onProgress?: any;
+}) => {
+  const { hash, ext } = await getHash(file);
+  // 初始化实例
+  const cos = new COS({
+    // getAuthorization 必选参数
+    async getAuthorization(_options, callback) {
+      // 初始化时不会调用,只有调用 cos 方法(例如 cos.putObject)时才会进入
+      // 异步获取临时密钥
+      // 服务端 JS 和 PHP 例子:https://github.com/tencentyun/cos-js-sdk-v5/blob/master/server/
+      // 服务端其他语言参考 COS STS SDK :https://github.com/tencentyun/qcloud-cos-sts-sdk
+      // STS 详细文档指引看:https://www.tencentcloud.com/document/product/436/14048
+      const data = await fetchGetPolicyByRes({ prefix });
+      if (data.code == 200 && !data.data.err) {
+        const { credentials, startTime, expiredTime } = data.data.credential;
+        callback({
+          TmpSecretId: credentials.tmpSecretId,
+          TmpSecretKey: credentials.tmpSecretKey,
+          SecurityToken: credentials.sessionToken,
+          // 建议返回服务器时间作为签名的开始时间,避免用户浏览器本地时间偏差过大导致签名错误
+          StartTime: startTime, // 时间戳,单位秒,如:1580000000
+          ExpiredTime: expiredTime, // 时间戳,单位秒,如:1580000000
+        });
+      }
+    },
+  });
+  return new Promise<{
+    flag: boolean;
+    respBody?: any;
+    respErr?: any;
+    respInfo?: any;
+    resultUrl?: string;
+  }>((resolve) => {
+    const cosKey = `${prefix}${hash}__${getRandomString(4)}.${ext}`;
+    cos.uploadFile(
+      {
+        Bucket:
+          TENCENTCLOUD_COS['res-1305322458']
+            .Bucket /* 填写自己的 bucket,必须字段 */,
+        Region:
+          TENCENTCLOUD_COS['res-1305322458']
+            .Region /* 存储桶所在地域,必须字段 */,
+        Key: cosKey /* 存储在桶里的对象键(例如:1.jpg,a/b/test.txt,图片.jpg)支持中文,必须字段 */,
+        Body: file, // 上传文件对象
+        SliceSize:
+          1024 *
+          1024 *
+          5 /* 触发分块上传的阈值,超过5MB使用分块上传,小于5MB使用简单上传。可自行设置,非必须 */,
+        onProgress(progressData) {
+          onProgress({ percent: progressData.percent });
+        },
+      },
+      function (err, data) {
+        if (err) {
+          console.log('上传失败', err);
+          resolve({ flag: false, respErr: err });
+        } else {
+          console.log('上传成功', data);
+          resolve({
+            flag: true,
+            resultUrl: `${TENCENTCLOUD_COS['res-1305322458'].url}/${cosKey}`,
+          });
+        }
+      }
+    );
+  });
+};

+ 0 - 4
src/hooks/webrtc/forwardAll.ts

@@ -1,4 +1,3 @@
-import { getRandomString } from 'billd-utils';
 import { ref } from 'vue';
 import { ref } from 'vue';
 
 
 import { fetchRtcV1Publish } from '@/api/srs';
 import { fetchRtcV1Publish } from '@/api/srs';
@@ -87,15 +86,12 @@ export const useForwardAll = () => {
             return;
             return;
           }
           }
           const answerRes = await fetchRtcV1Publish({
           const answerRes = await fetchRtcV1Publish({
-            api: `/rtc/v1/publish/`,
-            clientip: null,
             sdp: offerSdp.sdp!,
             sdp: offerSdp.sdp!,
             streamurl: `${myLiveRoom.rtmp_url!}?${
             streamurl: `${myLiveRoom.rtmp_url!}?${
               SRS_CB_URL_PARAMS.publishKey
               SRS_CB_URL_PARAMS.publishKey
             }=${myLiveRoom.key!}&${SRS_CB_URL_PARAMS.publishType}=${
             }=${myLiveRoom.key!}&${SRS_CB_URL_PARAMS.publishType}=${
               isPk.value ? LiveRoomTypeEnum.pk : LiveRoomTypeEnum.forward_all
               isPk.value ? LiveRoomTypeEnum.pk : LiveRoomTypeEnum.forward_all
             }`,
             }`,
-            tid: getRandomString(10),
           });
           });
           if (answerRes.data.code !== 0) {
           if (answerRes.data.code !== 0) {
             console.error('/rtc/v1/publish/拿不到sdp');
             console.error('/rtc/v1/publish/拿不到sdp');

+ 0 - 4
src/hooks/webrtc/forwardBilibili.ts

@@ -1,4 +1,3 @@
-import { getRandomString } from 'billd-utils';
 import { ref } from 'vue';
 import { ref } from 'vue';
 
 
 import { fetchRtcV1Publish } from '@/api/srs';
 import { fetchRtcV1Publish } from '@/api/srs';
@@ -91,8 +90,6 @@ export const useForwardBilibili = () => {
             return;
             return;
           }
           }
           const answerRes = await fetchRtcV1Publish({
           const answerRes = await fetchRtcV1Publish({
-            api: `/rtc/v1/publish/`,
-            clientip: null,
             sdp: offerSdp.sdp!,
             sdp: offerSdp.sdp!,
             streamurl: `${myLiveRoom.rtmp_url!}?${
             streamurl: `${myLiveRoom.rtmp_url!}?${
               SRS_CB_URL_PARAMS.publishKey
               SRS_CB_URL_PARAMS.publishKey
@@ -101,7 +98,6 @@ export const useForwardBilibili = () => {
                 ? LiveRoomTypeEnum.pk
                 ? LiveRoomTypeEnum.pk
                 : LiveRoomTypeEnum.forward_bilibili
                 : LiveRoomTypeEnum.forward_bilibili
             }`,
             }`,
-            tid: getRandomString(10),
           });
           });
           if (answerRes.data.code !== 0) {
           if (answerRes.data.code !== 0) {
             console.error('/rtc/v1/publish/拿不到sdp');
             console.error('/rtc/v1/publish/拿不到sdp');

+ 0 - 4
src/hooks/webrtc/forwardHuya.ts

@@ -1,4 +1,3 @@
-import { getRandomString } from 'billd-utils';
 import { ref } from 'vue';
 import { ref } from 'vue';
 
 
 import { fetchRtcV1Publish } from '@/api/srs';
 import { fetchRtcV1Publish } from '@/api/srs';
@@ -87,15 +86,12 @@ export const useForwardHuya = () => {
             return;
             return;
           }
           }
           const answerRes = await fetchRtcV1Publish({
           const answerRes = await fetchRtcV1Publish({
-            api: `/rtc/v1/publish/`,
-            clientip: null,
             sdp: offerSdp.sdp!,
             sdp: offerSdp.sdp!,
             streamurl: `${myLiveRoom.rtmp_url!}?${
             streamurl: `${myLiveRoom.rtmp_url!}?${
               SRS_CB_URL_PARAMS.publishKey
               SRS_CB_URL_PARAMS.publishKey
             }=${myLiveRoom.key!}&${SRS_CB_URL_PARAMS.publishType}=${
             }=${myLiveRoom.key!}&${SRS_CB_URL_PARAMS.publishType}=${
               isPk.value ? LiveRoomTypeEnum.pk : LiveRoomTypeEnum.forward_huya
               isPk.value ? LiveRoomTypeEnum.pk : LiveRoomTypeEnum.forward_huya
             }`,
             }`,
-            tid: getRandomString(10),
           });
           });
           if (answerRes.data.code !== 0) {
           if (answerRes.data.code !== 0) {
             console.error('/rtc/v1/publish/拿不到sdp');
             console.error('/rtc/v1/publish/拿不到sdp');

+ 129 - 0
src/hooks/webrtc/rtmpToRtc.ts

@@ -0,0 +1,129 @@
+import { ref } from 'vue';
+
+import { fetchRtcV1Whep } from '@/api/srs';
+import { useRTCParams } from '@/hooks/use-rtcParams';
+import { useNetworkStore } from '@/store/network';
+import { useUserStore } from '@/store/user';
+import { WebRTCClass } from '@/utils/network/webRTC';
+
+export const useWebRtcRtmpToRtc = () => {
+  const userStore = useUserStore();
+  const networkStore = useNetworkStore();
+
+  const { maxBitrate, maxFramerate, resolutionRatio } = useRTCParams();
+  const currentMaxBitrate = ref(maxBitrate.value[3].value);
+  const currentMaxFramerate = ref(maxFramerate.value[2].value);
+  const currentResolutionRatio = ref(resolutionRatio.value[3].value);
+  const isPk = ref(false);
+  const roomId = ref('');
+
+  function updateWebRtcRtmpToRtcConfig(data: { isPk; roomId }) {
+    isPk.value = data.isPk;
+    roomId.value = data.roomId;
+  }
+
+  const webRtcRtmpToRtc = {
+    newWebRtc: (data: {
+      sender: string;
+      receiver: string;
+      videoEl: HTMLVideoElement;
+      sucessCb: (stream) => void;
+    }) => {
+      console.log({
+        maxBitrate: currentMaxBitrate.value,
+        maxFramerate: currentMaxFramerate.value,
+        resolutionRatio: currentResolutionRatio.value,
+      });
+      return new WebRTCClass({
+        maxBitrate: currentMaxBitrate.value,
+        maxFramerate: currentMaxFramerate.value,
+        resolutionRatio: currentResolutionRatio.value,
+        isSRS: true,
+        roomId: roomId.value,
+        videoEl: data.videoEl,
+        sender: data.sender,
+        receiver: data.receiver,
+        sucessCb: data.sucessCb,
+      });
+    },
+    /**
+     * 主播发offer给观众
+     */
+    sendOffer: async ({
+      sender,
+      receiver,
+    }: {
+      sender: string;
+      receiver: string;
+    }) => {
+      console.log('开始webRtcRtmpToRtc的sendOffer', {
+        sender,
+        receiver,
+      });
+      try {
+        const ws = networkStore.wsMap.get(roomId.value);
+        if (!ws) return;
+        const rtc = networkStore.rtcMap.get(receiver);
+        if (rtc) {
+          rtc.peerConnection?.addTransceiver('audio', {
+            direction: 'recvonly',
+          });
+          rtc.peerConnection?.addTransceiver('video', {
+            direction: 'recvonly',
+          });
+          const offerSdp = await rtc.createOffer();
+          if (!offerSdp) {
+            console.error('webRtcRtmpToRtc的offerSdp为空');
+            window.$message.error('webRtcRtmpToRtc的offerSdp为空');
+            return;
+          }
+          await rtc.setLocalDescription(offerSdp!);
+          const liveRooms = userStore.userInfo?.live_rooms;
+          const myLiveRoom = liveRooms?.[0];
+          if (!myLiveRoom) {
+            window.$message.error('你没有开通直播间');
+            return;
+          }
+          const answerRes = await fetchRtcV1Whep({
+            sdp: offerSdp.sdp!,
+            stream: `roomId___${roomId.value}`,
+            app: 'livestream',
+          });
+          if (!answerRes.data.answer) {
+            console.error('/rtc/v1/play/拿不到sdp');
+            window.$message.error('/rtc/v1/play/拿不到sdp');
+            return;
+          }
+          await rtc.setRemoteDescription(
+            new RTCSessionDescription({
+              type: 'answer',
+              sdp: answerRes.data.answer,
+            })
+          );
+          // const answerRes = await fetchRtcV1Play({
+          //   sdp: offerSdp.sdp!,
+          //   streamurl: `${myLiveRoom.rtmp_url!}`,
+          // });
+          // if (answerRes.data.code !== 0) {
+          //   console.error('/rtc/v1/play/拿不到sdp');
+          //   window.$message.error('/rtc/v1/play/拿不到sdp');
+          //   return;
+          // }
+          // await rtc.setRemoteDescription(
+          //   new RTCSessionDescription({
+          //     type: 'answer',
+          //     sdp: answerRes.data.sdp,
+          //   })
+          // );
+        } else {
+          console.error('rtc不存在');
+        }
+      } catch (error) {
+        console.error('webRtcRtmpToRtc的sendOffer错误');
+        console.log(error);
+      }
+    },
+  };
+
+  return { updateWebRtcRtmpToRtcConfig, webRtcRtmpToRtc };
+};

+ 0 - 4
src/hooks/webrtc/srs.ts

@@ -1,4 +1,3 @@
-import { getRandomString } from 'billd-utils';
 import { ref } from 'vue';
 import { ref } from 'vue';
 
 
 import { fetchRtcV1Publish } from '@/api/srs';
 import { fetchRtcV1Publish } from '@/api/srs';
@@ -92,15 +91,12 @@ export const useWebRtcSrs = () => {
             return;
             return;
           }
           }
           const answerRes = await fetchRtcV1Publish({
           const answerRes = await fetchRtcV1Publish({
-            api: `/rtc/v1/publish/`,
-            clientip: null,
             sdp: offerSdp.sdp!,
             sdp: offerSdp.sdp!,
             streamurl: `${myLiveRoom.rtmp_url!}?${
             streamurl: `${myLiveRoom.rtmp_url!}?${
               SRS_CB_URL_PARAMS.publishKey
               SRS_CB_URL_PARAMS.publishKey
             }=${myLiveRoom.key!}&${SRS_CB_URL_PARAMS.publishType}=${
             }=${myLiveRoom.key!}&${SRS_CB_URL_PARAMS.publishType}=${
               isPk.value ? LiveRoomTypeEnum.pk : LiveRoomTypeEnum.srs
               isPk.value ? LiveRoomTypeEnum.pk : LiveRoomTypeEnum.srs
             }`,
             }`,
-            tid: getRandomString(10),
           });
           });
           if (answerRes.data.code !== 0) {
           if (answerRes.data.code !== 0) {
             console.error('/rtc/v1/publish/拿不到sdp');
             console.error('/rtc/v1/publish/拿不到sdp');

+ 13 - 0
src/interface.ts

@@ -360,6 +360,7 @@ export interface ISigninRecord {
 }
 }
 
 
 export enum LiveLineEnum {
 export enum LiveLineEnum {
+  'rtmp-rtc' = 'rtmp-rtc',
   rtc = 'rtc',
   rtc = 'rtc',
   hls = 'hls',
   hls = 'hls',
   flv = 'flv',
   flv = 'flv',
@@ -748,3 +749,15 @@ export interface ILiveUser {
     userInfo?: IUser;
     userInfo?: IUser;
   };
   };
 }
 }
+
+export interface ICredential {
+  expiredTime: number;
+  expiration: string;
+  credentials: {
+    sessionToken: string;
+    tmpSecretId: string;
+    tmpSecretKey: string;
+  };
+  requestId: string;
+  startTime: number;
+}

+ 2 - 2
src/spec-config.ts

@@ -11,12 +11,12 @@ export const GITHUB_OAUTH_URL = 'https://github.com/login/oauth/authorize?';
 export const WECHAT_GZH_APPID = `wxbd243c01ac5ad1b7`; // 公众号
 export const WECHAT_GZH_APPID = `wxbd243c01ac5ad1b7`; // 公众号
 export const WECHAT_GZH_OAUTH_URL = `https://open.weixin.qq.com/connect/oauth2/authorize?`;
 export const WECHAT_GZH_OAUTH_URL = `https://open.weixin.qq.com/connect/oauth2/authorize?`;
 
 
-export const TENCENTCLOUD_APPID = 1324073273; // 腾讯云APPID
+export const TENCENTCLOUD_APPID = 1305322458; // 腾讯云APPID
 export const TENCENTCLOUD_COS = {
 export const TENCENTCLOUD_COS = {
   [`res-${TENCENTCLOUD_APPID}`]: {
   [`res-${TENCENTCLOUD_APPID}`]: {
     url: `https://res.${prodDomain}`,
     url: `https://res.${prodDomain}`,
     Bucket: `res-${TENCENTCLOUD_APPID}`,
     Bucket: `res-${TENCENTCLOUD_APPID}`,
-    Region: 'ap-mumbai',
+    Region: 'ap-guangzhou',
     StorageClass: 'STANDARD',
     StorageClass: 'STANDARD',
     prefix: {
     prefix: {
       'common/': 'common/',
       'common/': 'common/',

+ 2 - 3
src/store/app/index.ts

@@ -11,6 +11,7 @@ import { mobileRouterName } from '@/router';
 import { ILiveRoom } from '@/types/ILiveRoom';
 import { ILiveRoom } from '@/types/ILiveRoom';
 
 
 export type AppRootState = {
 export type AppRootState = {
+  useGoogleAd: boolean;
   areaList: IArea[];
   areaList: IArea[];
   remoteDesk: {
   remoteDesk: {
     sender: string;
     sender: string;
@@ -78,6 +79,7 @@ export type AppRootState = {
 export const useAppStore = defineStore('app', {
 export const useAppStore = defineStore('app', {
   state: (): AppRootState => {
   state: (): AppRootState => {
     return {
     return {
+      useGoogleAd: false,
       areaList: [],
       areaList: [],
       remoteDesk: {
       remoteDesk: {
         startRemoteDesk: false,
         startRemoteDesk: false,
@@ -111,9 +113,6 @@ export const useAppStore = defineStore('app', {
     setLiveRoomInfo(res: AppRootState['liveRoomInfo']) {
     setLiveRoomInfo(res: AppRootState['liveRoomInfo']) {
       this.liveRoomInfo = res;
       this.liveRoomInfo = res;
     },
     },
-    setLiveLine(res: AppRootState['liveLine']) {
-      this.liveLine = res;
-    },
     setAllTrack(res: AppRootState['allTrack']) {
     setAllTrack(res: AppRootState['allTrack']) {
       this.allTrack = res;
       this.allTrack = res;
     },
     },

+ 3 - 0
src/utils/index.ts

@@ -596,6 +596,9 @@ export function videoToCanvas(data: {
     throw new Error('videoEl不能为空!');
     throw new Error('videoEl不能为空!');
   }
   }
   const canvas = document.createElement('canvas');
   const canvas = document.createElement('canvas');
+  canvas.oncontextmenu = (e) => {
+    e.preventDefault();
+  };
   const ctx = canvas.getContext('2d')!;
   const ctx = canvas.getContext('2d')!;
 
 
   let timer;
   let timer;

+ 11 - 10
src/utils/network/webRTC.ts

@@ -1,6 +1,5 @@
 import { getRandomString } from 'billd-utils';
 import { getRandomString } from 'billd-utils';
 
 
-import { LiveLineEnum } from '@/interface';
 import { prodDomain } from '@/spec-config';
 import { prodDomain } from '@/spec-config';
 import { useAppStore } from '@/store/app';
 import { useAppStore } from '@/store/app';
 import { useNetworkStore } from '@/store/network';
 import { useNetworkStore } from '@/store/network';
@@ -39,6 +38,7 @@ export class WebRTCClass {
   roomId = '-1';
   roomId = '-1';
   sender = '';
   sender = '';
   receiver = '';
   receiver = '';
+  sucessCb;
 
 
   videoEl: HTMLVideoElement;
   videoEl: HTMLVideoElement;
 
 
@@ -84,12 +84,14 @@ export class WebRTCClass {
     isSRS: boolean;
     isSRS: boolean;
     sender: string;
     sender: string;
     receiver: string;
     receiver: string;
+    sucessCb?: any;
   }) {
   }) {
     this.roomId = data.roomId;
     this.roomId = data.roomId;
     this.videoEl = data.videoEl;
     this.videoEl = data.videoEl;
     // document.body.appendChild(this.videoEl);
     // document.body.appendChild(this.videoEl);
     this.sender = data.sender;
     this.sender = data.sender;
     this.receiver = data.receiver;
     this.receiver = data.receiver;
+    this.sucessCb = data.sucessCb;
     if (data.maxBitrate) {
     if (data.maxBitrate) {
       this.maxBitrate = data.maxBitrate;
       this.maxBitrate = data.maxBitrate;
     }
     }
@@ -298,14 +300,13 @@ export class WebRTCClass {
       stream.onremovetrack = () => {
       stream.onremovetrack = () => {
         this.prettierLog({ msg: 'onremovetrack事件', type: 'warn' });
         this.prettierLog({ msg: 'onremovetrack事件', type: 'warn' });
       };
       };
-      this.localStream = stream;
+      this.localStream = event.streams[0];
       this.videoEl.srcObject = event.streams[0];
       this.videoEl.srcObject = event.streams[0];
     });
     });
   };
   };
 
 
   handleConnectionEvent = () => {
   handleConnectionEvent = () => {
     if (!this.peerConnection) return;
     if (!this.peerConnection) return;
-    const appStore = useAppStore();
 
 
     this.prettierLog({ msg: '开始监听pc的icecandidate事件', type: 'warn' });
     this.prettierLog({ msg: '开始监听pc的icecandidate事件', type: 'warn' });
     this.peerConnection.addEventListener('icecandidate', (event) => {
     this.peerConnection.addEventListener('icecandidate', (event) => {
@@ -348,12 +349,6 @@ export class WebRTCClass {
             msg: 'iceConnectionState:connected',
             msg: 'iceConnectionState:connected',
             type: 'warn',
             type: 'warn',
           });
           });
-          this.prettierLog({
-            msg: 'webrtc连接成功!',
-            type: 'success',
-          });
-          appStore.remoteDesk.isRemoteing = true;
-          console.log('sender', this.sender, 'receiver', this.receiver);
           this.update();
           this.update();
         }
         }
         if (iceConnectionState === 'completed') {
         if (iceConnectionState === 'completed') {
@@ -407,7 +402,13 @@ export class WebRTCClass {
             msg: 'connectionState:connected',
             msg: 'connectionState:connected',
             type: 'warn',
             type: 'warn',
           });
           });
-          appStore.setLiveLine(LiveLineEnum.rtc);
+          this.prettierLog({
+            msg: 'webrtc连接成功!',
+            type: 'success',
+          });
+          console.log('sender', this.sender, 'receiver', this.receiver);
+          this.sucessCb?.(this.localStream);
+          // appStore.liveLine = LiveLineEnum.rtc;
           if (this.maxBitrate !== -1) {
           if (this.maxBitrate !== -1) {
             this.setMaxBitrate(this.maxBitrate);
             this.setMaxBitrate(this.maxBitrate);
           }
           }

+ 5 - 2
src/views/home/index.vue

@@ -267,7 +267,10 @@
     <div ref="loadMoreRef"></div>
     <div ref="loadMoreRef"></div>
     <div class="foot">*{{ t('home.copyrightTip') }}~</div>
     <div class="foot">*{{ t('home.copyrightTip') }}~</div>
   </div>
   </div>
-  <div class="ad-wrap-a">
+  <div
+    class="ad-wrap-a"
+    v-if="appStore.useGoogleAd"
+  >
     <!-- live-首页广告位1 -->
     <!-- live-首页广告位1 -->
     <ins
     <ins
       class="adsbygoogle"
       class="adsbygoogle"
@@ -457,7 +460,7 @@ function changeLiveRoom(item: ILive) {
       LiveRoomTypeEnum.wertc_meeting_two,
       LiveRoomTypeEnum.wertc_meeting_two,
     ].includes(item.live_room!.type!)
     ].includes(item.live_room!.type!)
   ) {
   ) {
-    appStore.setLiveLine(LiveLineEnum.hls);
+    appStore.liveLine = LiveLineEnum.hls;
   }
   }
   playLive(item.live_room!);
   playLive(item.live_room!);
 }
 }

+ 8 - 2
src/views/pull/index.vue

@@ -207,7 +207,10 @@
           <div class="price">立即充值</div>
           <div class="price">立即充值</div>
         </div>
         </div>
       </div>
       </div>
-      <div class="ad-wrap-b">
+      <div
+        class="ad-wrap-b"
+        v-if="appStore.useGoogleAd"
+      >
         <!-- live-拉流页面广告位2 -->
         <!-- live-拉流页面广告位2 -->
         <ins
         <ins
           class="adsbygoogle"
           class="adsbygoogle"
@@ -415,7 +418,10 @@
       </div>
       </div>
     </div>
     </div>
 
 
-    <div class="ad-wrap-a">
+    <div
+      class="ad-wrap-a"
+      v-if="appStore.useGoogleAd"
+    >
       <!-- live-拉流页面广告位1 -->
       <!-- live-拉流页面广告位1 -->
       <ins
       <ins
         class="adsbygoogle"
         class="adsbygoogle"