shuisheng 2 лет назад
Родитель
Сommit
762e84725a

+ 1 - 1
src/App.vue

@@ -29,7 +29,7 @@ onMounted(() => {
 </script>
 </script>
 
 
 <style lang="scss">
 <style lang="scss">
-#app {
+body {
   font-size: 16px;
   font-size: 16px;
   // naive的message会影响全局line-height
   // naive的message会影响全局line-height
   line-height: initial;
   line-height: initial;

+ 17 - 16
src/hooks/use-pull.ts

@@ -269,22 +269,14 @@ export function usePull({
     });
     });
   }
   }
 
 
-  function addTransceiver(socketId: string) {
-    if (!localStream.value) return;
-    if (socketId !== getSocketId()) {
-      localStream.value.getTracks().forEach((track) => {
-        const rtc = networkStore.getRtcMap(`${roomId.value}___${socketId}`);
-        rtc?.addTransceiver(track, localStream.value);
-      });
-    }
-  }
-
   function addTrack() {
   function addTrack() {
     if (!localStream.value) return;
     if (!localStream.value) return;
     liveUserList.value.forEach((item) => {
     liveUserList.value.forEach((item) => {
+      console.log(item, item.id, 1111113223431);
       if (item.id !== getSocketId()) {
       if (item.id !== getSocketId()) {
         localStream.value.getTracks().forEach((track) => {
         localStream.value.getTracks().forEach((track) => {
           const rtc = networkStore.getRtcMap(`${roomId.value}___${item.id}`);
           const rtc = networkStore.getRtcMap(`${roomId.value}___${item.id}`);
+          console.log('pull-addTrack', `${roomId.value}___${item.id}`);
           rtc?.addTrack(track, localStream.value);
           rtc?.addTrack(track, localStream.value);
         });
         });
       }
       }
@@ -322,7 +314,7 @@ export function usePull({
     await nextTick(async () => {
     await nextTick(async () => {
       if (!offerSended.value.has(socketId) && socketId !== getSocketId()) {
       if (!offerSended.value.has(socketId) && socketId !== getSocketId()) {
         hooksRtcMap.value.add(await startNewWebRtc({ receiver: socketId }));
         hooksRtcMap.value.add(await startNewWebRtc({ receiver: socketId }));
-        await addTransceiver(socketId);
+        await addTrack();
         console.log('执行sendOffer', {
         console.log('执行sendOffer', {
           sender: getSocketId(),
           sender: getSocketId(),
           receiver: socketId,
           receiver: socketId,
@@ -335,7 +327,7 @@ export function usePull({
 
 
   function addVideo() {
   function addVideo() {
     sidebarList.value.push({ socketId: getSocketId() });
     sidebarList.value.push({ socketId: getSocketId() });
-    console.log(sidebarList.value, 111);
+    console.log(sidebarList.value, 111, getSocketId());
     nextTick(() => {
     nextTick(() => {
       console.log(liveUserList.value, 222);
       console.log(liveUserList.value, 222);
       liveUserList.value.forEach(async (item) => {
       liveUserList.value.forEach(async (item) => {
@@ -351,7 +343,7 @@ export function usePull({
               videoEl: localVideoRef.value[socketId],
               videoEl: localVideoRef.value[socketId],
             })
             })
           );
           );
-          await addTransceiver(socketId);
+          await addTrack();
           console.log('执行sendOffer', {
           console.log('执行sendOffer', {
             sender: getSocketId(),
             sender: getSocketId(),
             receiver: socketId,
             receiver: socketId,
@@ -397,9 +389,7 @@ export function usePull({
           streamurl: streamurl.value,
           streamurl: streamurl.value,
           tid: getRandomString(10),
           tid: getRandomString(10),
         });
         });
-        await rtc.setRemoteDescription(
-          new RTCSessionDescription({ type: 'answer', sdp: res.data.sdp })
-        );
+        await rtc.setRemoteDescription(res.data.sdp);
       } catch (error) {
       } catch (error) {
         console.log(error);
         console.log(error);
       }
       }
@@ -543,6 +533,9 @@ export function usePull({
           }
           }
           videoLoading.value = false;
           videoLoading.value = false;
         }
         }
+        instance.send({
+          msgType: WsMsgTypeEnum.getLiveUser,
+        });
       }
       }
     );
     );
 
 
@@ -683,9 +676,14 @@ export function usePull({
       const danmu: IDanmu = {
       const danmu: IDanmu = {
         msgType: DanmuMsgTypeEnum.otherJoin,
         msgType: DanmuMsgTypeEnum.otherJoin,
         socket_id: data.data.join_socket_id,
         socket_id: data.data.join_socket_id,
+        userInfo: data.data.liveRoom.user,
         msg: '',
         msg: '',
       };
       };
       damuList.value.push(danmu);
       damuList.value.push(danmu);
+      liveUserList.value.push({
+        id: data.data.join_socket_id,
+        userInfo: data.data.liveRoom.user,
+      });
       batchSendOffer(data.data.join_socket_id);
       batchSendOffer(data.data.join_socket_id);
     });
     });
 
 
@@ -702,6 +700,9 @@ export function usePull({
     // 用户离开房间完成
     // 用户离开房间完成
     instance.socketIo.on(WsMsgTypeEnum.leaved, (data) => {
     instance.socketIo.on(WsMsgTypeEnum.leaved, (data) => {
       prettierReceiveWebsocket(WsMsgTypeEnum.leaved, data);
       prettierReceiveWebsocket(WsMsgTypeEnum.leaved, data);
+      networkStore.rtcMap
+        .get(`${roomId.value}___${data.socketId as string}`)
+        ?.close();
       if (!instance) return;
       if (!instance) return;
       const res = liveUserList.value.filter(
       const res = liveUserList.value.filter(
         (item) => item.id !== data.socketId
         (item) => item.id !== data.socketId

+ 120 - 13
src/hooks/use-push.ts

@@ -66,6 +66,70 @@ export function usePush({
   const disabled = ref(false);
   const disabled = ref(false);
   const localStream = ref();
   const localStream = ref();
   const offerSended = ref(new Set());
   const offerSended = ref(new Set());
+  const webRTC = ref<WebRTCClass | SRSWebRTCClass>();
+  const maxBitrate = ref([
+    {
+      label: '1000',
+      value: 1000,
+    },
+    {
+      label: '2000',
+      value: 2000,
+    },
+    {
+      label: '3000',
+      value: 3000,
+    },
+    {
+      label: '4000',
+      value: 4000,
+    },
+    {
+      label: '5000',
+      value: 5000,
+    },
+    {
+      label: '6000',
+      value: 6000,
+    },
+    {
+      label: '7000',
+      value: 7000,
+    },
+    {
+      label: '8000',
+      value: 8000,
+    },
+    {
+      label: '9000',
+      value: 9000,
+    },
+    {
+      label: '10000',
+      value: 10000,
+    },
+  ]);
+  const currentMaxBitrate = ref(maxBitrate.value[0].value);
+
+  const resolutionRatio = ref([
+    {
+      label: '1440P',
+      value: 1440,
+    },
+    {
+      label: '1080P',
+      value: 1080,
+    },
+    {
+      label: '720P',
+      value: 720,
+    },
+    {
+      label: '360P',
+      value: 360,
+    },
+  ]);
+  const currentResolutionRatio = ref(resolutionRatio.value[1].value);
 
 
   const track = reactive({
   const track = reactive({
     audio: 1,
     audio: 1,
@@ -97,6 +161,30 @@ export function usePush({
     txt: string;
     txt: string;
   }>();
   }>();
 
 
+  watch(
+    () => currentMaxBitrate.value,
+    async (newVal) => {
+      const res = await webRTC.value?.setMaxBitrate(newVal);
+      if (res === 1) {
+        window.$message.success('切换码率成功!');
+      } else {
+        window.$message.success('切换码率失败!');
+      }
+    }
+  );
+
+  watch(
+    () => currentResolutionRatio.value,
+    async (newVal) => {
+      const res = await webRTC.value?.setResolutionRatio(newVal);
+      if (res === 1) {
+        window.$message.success('切换分辨率成功!');
+      } else {
+        window.$message.success('切换分辨率失败!');
+      }
+    }
+  );
+
   watch(
   watch(
     () => userStore.userInfo,
     () => userStore.userInfo,
     async (newVal) => {
     async (newVal) => {
@@ -113,7 +201,8 @@ export function usePush({
           streamurl.value = rtmpUrl;
           streamurl.value = rtmpUrl;
         }
         }
       }
       }
-    }
+    },
+    { immediate: true }
   );
   );
 
 
   onMounted(() => {
   onMounted(() => {
@@ -199,12 +288,14 @@ export function usePush({
       const rtc = new SRSWebRTCClass({
       const rtc = new SRSWebRTCClass({
         roomId: `${roomId.value}___${getSocketId()}`,
         roomId: `${roomId.value}___${getSocketId()}`,
         videoEl,
         videoEl,
+        maxBitrate: currentMaxBitrate.value,
+        resolutionRatio: currentResolutionRatio.value,
       });
       });
+      webRTC.value = rtc;
       localStream.value.getTracks().forEach((track) => {
       localStream.value.getTracks().forEach((track) => {
-        rtc.addTransceiver({
+        rtc.addTrack({
           track,
           track,
           stream: localStream.value,
           stream: localStream.value,
-          direction: 'sendonly',
         });
         });
       });
       });
       try {
       try {
@@ -221,9 +312,7 @@ export function usePush({
           ),
           ),
           tid: getRandomString(10),
           tid: getRandomString(10),
         });
         });
-        await rtc.setRemoteDescription(
-          new RTCSessionDescription({ type: 'answer', sdp: res.data.sdp })
-        );
+        await rtc.setRemoteDescription(res.data.sdp);
       } catch (error) {
       } catch (error) {
         console.log(error);
         console.log(error);
       }
       }
@@ -233,6 +322,7 @@ export function usePush({
         roomId: `${roomId.value}___${receiver!}`,
         roomId: `${roomId.value}___${receiver!}`,
         videoEl,
         videoEl,
       });
       });
+      webRTC.value = rtc;
       return rtc;
       return rtc;
     }
     }
   }
   }
@@ -274,7 +364,8 @@ export function usePush({
       if (item.id !== getSocketId()) {
       if (item.id !== getSocketId()) {
         localStream.value.getTracks().forEach((track) => {
         localStream.value.getTracks().forEach((track) => {
           const rtc = networkStore.getRtcMap(`${roomId.value}___${item.id}`);
           const rtc = networkStore.getRtcMap(`${roomId.value}___${item.id}`);
-          rtc?.addTransceiver(track, localStream.value);
+          // rtc?.addTransceiver(track, localStream.value);
+          rtc?.addTrack(track, localStream.value);
         });
         });
       }
       }
     });
     });
@@ -505,14 +596,15 @@ export function usePush({
       prettierReceiveWebsocket(WsMsgTypeEnum.otherJoin, data);
       prettierReceiveWebsocket(WsMsgTypeEnum.otherJoin, data);
       liveUserList.value.push({
       liveUserList.value.push({
         id: data.data.join_socket_id,
         id: data.data.join_socket_id,
-        userInfo: data.data.user,
+        userInfo: data.data.liveRoom.user,
       });
       });
-      damuList.value.push({
+      const danmu: IDanmu = {
         msgType: DanmuMsgTypeEnum.otherJoin,
         msgType: DanmuMsgTypeEnum.otherJoin,
         socket_id: data.data.join_socket_id,
         socket_id: data.data.join_socket_id,
-        userInfo: data.data.user,
+        userInfo: data.data.liveRoom.user,
         msg: '',
         msg: '',
-      });
+      };
+      damuList.value.push(danmu);
       if (isSRS) return;
       if (isSRS) return;
       if (joined.value) {
       if (joined.value) {
         batchSendOffer();
         batchSendOffer();
@@ -532,6 +624,9 @@ export function usePush({
     // 用户离开房间完成
     // 用户离开房间完成
     instance.socketIo.on(WsMsgTypeEnum.leaved, (data) => {
     instance.socketIo.on(WsMsgTypeEnum.leaved, (data) => {
       prettierReceiveWebsocket(WsMsgTypeEnum.leaved, data);
       prettierReceiveWebsocket(WsMsgTypeEnum.leaved, data);
+      networkStore.rtcMap
+        .get(`${roomId.value}___${data.socketId as string}`)
+        ?.close();
       const res = liveUserList.value.filter(
       const res = liveUserList.value.filter(
         (item) => item.id !== data.socketId
         (item) => item.id !== data.socketId
       );
       );
@@ -561,7 +656,11 @@ export function usePush({
     if (!localStream.value) {
     if (!localStream.value) {
       // WARN navigator.mediaDevices在localhost和https才能用,http://192.168.1.103:8000局域网用不了
       // WARN navigator.mediaDevices在localhost和https才能用,http://192.168.1.103:8000局域网用不了
       const event = await navigator.mediaDevices.getUserMedia({
       const event = await navigator.mediaDevices.getUserMedia({
-        video: true,
+        video: {
+          height: currentResolutionRatio.value,
+          // frameRate: 30,
+        },
+        // video: true,
         audio: true,
         audio: true,
       });
       });
       const audio = event.getAudioTracks();
       const audio = event.getAudioTracks();
@@ -582,7 +681,11 @@ export function usePush({
     if (!localStream.value) {
     if (!localStream.value) {
       // WARN navigator.mediaDevices.getDisplayMedia在localhost和https才能用,http://192.168.1.103:8000局域网用不了
       // WARN navigator.mediaDevices.getDisplayMedia在localhost和https才能用,http://192.168.1.103:8000局域网用不了
       const event = await navigator.mediaDevices.getDisplayMedia({
       const event = await navigator.mediaDevices.getDisplayMedia({
-        video: true,
+        video: {
+          height: currentResolutionRatio.value,
+          // frameRate: 30,
+        },
+        // video: true,
         audio: true,
         audio: true,
       });
       });
       const audio = event.getAudioTracks();
       const audio = event.getAudioTracks();
@@ -706,6 +809,10 @@ export function usePush({
     endLive,
     endLive,
     sendDanmu,
     sendDanmu,
     keydownDanmu,
     keydownDanmu,
+    currentResolutionRatio,
+    currentMaxBitrate,
+    resolutionRatio,
+    maxBitrate,
     disabled,
     disabled,
     danmuStr,
     danmuStr,
     roomName,
     roomName,

+ 2 - 2
src/interface.ts

@@ -364,11 +364,11 @@ export interface IMessage {
 }
 }
 
 
 export type IOtherJoin = {
 export type IOtherJoin = {
-  data: IUserLiveRoom & {
+  data: {
+    liveRoom: IUserLiveRoom;
     join_socket_id: string;
     join_socket_id: string;
   };
   };
 };
 };
-
 export interface IJoin {
 export interface IJoin {
   socket_id: string;
   socket_id: string;
   is_anchor: boolean;
   is_anchor: boolean;

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

@@ -363,6 +363,7 @@ function handleStartLive(key) {
   padding: 0 30px;
   padding: 0 30px;
   min-width: $medium-width;
   min-width: $medium-width;
   height: 64px;
   height: 64px;
+  font-size: 14px;
   background-color: #fff;
   background-color: #fff;
   box-shadow: inset 0 -1px #f1f2f3 !important;
   box-shadow: inset 0 -1px #f1f2f3 !important;
   .hr {
   .hr {

+ 118 - 123
src/network/srsWebRtc.ts

@@ -2,30 +2,12 @@ import browserTool from 'browser-tool';
 
 
 import { useNetworkStore } from '@/store/network';
 import { useNetworkStore } from '@/store/network';
 
 
-function prettierInfo(
-  str: string,
-  data: {
-    browser: string;
-  },
-  type?: 'log' | 'warn' | 'error',
-  ...args
-) {
-  console[type || 'log'](
-    `${new Date().toLocaleString()},${data.browser}浏览器,${str}`,
-    ...args
-  );
-}
-
 export class SRSWebRTCClass {
 export class SRSWebRTCClass {
   roomId = '-1';
   roomId = '-1';
-  videoEl;
+  videoEl: HTMLVideoElement;
   peerConnection: RTCPeerConnection | null = null;
   peerConnection: RTCPeerConnection | null = null;
   dataChannel: RTCDataChannel | null = null;
   dataChannel: RTCDataChannel | null = null;
 
 
-  candidateFlag = false;
-
-  sender?: RTCRtpTransceiver;
-
   getStatsSetIntervalDelay = 1000;
   getStatsSetIntervalDelay = 1000;
   getStatsSetIntervalTimer;
   getStatsSetIntervalTimer;
 
 
@@ -35,6 +17,11 @@ export class SRSWebRTCClass {
 
 
   preFramesDecoded = -1; // 上一帧
   preFramesDecoded = -1; // 上一帧
 
 
+  maxBitrate: number; // 当前码率
+  resolutionRatio: number; // 当前分辨率
+
+  localStream?: MediaStream;
+
   browser: {
   browser: {
     device: string;
     device: string;
     language: string;
     language: string;
@@ -60,44 +47,107 @@ export class SRSWebRTCClass {
     loadedmetadata: false, // true代表成功,false代表失败
     loadedmetadata: false, // true代表成功,false代表失败
   };
   };
 
 
-  localDescription: any;
-
   constructor({
   constructor({
     roomId,
     roomId,
     videoEl,
     videoEl,
+    maxBitrate,
+    resolutionRatio,
   }: {
   }: {
     roomId: string;
     roomId: string;
     videoEl: HTMLVideoElement;
     videoEl: HTMLVideoElement;
+    maxBitrate?: number;
+    resolutionRatio?: number;
   }) {
   }) {
     this.roomId = roomId;
     this.roomId = roomId;
     this.videoEl = videoEl;
     this.videoEl = videoEl;
+    this.maxBitrate = maxBitrate || 1000;
+    this.resolutionRatio = resolutionRatio || 1080;
     this.browser = browserTool();
     this.browser = browserTool();
     this.createPeerConnection();
     this.createPeerConnection();
     this.update();
     this.update();
   }
   }
 
 
-  addTransceiver = ({ track, stream, direction }) => {
-    console.warn('addTransceiveraddTransceiver', track, stream);
-    this.sender = this.peerConnection?.addTransceiver(track, {
-      streams: [stream],
-      direction,
+  prettierLog = (msg: string, type?: 'log' | 'warn' | 'error', ...args) => {
+    console[type || 'log'](
+      `${new Date().toLocaleString()},${this.roomId},${
+        this.browser.browser
+      }浏览器,${msg}`,
+      ...args
+    );
+  };
+
+  addTrack = ({
+    track,
+    stream,
+  }: {
+    track: MediaStreamTrack;
+    stream: MediaStream;
+  }) => {
+    console.warn('开始addTrack', track, stream);
+    this.localStream = stream;
+    this.peerConnection?.addTrack(track, stream);
+    this.setMaxBitrate(this.maxBitrate);
+    this.setResolutionRatio(this.resolutionRatio);
+  };
+
+  setResolutionRatio = (height: number) => {
+    console.log('开始设置分辨率', height);
+    return new Promise((resolve) => {
+      this.localStream?.getTracks().forEach((track) => {
+        if (track.kind === 'video') {
+          track
+            .applyConstraints({
+              height,
+            })
+            .then(() => {
+              console.log('设置分辨率成功');
+              resolve(1);
+            })
+            .catch((error) => {
+              console.error('设置分辨率失败', error);
+              resolve(0);
+            });
+        }
+      });
+    });
+  };
+
+  setMaxBitrate = (maxBitrate: number) => {
+    console.log('开始设置码率', maxBitrate);
+    return new Promise<number>((resolve) => {
+      this.peerConnection?.getSenders().forEach((sender) => {
+        if (sender.track?.kind === 'video') {
+          const parameters = { ...sender.getParameters() };
+          if (parameters.encodings[0]) {
+            parameters.encodings[0].maxBitrate = 1000 * maxBitrate;
+            sender
+              .setParameters(parameters)
+              .then(() => {
+                console.log('设置码率成功');
+                resolve(1);
+              })
+              .catch((error) => {
+                console.error('设置码率失败', error);
+                resolve(0);
+              });
+          }
+        }
+      });
     });
     });
-    // this.peerConnection?.addTrack(track, stream);
   };
   };
 
 
-  addStream = (stream) => {
-    console.warn('addStreamaddStream', stream);
+  addStream = (stream: MediaProvider) => {
+    console.warn('开始addStream', stream);
     if (!this.peerConnection) return;
     if (!this.peerConnection) return;
     this.rtcStatus.addStream = true;
     this.rtcStatus.addStream = true;
     this.update();
     this.update();
     this.videoEl.srcObject = stream;
     this.videoEl.srcObject = stream;
-    prettierInfo('addStream成功', { browser: this.browser.browser }, 'warn');
   };
   };
 
 
   initStreamEvent = () => {
   initStreamEvent = () => {
     console.warn(`${this.roomId},开始监听pc的addstream`);
     console.warn(`${this.roomId},开始监听pc的addstream`);
     this.peerConnection?.addEventListener('addstream', (event: any) => {
     this.peerConnection?.addEventListener('addstream', (event: any) => {
-      console.warn(`${this.roomId},pc收到addstream事件`, event, event.stream);
+      console.warn(`${this.roomId},pc收到addstream事件`, event);
       this.addStream(event.stream);
       this.addStream(event.stream);
     });
     });
 
 
@@ -116,111 +166,67 @@ export class SRSWebRTCClass {
     this.peerConnection?.addEventListener('track', (event: any) => {
     this.peerConnection?.addEventListener('track', (event: any) => {
       console.warn(`${this.roomId},pc收到track事件`, event);
       console.warn(`${this.roomId},pc收到track事件`, event);
       this.addStream(event.streams[0]);
       this.addStream(event.streams[0]);
-      // document.querySelector<HTMLVideoElement>('#localVideo')!.srcObject =
-      //   event.streams[0];
     });
     });
   };
   };
 
 
   // 创建offer
   // 创建offer
   createOffer = async () => {
   createOffer = async () => {
     if (!this.peerConnection) return;
     if (!this.peerConnection) return;
-    prettierInfo('createOffer开始', { browser: this.browser.browser }, 'warn');
+    this.prettierLog('开始createOffer', 'warn');
     try {
     try {
-      const description = await this.peerConnection.createOffer();
-      this.localDescription = description;
+      const sdp = await this.peerConnection.createOffer();
       this.rtcStatus.createOffer = true;
       this.rtcStatus.createOffer = true;
       this.update();
       this.update();
-      prettierInfo(
-        'createOffer成功',
-        { browser: this.browser.browser },
-        'warn'
-      );
-      console.log('createOffer', description);
-      return description;
+      this.prettierLog('createOffer成功', 'warn');
+      return sdp;
     } catch (error) {
     } catch (error) {
-      prettierInfo(
-        'createOffer失败',
-        { browser: this.browser.browser },
-        'error'
-      );
-      console.log(error);
+      this.prettierLog('createOffer失败', 'error');
+      console.error(error);
     }
     }
   };
   };
 
 
   // 设置本地描述
   // 设置本地描述
-  setLocalDescription = async (description) => {
+  setLocalDescription = async (sdp: RTCLocalSessionDescriptionInit) => {
     if (!this.peerConnection) return;
     if (!this.peerConnection) return;
-    prettierInfo(
-      'setLocalDescription开始',
-      { browser: this.browser.browser },
-      'warn'
-    );
+    this.prettierLog('开始setLocalDescription', 'warn');
     try {
     try {
-      await this.peerConnection.setLocalDescription(description);
+      await this.peerConnection.setLocalDescription(sdp);
       this.rtcStatus.setLocalDescription = true;
       this.rtcStatus.setLocalDescription = true;
       this.update();
       this.update();
-      prettierInfo(
-        'setLocalDescription成功',
-        { browser: this.browser.browser },
-        'warn'
-      );
-      console.log(description);
+      this.prettierLog('setLocalDescription成功', 'warn');
     } catch (error) {
     } catch (error) {
-      prettierInfo(
-        'setLocalDescription失败',
-        { browser: this.browser.browser },
-        'error'
-      );
-      console.log('setLocalDescription', description);
-      console.log(error);
+      this.prettierLog('setLocalDescription失败', 'error');
+      console.error(error, sdp);
     }
     }
   };
   };
 
 
   // 设置远端描述
   // 设置远端描述
-  setRemoteDescription = async (description) => {
+  setRemoteDescription = async (sdp: string) => {
     if (!this.peerConnection) return;
     if (!this.peerConnection) return;
-    prettierInfo(
-      `setRemoteDescription开始`,
-      { browser: this.browser.browser },
-      'warn'
-    );
+    this.prettierLog(`开始setRemoteDescription`, 'warn');
     try {
     try {
       await this.peerConnection.setRemoteDescription(
       await this.peerConnection.setRemoteDescription(
-        new RTCSessionDescription(description)
+        new RTCSessionDescription({ type: 'answer', sdp })
       );
       );
       this.rtcStatus.setRemoteDescription = true;
       this.rtcStatus.setRemoteDescription = true;
       this.update();
       this.update();
-      prettierInfo(
-        'setRemoteDescription成功',
-        { browser: this.browser.browser },
-        'warn'
-      );
-      console.log(description);
+      this.prettierLog('setRemoteDescription成功', 'warn');
     } catch (error) {
     } catch (error) {
-      prettierInfo(
-        'setRemoteDescription失败',
-        { browser: this.browser.browser },
-        'error'
-      );
-      console.log('setRemoteDescription', description);
-      console.log(error);
+      this.prettierLog('setRemoteDescription失败', 'error');
+      console.error(error, sdp);
     }
     }
   };
   };
 
 
   // 创建连接
   // 创建连接
   startConnect = () => {
   startConnect = () => {
     if (!this.peerConnection) return;
     if (!this.peerConnection) return;
-    console.warn(`${this.roomId},开始监听pc的icecandidate`);
+    this.initStreamEvent();
+
+    // icecandidate
     this.peerConnection.addEventListener('icecandidate', (event) => {
     this.peerConnection.addEventListener('icecandidate', (event) => {
-      prettierInfo(
-        'pc收到icecandidate',
-        { browser: this.browser.browser },
-        'warn'
-      );
+      console.log('pc收到icecandidate', event);
     });
     });
 
 
-    this.initStreamEvent();
-
     // iceconnectionstatechange
     // iceconnectionstatechange
     this.peerConnection.addEventListener(
     this.peerConnection.addEventListener(
       'iceconnectionstatechange',
       'iceconnectionstatechange',
@@ -228,6 +234,7 @@ export class SRSWebRTCClass {
         // https://developer.mozilla.org/zh-CN/docs/Web/API/RTCPeerConnection/connectionState
         // https://developer.mozilla.org/zh-CN/docs/Web/API/RTCPeerConnection/connectionState
         const iceConnectionState = event.currentTarget.iceConnectionState;
         const iceConnectionState = event.currentTarget.iceConnectionState;
         console.log(
         console.log(
+          this.roomId,
           'pc收到iceconnectionstatechange',
           'pc收到iceconnectionstatechange',
           // eslint-disable-next-line
           // eslint-disable-next-line
           `iceConnectionState:${iceConnectionState}`,
           `iceConnectionState:${iceConnectionState}`,
@@ -235,23 +242,23 @@ export class SRSWebRTCClass {
         );
         );
         if (iceConnectionState === 'connected') {
         if (iceConnectionState === 'connected') {
           // ICE 代理至少对每个候选发现了一个可用的连接,此时仍然会继续测试远程候选以便发现更优的连接。同时可能在继续收集候选。
           // ICE 代理至少对每个候选发现了一个可用的连接,此时仍然会继续测试远程候选以便发现更优的连接。同时可能在继续收集候选。
-          console.warn('iceConnectionState:connected', event);
+          console.warn(this.roomId, 'iceConnectionState:connected', event);
         }
         }
         if (iceConnectionState === 'completed') {
         if (iceConnectionState === 'completed') {
           // ICE 代理已经发现了可用的连接,不再测试远程候选。
           // ICE 代理已经发现了可用的连接,不再测试远程候选。
-          console.warn('iceConnectionState:completed', event);
+          console.warn(this.roomId, 'iceConnectionState:completed', event);
         }
         }
         if (iceConnectionState === 'failed') {
         if (iceConnectionState === 'failed') {
           // ICE 候选测试了所有远程候选没有发现匹配的候选。也可能有些候选中发现了一些可用连接。
           // ICE 候选测试了所有远程候选没有发现匹配的候选。也可能有些候选中发现了一些可用连接。
-          console.error('iceConnectionState:failed', event);
+          console.error(this.roomId, 'iceConnectionState:failed', event);
         }
         }
         if (iceConnectionState === 'disconnected') {
         if (iceConnectionState === 'disconnected') {
           // 测试不再活跃,这可能是一个暂时的状态,可以自我恢复。
           // 测试不再活跃,这可能是一个暂时的状态,可以自我恢复。
-          console.error('iceConnectionState:disconnected', event);
+          console.error(this.roomId, 'iceConnectionState:disconnected', event);
         }
         }
         if (iceConnectionState === 'closed') {
         if (iceConnectionState === 'closed') {
           // ICE 代理关闭,不再应答任何请求。
           // ICE 代理关闭,不再应答任何请求。
-          console.error('iceConnectionState:closed', event);
+          console.error(this.roomId, 'iceConnectionState:closed', event);
         }
         }
       }
       }
     );
     );
@@ -262,6 +269,7 @@ export class SRSWebRTCClass {
       (event: any) => {
       (event: any) => {
         const connectionState = event.currentTarget.connectionState;
         const connectionState = event.currentTarget.connectionState;
         console.log(
         console.log(
+          this.roomId,
           'pc收到connectionstatechange',
           'pc收到connectionstatechange',
           // eslint-disable-next-line
           // eslint-disable-next-line
           `connectionState:${connectionState}`,
           `connectionState:${connectionState}`,
@@ -269,19 +277,19 @@ export class SRSWebRTCClass {
         );
         );
         if (connectionState === 'connected') {
         if (connectionState === 'connected') {
           // 表示每一个 ICE 连接要么正在使用(connected 或 completed 状态),要么已被关闭(closed 状态);并且,至少有一个连接处于 connected 或 completed 状态。
           // 表示每一个 ICE 连接要么正在使用(connected 或 completed 状态),要么已被关闭(closed 状态);并且,至少有一个连接处于 connected 或 completed 状态。
-          console.warn('connectionState:connected');
+          console.warn(this.roomId, 'connectionState:connected');
         }
         }
         if (connectionState === 'disconnected') {
         if (connectionState === 'disconnected') {
           // 表示至少有一个 ICE 连接处于 disconnected 状态,并且没有连接处于 failed、connecting 或 checking 状态。
           // 表示至少有一个 ICE 连接处于 disconnected 状态,并且没有连接处于 failed、connecting 或 checking 状态。
-          console.error('connectionState:disconnected');
+          console.error(this.roomId, 'connectionState:disconnected');
         }
         }
         if (connectionState === 'closed') {
         if (connectionState === 'closed') {
           // 表示 RTCPeerConnection 已关闭。
           // 表示 RTCPeerConnection 已关闭。
-          console.error('connectionState:closed');
+          console.error(this.roomId, 'connectionState:closed');
         }
         }
         if (connectionState === 'failed') {
         if (connectionState === 'failed') {
           // 表示至少有一个 ICE 连接处于 failed 的状态。
           // 表示至少有一个 ICE 连接处于 failed 的状态。
-          console.error('connectionState:failed');
+          console.error(this.roomId, 'connectionState:failed');
         }
         }
       }
       }
     );
     );
@@ -343,17 +351,12 @@ export class SRSWebRTCClass {
             // );
             // );
             if (this.preFramesDecoded === currFramesDecoded) {
             if (this.preFramesDecoded === currFramesDecoded) {
               if (this.forceINums >= this.forceINumsMax) {
               if (this.forceINums >= this.forceINumsMax) {
-                prettierInfo(
-                  '超过forceI次数,提示更换网络',
-                  { browser: this.browser.browser },
-                  'warn'
-                );
+                this.prettierLog('超过forceI次数,提示更换网络', 'warn');
                 this.forceINums = 0;
                 this.forceINums = 0;
               } else {
               } else {
                 this.forceINums += 1;
                 this.forceINums += 1;
-                prettierInfo(
+                this.prettierLog(
                   `当前视频流卡主了,主动刷新云手机(${this.forceINums}/${this.forceINumsMax})`,
                   `当前视频流卡主了,主动刷新云手机(${this.forceINums}/${this.forceINumsMax})`,
-                  { browser: this.browser.browser },
                   'warn'
                   'warn'
                 );
                 );
               }
               }
@@ -366,11 +369,7 @@ export class SRSWebRTCClass {
           /** 处理黑屏 */
           /** 处理黑屏 */
           const handleBlackScreen = () => {
           const handleBlackScreen = () => {
             if (isBlack) {
             if (isBlack) {
-              prettierInfo(
-                '黑屏了',
-                { browser: this.browser.browser },
-                'error'
-              );
+              this.prettierLog('黑屏了', 'error');
             }
             }
             // else {
             // else {
             //   console.warn('没有黑屏');
             //   console.warn('没有黑屏');
@@ -381,9 +380,8 @@ export class SRSWebRTCClass {
             const res = this.rtcStatusIsOk();
             const res = this.rtcStatusIsOk();
             const length = Object.keys(res).length;
             const length = Object.keys(res).length;
             if (length) {
             if (length) {
-              prettierInfo(
+              this.prettierLog(
                 `rtcStatus错误:${Object.keys(res).join()}`,
                 `rtcStatus错误:${Object.keys(res).join()}`,
-                { browser: this.browser.browser },
                 'error'
                 'error'
               );
               );
             }
             }
@@ -493,9 +491,6 @@ export class SRSWebRTCClass {
   // 手动关闭webrtc连接
   // 手动关闭webrtc连接
   close = () => {
   close = () => {
     console.warn(`${new Date().toLocaleString()},手动关闭webrtc连接`);
     console.warn(`${new Date().toLocaleString()},手动关闭webrtc连接`);
-    if (this.sender?.sender) {
-      this.peerConnection?.removeTrack(this.sender?.sender);
-    }
     this.peerConnection?.close();
     this.peerConnection?.close();
     this.dataChannel?.close();
     this.dataChannel?.close();
     this.peerConnection = null;
     this.peerConnection = null;

+ 149 - 114
src/network/webRtc.ts

@@ -5,20 +5,6 @@ import { useNetworkStore } from '@/store/network';
 
 
 import { WsMsgTypeEnum } from './webSocket';
 import { WsMsgTypeEnum } from './webSocket';
 
 
-function prettierInfo(
-  str: string,
-  data: {
-    browser: string;
-  },
-  type?: 'log' | 'warn' | 'error',
-  ...args
-) {
-  console[type || 'log'](
-    `${new Date().toLocaleString()},${data.browser}浏览器,${str}`,
-    ...args
-  );
-}
-
 export const frontendErrorCode = {
 export const frontendErrorCode = {
   rtcStatusErr: {
   rtcStatusErr: {
     // rtcStatus没有100%
     // rtcStatus没有100%
@@ -78,6 +64,11 @@ export class WebRTCClass {
 
 
   preFramesDecoded = -1; // 上一帧
   preFramesDecoded = -1; // 上一帧
 
 
+  maxBitrate: number; // 当前码率
+  resolutionRatio: number; // 当前分辨率
+
+  localStream?: MediaStream;
+
   browser: {
   browser: {
     device: string;
     device: string;
     language: string;
     language: string;
@@ -108,30 +99,92 @@ export class WebRTCClass {
   constructor({
   constructor({
     roomId,
     roomId,
     videoEl,
     videoEl,
+    maxBitrate,
+    resolutionRatio,
   }: {
   }: {
     roomId: string;
     roomId: string;
     videoEl: HTMLVideoElement;
     videoEl: HTMLVideoElement;
+    maxBitrate?: number;
+    resolutionRatio?: number;
   }) {
   }) {
     this.roomId = roomId;
     this.roomId = roomId;
     this.videoEl = videoEl;
     this.videoEl = videoEl;
+    this.maxBitrate = maxBitrate || 1000;
+    this.resolutionRatio = resolutionRatio || 1080;
     this.browser = browserTool();
     this.browser = browserTool();
     this.createPeerConnection();
     this.createPeerConnection();
     this.update();
     this.update();
     // this.handleWebRtcError();
     // this.handleWebRtcError();
   }
   }
 
 
-  addTransceiver = (track, stream) => {
-    console.warn('addTransceiveraddTransceiver', track, stream);
-    this.sender = this.peerConnection?.addTransceiver(track, {
-      streams: [stream],
-      direction: 'sendonly',
-    });
-    // this.peerConnection?.addTrack(track, stream);
+  prettierLog = (msg: string, type?: 'log' | 'warn' | 'error', ...args) => {
+    console[type || 'log'](
+      `${new Date().toLocaleString()},${this.roomId},${
+        this.browser.browser
+      }浏览器,${msg}`,
+      ...args
+    );
   };
   };
 
 
   addTrack = (track, stream) => {
   addTrack = (track, stream) => {
-    console.warn('addTrackaddTrack', track, stream);
-    this.peerConnection?.addTrack(track, stream);
+    const sender = this.peerConnection
+      ?.getSenders()
+      .find((s) => s.track === track);
+    if (!sender) {
+      console.warn('开始addTrack', this.roomId, track, stream);
+      this.peerConnection?.addTrack(track, stream);
+      this.localStream = stream;
+      this.setMaxBitrate(this.maxBitrate);
+      this.setResolutionRatio(this.resolutionRatio);
+    } else {
+      console.warn('不addTrack了', this.roomId, track, stream);
+    }
+  };
+
+  setResolutionRatio = (height: number) => {
+    console.log('开始设置分辨率', height);
+    return new Promise((resolve) => {
+      this.localStream?.getTracks().forEach((track) => {
+        if (track.kind === 'video') {
+          track
+            .applyConstraints({
+              height,
+            })
+            .then(() => {
+              console.log('设置分辨率成功');
+              resolve(1);
+            })
+            .catch((error) => {
+              console.error('设置分辨率失败', error);
+              resolve(0);
+            });
+        }
+      });
+    });
+  };
+
+  setMaxBitrate = (maxBitrate: number) => {
+    console.log('开始设置码率', maxBitrate);
+    return new Promise<number>((resolve) => {
+      this.peerConnection?.getSenders().forEach((sender) => {
+        if (sender.track?.kind === 'video') {
+          const parameters = { ...sender.getParameters() };
+          if (parameters.encodings[0]) {
+            parameters.encodings[0].maxBitrate = 1000 * maxBitrate;
+            sender
+              .setParameters(parameters)
+              .then(() => {
+                console.log('设置码率成功');
+                resolve(1);
+              })
+              .catch((error) => {
+                console.error('设置码率失败', error);
+                resolve(0);
+              });
+          }
+        }
+      });
+    });
   };
   };
 
 
   handleWebRtcError = () => {
   handleWebRtcError = () => {
@@ -176,17 +229,12 @@ export class WebRTCClass {
             // );
             // );
             if (this.preFramesDecoded === currFramesDecoded) {
             if (this.preFramesDecoded === currFramesDecoded) {
               if (this.forceINums >= this.forceINumsMax) {
               if (this.forceINums >= this.forceINumsMax) {
-                prettierInfo(
-                  '超过forceI次数,提示更换网络',
-                  { browser: this.browser.browser },
-                  'warn'
-                );
+                this.prettierLog('超过forceI次数,提示更换网络', 'warn');
                 this.forceINums = 0;
                 this.forceINums = 0;
               } else {
               } else {
                 this.forceINums += 1;
                 this.forceINums += 1;
-                prettierInfo(
+                this.prettierLog(
                   `当前视频流卡主了,主动刷新云手机(${this.forceINums}/${this.forceINumsMax})`,
                   `当前视频流卡主了,主动刷新云手机(${this.forceINums}/${this.forceINumsMax})`,
-                  { browser: this.browser.browser },
                   'warn'
                   'warn'
                 );
                 );
               }
               }
@@ -199,11 +247,7 @@ export class WebRTCClass {
           /** 处理黑屏 */
           /** 处理黑屏 */
           const handleBlackScreen = () => {
           const handleBlackScreen = () => {
             if (isBlack) {
             if (isBlack) {
-              prettierInfo(
-                '黑屏了',
-                { browser: this.browser.browser },
-                'error'
-              );
+              this.prettierLog('黑屏了', 'error');
             }
             }
             // else {
             // else {
             //   console.warn('没有黑屏');
             //   console.warn('没有黑屏');
@@ -214,9 +258,8 @@ export class WebRTCClass {
             const res = this.rtcStatusIsOk();
             const res = this.rtcStatusIsOk();
             const length = Object.keys(res).length;
             const length = Object.keys(res).length;
             if (length) {
             if (length) {
-              prettierInfo(
+              this.prettierLog(
                 `rtcStatus错误:${Object.keys(res).join()}`,
                 `rtcStatus错误:${Object.keys(res).join()}`,
-                { browser: this.browser.browser },
                 'error'
                 'error'
               );
               );
             }
             }
@@ -326,7 +369,7 @@ export class WebRTCClass {
   // 创建offer
   // 创建offer
   createOffer = async () => {
   createOffer = async () => {
     if (!this.peerConnection) return;
     if (!this.peerConnection) return;
-    prettierInfo('createOffer开始', { browser: this.browser.browser }, 'warn');
+    this.prettierLog('createOffer开始', 'warn');
     try {
     try {
       const description = await this.peerConnection.createOffer({
       const description = await this.peerConnection.createOffer({
         iceRestart: true,
         iceRestart: true,
@@ -339,19 +382,11 @@ export class WebRTCClass {
       this.localDescription = description;
       this.localDescription = description;
       this.rtcStatus.createOffer = true;
       this.rtcStatus.createOffer = true;
       this.update();
       this.update();
-      prettierInfo(
-        'createOffer成功',
-        { browser: this.browser.browser },
-        'warn'
-      );
+      this.prettierLog('createOffer成功', 'warn');
       console.log('createOffer', description);
       console.log('createOffer', description);
       return description;
       return description;
     } catch (error) {
     } catch (error) {
-      prettierInfo(
-        'createOffer失败',
-        { browser: this.browser.browser },
-        'error'
-      );
+      this.prettierLog('createOffer失败', 'error');
       console.log(error);
       console.log(error);
     }
     }
   };
   };
@@ -359,27 +394,15 @@ export class WebRTCClass {
   // 设置本地描述
   // 设置本地描述
   setLocalDescription = async (description) => {
   setLocalDescription = async (description) => {
     if (!this.peerConnection) return;
     if (!this.peerConnection) return;
-    prettierInfo(
-      'setLocalDescription开始',
-      { browser: this.browser.browser },
-      'warn'
-    );
+    this.prettierLog('setLocalDescription开始', 'warn');
     try {
     try {
       await this.peerConnection.setLocalDescription(description);
       await this.peerConnection.setLocalDescription(description);
       this.rtcStatus.setLocalDescription = true;
       this.rtcStatus.setLocalDescription = true;
       this.update();
       this.update();
-      prettierInfo(
-        'setLocalDescription成功',
-        { browser: this.browser.browser },
-        'warn'
-      );
+      this.prettierLog('setLocalDescription成功', 'warn');
       console.log(description);
       console.log(description);
     } catch (error) {
     } catch (error) {
-      prettierInfo(
-        'setLocalDescription失败',
-        { browser: this.browser.browser },
-        'error'
-      );
+      this.prettierLog('setLocalDescription失败', 'error');
       console.log('setLocalDescription', description);
       console.log('setLocalDescription', description);
       console.log(error);
       console.log(error);
     }
     }
@@ -388,23 +411,15 @@ export class WebRTCClass {
   // 创建answer
   // 创建answer
   createAnswer = async () => {
   createAnswer = async () => {
     if (!this.peerConnection) return;
     if (!this.peerConnection) return;
-    prettierInfo('createAnswer开始', { browser: this.browser.browser }, 'warn');
+    this.prettierLog('createAnswer开始', 'warn');
     try {
     try {
       const description = await this.peerConnection.createAnswer();
       const description = await this.peerConnection.createAnswer();
       this.update();
       this.update();
-      prettierInfo(
-        'createAnswer成功',
-        { browser: this.browser.browser },
-        'warn'
-      );
+      this.prettierLog('createAnswer成功', 'warn');
       console.log(description);
       console.log(description);
       return description;
       return description;
     } catch (error) {
     } catch (error) {
-      prettierInfo(
-        'createAnswer失败',
-        { browser: this.browser.browser },
-        'error'
-      );
+      this.prettierLog('createAnswer失败', 'error');
       console.log(error);
       console.log(error);
     }
     }
   };
   };
@@ -412,29 +427,17 @@ export class WebRTCClass {
   // 设置远端描述
   // 设置远端描述
   setRemoteDescription = async (description) => {
   setRemoteDescription = async (description) => {
     if (!this.peerConnection) return;
     if (!this.peerConnection) return;
-    prettierInfo(
-      `setRemoteDescription开始`,
-      { browser: this.browser.browser },
-      'warn'
-    );
+    this.prettierLog(`setRemoteDescription开始`, 'warn');
     try {
     try {
       await this.peerConnection.setRemoteDescription(
       await this.peerConnection.setRemoteDescription(
         new RTCSessionDescription(description)
         new RTCSessionDescription(description)
       );
       );
       this.rtcStatus.setRemoteDescription = true;
       this.rtcStatus.setRemoteDescription = true;
       this.update();
       this.update();
-      prettierInfo(
-        'setRemoteDescription成功',
-        { browser: this.browser.browser },
-        'warn'
-      );
+      this.prettierLog('setRemoteDescription成功', 'warn');
       console.log(description);
       console.log(description);
     } catch (error) {
     } catch (error) {
-      prettierInfo(
-        'setRemoteDescription失败',
-        { browser: this.browser.browser },
-        'error'
-      );
+      this.prettierLog('setRemoteDescription失败', 'error');
       console.log('setRemoteDescription', description);
       console.log('setRemoteDescription', description);
       console.log(error);
       console.log(error);
     }
     }
@@ -442,12 +445,11 @@ export class WebRTCClass {
 
 
   addStream = (stream) => {
   addStream = (stream) => {
     if (!this.peerConnection) return;
     if (!this.peerConnection) return;
-    // if (!this.peerConnection || this.rtcStatus.addStream) return;
     this.rtcStatus.addStream = true;
     this.rtcStatus.addStream = true;
     this.update();
     this.update();
-    console.log('addStreamaddStreamaddStream', this.videoEl, stream);
+    console.log('开始addStream', this.videoEl, stream, this.roomId);
     this.videoEl.srcObject = stream;
     this.videoEl.srcObject = stream;
-    prettierInfo('addStream成功', { browser: this.browser.browser }, 'warn');
+    this.prettierLog('addStream成功', 'warn');
   };
   };
 
 
   handleStream = () => {
   handleStream = () => {
@@ -478,13 +480,10 @@ export class WebRTCClass {
   // 创建连接
   // 创建连接
   startConnect = () => {
   startConnect = () => {
     if (!this.peerConnection) return;
     if (!this.peerConnection) return;
+    this.handleStream();
     console.warn(`${this.roomId},开始监听pc的icecandidate`);
     console.warn(`${this.roomId},开始监听pc的icecandidate`);
     this.peerConnection.addEventListener('icecandidate', (event) => {
     this.peerConnection.addEventListener('icecandidate', (event) => {
-      prettierInfo(
-        'pc收到icecandidate',
-        { browser: this.browser.browser },
-        'warn'
-      );
+      this.prettierLog('pc收到icecandidate', 'warn');
       this.rtcStatus.icecandidate = true;
       this.rtcStatus.icecandidate = true;
       this.update();
       this.update();
       if (event.candidate) {
       if (event.candidate) {
@@ -509,33 +508,71 @@ export class WebRTCClass {
       }
       }
     });
     });
 
 
-    this.handleStream();
+    console.warn(`${this.roomId},开始监听pc的iceconnectionstatechange`);
+    // iceconnectionstatechange
+    this.peerConnection.addEventListener(
+      'iceconnectionstatechange',
+      (event: any) => {
+        // https://developer.mozilla.org/zh-CN/docs/Web/API/RTCPeerConnection/connectionState
+        const iceConnectionState = event.currentTarget.iceConnectionState;
+        console.log(
+          this.roomId,
+          'pc收到iceconnectionstatechange',
+          // eslint-disable-next-line
+          `iceConnectionState:${iceConnectionState}`,
+          event
+        );
+        if (iceConnectionState === 'connected') {
+          // ICE 代理至少对每个候选发现了一个可用的连接,此时仍然会继续测试远程候选以便发现更优的连接。同时可能在继续收集候选。
+          console.warn(this.roomId, 'iceConnectionState:connected', event);
+        }
+        if (iceConnectionState === 'completed') {
+          // ICE 代理已经发现了可用的连接,不再测试远程候选。
+          console.warn(this.roomId, 'iceConnectionState:completed', event);
+        }
+        if (iceConnectionState === 'failed') {
+          // ICE 候选测试了所有远程候选没有发现匹配的候选。也可能有些候选中发现了一些可用连接。
+          console.error(this.roomId, 'iceConnectionState:failed', event);
+        }
+        if (iceConnectionState === 'disconnected') {
+          // 测试不再活跃,这可能是一个暂时的状态,可以自我恢复。
+          console.error(this.roomId, 'iceConnectionState:disconnected', event);
+        }
+        if (iceConnectionState === 'closed') {
+          // ICE 代理关闭,不再应答任何请求。
+          console.error(this.roomId, 'iceConnectionState:closed', event);
+        }
+      }
+    );
 
 
+    console.warn(`${this.roomId},开始监听pc的connectionstatechange`);
     // connectionstatechange
     // connectionstatechange
     this.peerConnection.addEventListener(
     this.peerConnection.addEventListener(
       'connectionstatechange',
       'connectionstatechange',
       (event: any) => {
       (event: any) => {
-        prettierInfo(
-          'pc收到connectionstatechange',
-          { browser: this.browser.browser },
-          'warn'
-        );
         const connectionState = event.currentTarget.connectionState;
         const connectionState = event.currentTarget.connectionState;
-        const iceConnectionState = event.currentTarget.iceConnectionState;
         console.log(
         console.log(
+          this.roomId,
+          'pc收到connectionstatechange',
           // eslint-disable-next-line
           // eslint-disable-next-line
-          `connectionState:${connectionState}, iceConnectionState:${iceConnectionState}`
+          `connectionState:${connectionState}`,
+          event
         );
         );
         if (connectionState === 'connected') {
         if (connectionState === 'connected') {
-          console.warn('connectionState:connected');
+          // 表示每一个 ICE 连接要么正在使用(connected 或 completed 状态),要么已被关闭(closed 状态);并且,至少有一个连接处于 connected 或 completed 状态。
+          console.warn(this.roomId, 'connectionState:connected');
         }
         }
-        if (connectionState === 'failed') {
-          // 失败
-          console.error('connectionState:failed', event);
+        if (connectionState === 'disconnected') {
+          // 表示至少有一个 ICE 连接处于 disconnected 状态,并且没有连接处于 failed、connecting 或 checking 状态。
+          console.error(this.roomId, 'connectionState:disconnected');
         }
         }
-        if (iceConnectionState === 'disconnected') {
-          // 已断开,请重新连接
-          console.error('iceConnectionState:disconnected', event);
+        if (connectionState === 'closed') {
+          // 表示 RTCPeerConnection 已关闭。
+          console.error(this.roomId, 'connectionState:closed');
+        }
+        if (connectionState === 'failed') {
+          // 表示至少有一个 ICE 连接处于 failed 的状态。
+          console.error(this.roomId, 'connectionState:failed');
         }
         }
       }
       }
     );
     );
@@ -573,8 +610,6 @@ export class WebRTCClass {
       // this.dataChannel.onmessage = (event) => {
       // this.dataChannel.onmessage = (event) => {
       //   console.log('dataChannel---onmessage', event);
       //   console.log('dataChannel---onmessage', event);
       // };
       // };
-      // this.peerConnection.addTransceiver('video', { direction: 'recvonly' });
-      // this.peerConnection.addTransceiver('audio', { direction: 'recvonly' });
       this.startConnect();
       this.startConnect();
       this.update();
       this.update();
     }
     }

+ 1 - 0
src/utils/index.ts

@@ -45,6 +45,7 @@ export function videoToCanvas(data: {
     cancelAnimationFrame(timer);
     cancelAnimationFrame(timer);
   }
   }
   targetEl.appendChild(canvas);
   targetEl.appendChild(canvas);
+  // document.body.appendChild(videoEl);
   // targetEl.parentNode?.replaceChild(canvas, targetEl);
   // targetEl.parentNode?.replaceChild(canvas, targetEl);
 
 
   drawCanvas();
   drawCanvas();

+ 2 - 2
src/views/h5/room/index.vue

@@ -114,11 +114,9 @@ import { useHlsPlay } from '@/hooks/use-play';
 import { usePull } from '@/hooks/use-pull';
 import { usePull } from '@/hooks/use-pull';
 import { DanmuMsgTypeEnum, ILiveRoom, liveTypeEnum } from '@/interface';
 import { DanmuMsgTypeEnum, ILiveRoom, liveTypeEnum } from '@/interface';
 import router, { mobileRouterName } from '@/router';
 import router, { mobileRouterName } from '@/router';
-import { useAppStore } from '@/store/app';
 import { videoToCanvas } from '@/utils';
 import { videoToCanvas } from '@/utils';
 
 
 const route = useRoute();
 const route = useRoute();
-const appStore = useAppStore();
 
 
 const bottomRef = ref<HTMLDivElement>();
 const bottomRef = ref<HTMLDivElement>();
 const containerRef = ref<HTMLDivElement>();
 const containerRef = ref<HTMLDivElement>();
@@ -299,6 +297,8 @@ onMounted(() => {
     .list {
     .list {
       overflow-y: scroll;
       overflow-y: scroll;
       height: 100vh;
       height: 100vh;
+
+      @extend %hideScrollbar;
     }
     }
     .item {
     .item {
       margin-bottom: 10px;
       margin-bottom: 10px;

+ 23 - 18
src/views/home/index.vue

@@ -116,7 +116,7 @@
             v-for="(iten, indey) in otherLiveRoomList"
             v-for="(iten, indey) in otherLiveRoomList"
             :key="indey"
             :key="indey"
             class="live-room"
             class="live-room"
-            @click="goRoom(iten.live_room)"
+            @click="goRoom(iten.live_room!)"
           >
           >
             <div
             <div
               class="cover"
               class="cover"
@@ -182,6 +182,7 @@ async function changeLiveRoom(item: ILive) {
     item.live_room?.type === LiveRoomTypeEnum.user_obs ||
     item.live_room?.type === LiveRoomTypeEnum.user_obs ||
     item.live_room?.type === LiveRoomTypeEnum.system
     item.live_room?.type === LiveRoomTypeEnum.system
   ) {
   ) {
+    // @ts-ignore
     if (flvJs.isSupported()) {
     if (flvJs.isSupported()) {
       const { width, height } = await startFlvPlay({
       const { width, height } = await startFlvPlay({
         flvurl: item.live_room.flv_url!,
         flvurl: item.live_room.flv_url!,
@@ -334,20 +335,20 @@ function joinHlsRoom() {
 
 
       .cdn-ico {
       .cdn-ico {
         position: absolute;
         position: absolute;
-        right: -10px;
         top: -9px;
         top: -9px;
-        background-color: #f87c48;
-        color: white;
+        right: -10px;
         z-index: 2;
         z-index: 2;
-        height: 32px;
         width: 70px;
         width: 70px;
-        transform-origin: bottom;
+        height: 32px;
+        background-color: #f87c48;
+        color: white;
         transform: rotate(45deg);
         transform: rotate(45deg);
+        transform-origin: bottom;
         .txt {
         .txt {
           margin-top: 11px;
           margin-top: 11px;
           margin-left: 2px;
           margin-left: 2px;
-          font-size: 14px;
           background-image: initial !important;
           background-image: initial !important;
+          font-size: 14px;
         }
         }
       }
       }
 
 
@@ -365,6 +366,7 @@ function joinHlsRoom() {
         left: 50%;
         left: 50%;
         height: 100%;
         height: 100%;
         transform: translate(-50%);
         transform: translate(-50%);
+
         user-select: none;
         user-select: none;
       }
       }
       :deep(video) {
       :deep(video) {
@@ -374,6 +376,7 @@ function joinHlsRoom() {
         width: 100%;
         width: 100%;
         height: 100%;
         height: 100%;
         transform: translate(-50%);
         transform: translate(-50%);
+
         user-select: none;
         user-select: none;
       }
       }
       .controls {
       .controls {
@@ -390,12 +393,12 @@ function joinHlsRoom() {
         top: 50%;
         top: 50%;
         left: 50%;
         left: 50%;
         z-index: 1;
         z-index: 1;
-        width: 100%;
-        box-sizing: border-box;
-        align-items: center;
-        justify-content: center;
         display: none;
         display: none;
         align-items: center;
         align-items: center;
+        align-items: center;
+        justify-content: center;
+        box-sizing: border-box;
+        width: 80%;
         transform: translate(-50%, -50%);
         transform: translate(-50%, -50%);
 
 
         .btn {
         .btn {
@@ -430,6 +433,8 @@ function joinHlsRoom() {
       background-color: rgba($color: #000000, $alpha: 0.3);
       background-color: rgba($color: #000000, $alpha: 0.3);
       vertical-align: top;
       vertical-align: top;
 
 
+      @extend %hideScrollbar;
+
       .list {
       .list {
         .item {
         .item {
           position: relative;
           position: relative;
@@ -514,8 +519,8 @@ function joinHlsRoom() {
     }
     }
   }
   }
   .area-container {
   .area-container {
-    width: 1336px;
     margin: 10px auto;
     margin: 10px auto;
+    width: 1336px;
     .area-item {
     .area-item {
       .title {
       .title {
         padding: 10px 0;
         padding: 10px 0;
@@ -538,19 +543,19 @@ function joinHlsRoom() {
           background-size: cover;
           background-size: cover;
           .cdn-ico {
           .cdn-ico {
             position: absolute;
             position: absolute;
-            right: -10px;
             top: -10px;
             top: -10px;
-            background-color: #f87c48;
-            color: white;
+            right: -10px;
             z-index: 2;
             z-index: 2;
-            height: 28px;
             width: 70px;
             width: 70px;
-            transform-origin: bottom;
+            height: 28px;
+            background-color: #f87c48;
+            color: white;
             transform: rotate(45deg);
             transform: rotate(45deg);
+            transform-origin: bottom;
             .txt {
             .txt {
               margin-left: 18px;
               margin-left: 18px;
-              font-size: 13px;
               background-image: initial !important;
               background-image: initial !important;
+              font-size: 13px;
             }
             }
           }
           }
 
 

+ 12 - 22
src/views/pull/index.vue

@@ -18,6 +18,7 @@
               <div class="top">{{ userName || '-' }}</div>
               <div class="top">{{ userName || '-' }}</div>
               <div class="bottom">
               <div class="bottom">
                 <span>{{ roomName }}</span>
                 <span>{{ roomName }}</span>
+                <span>socketId:{{ getSocketId() }}</span>
               </div>
               </div>
             </div>
             </div>
           </div>
           </div>
@@ -74,7 +75,7 @@
                 x5-video-orientation="portraint"
                 x5-video-orientation="portraint"
                 muted
                 muted
               ></video>
               ></video>
-              <div>{{ item.socketId }}</div>
+              <div class="name">{{ item.socketId }}</div>
             </div>
             </div>
 
 
             <div
             <div
@@ -214,7 +215,6 @@ import { useRoute } from 'vue-router';
 
 
 import { fetchGoodsList } from '@/api/goods';
 import { fetchGoodsList } from '@/api/goods';
 import { loginTip } from '@/hooks/use-login';
 import { loginTip } from '@/hooks/use-login';
-import { useHlsPlay } from '@/hooks/use-play';
 import { usePull } from '@/hooks/use-pull';
 import { usePull } from '@/hooks/use-pull';
 import {
 import {
   DanmuMsgTypeEnum,
   DanmuMsgTypeEnum,
@@ -223,7 +223,6 @@ import {
   liveTypeEnum,
   liveTypeEnum,
 } from '@/interface';
 } from '@/interface';
 import { useUserStore } from '@/store/user';
 import { useUserStore } from '@/store/user';
-import { videoToCanvas } from '@/utils';
 
 
 import RechargeCpt from './recharge/index.vue';
 import RechargeCpt from './recharge/index.vue';
 
 
@@ -231,7 +230,6 @@ const route = useRoute();
 const userStore = useUserStore();
 const userStore = useUserStore();
 
 
 const giftGoodsList = ref<IGoods[]>([]);
 const giftGoodsList = ref<IGoods[]>([]);
-const showControls = ref(false);
 const giftLoading = ref(false);
 const giftLoading = ref(false);
 const showRecharge = ref(false);
 const showRecharge = ref(false);
 const showJoin = ref(true);
 const showJoin = ref(true);
@@ -276,24 +274,6 @@ const {
   liveType: route.query.liveType as liveTypeEnum,
   liveType: route.query.liveType as liveTypeEnum,
   isSRS: route.query.liveType === liveTypeEnum.srsWebrtcPull,
   isSRS: route.query.liveType === liveTypeEnum.srsWebrtcPull,
 });
 });
-const showPlayBtn = ref(true);
-
-const { hlsVideoEl, startHlsPlay } = useHlsPlay();
-
-async function startPull() {
-  showPlayBtn.value = false;
-  videoLoading.value = true;
-  const res = await startHlsPlay({
-    hlsurl: hlsurl.value,
-  });
-  videoToCanvas({
-    videoEl: hlsVideoEl.value!,
-    targetEl: canvasRef.value!,
-    width: res.width,
-    height: res.height,
-  });
-  videoLoading.value = false;
-}
 
 
 async function getGoodsList() {
 async function getGoodsList() {
   try {
   try {
@@ -499,9 +479,12 @@ onMounted(() => {
         }
         }
       }
       }
       .sidebar {
       .sidebar {
+        overflow: scroll;
         width: 120px;
         width: 120px;
         height: 100%;
         height: 100%;
         background-color: rgba($color: #000000, $alpha: 0.3);
         background-color: rgba($color: #000000, $alpha: 0.3);
+
+        @extend %hideScrollbar;
         .join {
         .join {
           color: white;
           color: white;
           cursor: pointer;
           cursor: pointer;
@@ -509,6 +492,9 @@ onMounted(() => {
         video {
         video {
           max-width: 100%;
           max-width: 100%;
         }
         }
+        .name {
+          word-wrap: break-word;
+        }
       }
       }
     }
     }
 
 
@@ -597,6 +583,8 @@ onMounted(() => {
       padding: 0 15px;
       padding: 0 15px;
       height: 100px;
       height: 100px;
       background-color: papayawhip;
       background-color: papayawhip;
+
+      @extend %hideScrollbar;
       .item {
       .item {
         display: flex;
         display: flex;
         align-items: center;
         align-items: center;
@@ -625,6 +613,8 @@ onMounted(() => {
       padding: 0 15px;
       padding: 0 15px;
       height: 450px;
       height: 450px;
       text-align: initial;
       text-align: initial;
+
+      @extend %hideScrollbar;
       .item {
       .item {
         margin-bottom: 10px;
         margin-bottom: 10px;
         font-size: 12px;
         font-size: 12px;

+ 49 - 3
src/views/push/index.vue

@@ -102,6 +102,26 @@
             </div>
             </div>
           </div>
           </div>
         </div>
         </div>
+        <div class="rtc">
+          <div class="item">
+            <div class="txt">码率设置</div>
+            <div class="down">
+              <n-select
+                v-model:value="currentMaxBitrate"
+                :options="maxBitrate"
+              />
+            </div>
+          </div>
+          <div class="item">
+            <div class="txt">分辨率设置</div>
+            <div class="down">
+              <n-select
+                v-model:value="currentResolutionRatio"
+                :options="resolutionRatio"
+              />
+            </div>
+          </div>
+        </div>
         <div class="other">
         <div class="other">
           <div class="top">
           <div class="top">
             <span class="item">
             <span class="item">
@@ -229,6 +249,10 @@ const {
   endLive,
   endLive,
   sendDanmu,
   sendDanmu,
   keydownDanmu,
   keydownDanmu,
+  currentResolutionRatio,
+  currentMaxBitrate,
+  resolutionRatio,
+  maxBitrate,
   disabled,
   disabled,
   danmuStr,
   danmuStr,
   roomName,
   roomName,
@@ -311,9 +335,12 @@ onMounted(() => {
         }
         }
       }
       }
       .sidebar {
       .sidebar {
+        overflow: scroll;
         width: 130px;
         width: 130px;
         height: 100%;
         height: 100%;
         background-color: rgba($color: #000000, $alpha: 0.3);
         background-color: rgba($color: #000000, $alpha: 0.3);
+
+        @extend %hideScrollbar;
         .title {
         .title {
           color: white;
           color: white;
         }
         }
@@ -352,19 +379,36 @@ onMounted(() => {
         .detail {
         .detail {
           display: flex;
           display: flex;
           flex-direction: column;
           flex-direction: column;
+          flex-shrink: 0;
+          width: 200px;
           text-align: initial;
           text-align: initial;
           .top {
           .top {
             margin-bottom: 10px;
             margin-bottom: 10px;
             color: #18191c;
             color: #18191c;
-            .btn {
-              margin-left: 10px;
-            }
           }
           }
           .bottom {
           .bottom {
             font-size: 14px;
             font-size: 14px;
           }
           }
         }
         }
       }
       }
+      .rtc {
+        display: flex;
+        align-items: center;
+        flex: 1;
+        font-size: 14px;
+        .item {
+          display: flex;
+          align-items: center;
+          flex: 1;
+          .txt {
+            flex-shrink: 0;
+            width: 80px;
+          }
+          .down {
+            width: 80px;
+          }
+        }
+      }
       .other {
       .other {
         display: flex;
         display: flex;
         flex-direction: column;
         flex-direction: column;
@@ -439,6 +483,8 @@ onMounted(() => {
         margin-bottom: 10px;
         margin-bottom: 10px;
         height: 300px;
         height: 300px;
 
 
+        @extend %hideScrollbar;
+
         .item {
         .item {
           margin-bottom: 10px;
           margin-bottom: 10px;
           font-size: 12px;
           font-size: 12px;