Pārlūkot izejas kodu

feat: 优化移动端webrtc

shuisheng 3 gadi atpakaļ
vecāks
revīzija
722e3a4699

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

@@ -42,7 +42,7 @@ export function usePull({
   localVideoRef: Ref<HTMLVideoElement[]>;
   canvasRef: Ref<Element | undefined>;
   isSRS?: boolean;
-  liveType?: liveTypeEnum;
+  liveType: liveTypeEnum;
 }) {
   const route = useRoute();
   const userStore = useUserStore();
@@ -59,7 +59,9 @@ export function usePull({
   const remoteVideoRef = ref(videoEl);
   const heartbeatTimer = ref();
   const roomId = ref(route.params.roomId as string);
+  const roomLiveType = ref<liveTypeEnum>(liveType);
   const roomName = ref('');
+  const roomSocketId = ref('');
   const userName = ref('');
   const userAvatar = ref('');
   const streamurl = ref('');
@@ -214,8 +216,8 @@ export function usePull({
     remoteVideoRef.value?.addEventListener('loadedmetadata', () => {
       console.warn('视频流-loadedmetadata');
       if (
-        liveType === liveTypeEnum.webrtcPull ||
-        liveType === liveTypeEnum.srsWebrtcPull
+        roomLiveType.value === liveTypeEnum.webrtcPull ||
+        roomLiveType.value === liveTypeEnum.srsWebrtcPull
       ) {
         canvasRef.value?.appendChild(remoteVideoRef.value);
       }
@@ -290,10 +292,14 @@ export function usePull({
     sender: string;
     receiver: string;
   }) {
+    console.log(isDone.value);
     if (isDone.value) return;
     const instance = networkStore.wsMap.get(roomId.value);
+    console.log(instance, roomId.value);
     if (!instance) return;
     const rtc = networkStore.getRtcMap(`${roomId.value}___${receiver}`);
+    console.log(rtc, `${roomId.value}___${receiver}`, 222);
+
     if (!rtc) return;
     const sdp = await rtc.createOffer();
     await rtc.setLocalDescription(sdp);
@@ -464,6 +470,7 @@ export function usePull({
       WsMsgTypeEnum.joined,
       async (data: { data: ILive }) => {
         prettierReceiveWebsocket(WsMsgTypeEnum.joined, data);
+        roomSocketId.value = data.data.socket_id!;
         roomName.value = data.data.live_room?.name!;
         userName.value = data.data.user?.username!;
         userAvatar.value = data.data.user?.avatar!;
@@ -477,9 +484,9 @@ export function usePull({
           'webrtc'
         );
         currentLiveRoom.value = data.data;
-        if (route.query.liveType === liveTypeEnum.srsWebrtcPull) {
+        if (roomLiveType.value === liveTypeEnum.srsWebrtcPull) {
           instance.send({ msgType: WsMsgTypeEnum.getLiveUser });
-        } else if (route.query.liveType === liveTypeEnum.srsFlvPull) {
+        } else if (roomLiveType.value === liveTypeEnum.srsFlvPull) {
           if (!autoplayVal.value) return;
           const { width, height } = await startFlvPlay({
             flvurl: flvurl.value,
@@ -493,7 +500,7 @@ export function usePull({
             // height: flvPlayer.value?.mediaInfo.height!,
           });
           videoLoading.value = false;
-        } else if (route.query.liveType === liveTypeEnum.srsHlsPull) {
+        } else if (roomLiveType.value === liveTypeEnum.srsHlsPull) {
           if (!autoplayVal.value) return;
           const { width, height } = await startHlsPlay({
             hlsurl: hlsurl.value,
@@ -637,7 +644,7 @@ export function usePull({
     // 管理员正在直播
     instance.socketIo.on(WsMsgTypeEnum.roomLiveing, (data) => {
       prettierReceiveWebsocket(WsMsgTypeEnum.roomLiveing, data);
-      if (isSRS && liveType !== liveTypeEnum.srsFlvPull) {
+      if (isSRS && roomLiveType.value !== liveTypeEnum.srsFlvPull) {
         startNewWebRtc({});
       }
     });
@@ -730,8 +737,11 @@ export function usePull({
     startGetDisplayMedia,
     addTrack,
     addVideo,
+    autoplayVal,
     videoLoading,
     balance,
+    roomLiveType,
+    roomSocketId,
     roomName,
     userName,
     userAvatar,

+ 30 - 25
src/hooks/use-push.ts

@@ -61,9 +61,9 @@ export function usePush({
   const roomId = ref('-1');
   const roomName = ref('');
   const danmuStr = ref('');
+  const isLiving = ref(false);
   const isDone = ref(false);
   const joined = ref(false);
-  const disabled = ref(false);
   const localStream = ref();
   const offerSended = ref(new Set());
   const webRTC = ref<WebRTCClass | SRSWebRTCClass>();
@@ -164,6 +164,9 @@ export function usePush({
   watch(
     () => currentMaxBitrate.value,
     async (newVal) => {
+      if (!webRTC.value) {
+        return;
+      }
       const res = await webRTC.value?.setMaxBitrate(newVal);
       if (res === 1) {
         window.$message.success('切换码率成功!');
@@ -176,7 +179,10 @@ export function usePush({
   watch(
     () => currentResolutionRatio.value,
     async (newVal) => {
-      const res = await webRTC.value?.setResolutionRatio(newVal);
+      if (!webRTC.value) {
+        return;
+      }
+      const res = await webRTC.value.setResolutionRatio(newVal);
       if (res === 1) {
         window.$message.success('切换分辨率成功!');
       } else {
@@ -265,16 +271,36 @@ export function usePush({
       window.$message.warning('请选择一个素材!');
       return;
     }
-    disabled.value = true;
     const instance = new WebSocketClass({
       roomId: roomId.value,
       url: WEBSOCKET_URL,
       isAnchor: true,
     });
+    isLiving.value = true;
     instance.update();
     initReceive();
   }
 
+  /** 结束直播 */
+  function endLive() {
+    isLiving.value = false;
+    currMediaTypeList.value = [];
+    localStream.value = null;
+    localVideoRef.value!.srcObject = null;
+    clearInterval(heartbeatTimer.value);
+    const instance = networkStore.wsMap.get(roomId.value);
+    if (instance) {
+      instance.send({
+        msgType: WsMsgTypeEnum.roomNoLive,
+        data: {},
+      });
+    }
+    setTimeout(() => {
+      closeWs();
+      closeRtc();
+    }, 500);
+  }
+
   /** 原生的webrtc时,receiver必传 */
   async function startNewWebRtc({
     receiver,
@@ -417,7 +443,6 @@ export function usePush({
   function batchSendOffer() {
     console.log('batchSendOffer');
     liveUserList.value.forEach(async (item) => {
-      console.log(item, 'liveUserList');
       const socketId = item.id;
       if (!offerSended.value.has(socketId) && socketId !== getSocketId()) {
         const rtc = networkStore.getRtcMap(`${roomId.value}___${socketId}`);
@@ -711,7 +736,6 @@ export function usePush({
 
   function confirmRoomName() {
     if (!roomNameIsOk()) return;
-    disabled.value = true;
   }
 
   function sendDanmu() {
@@ -742,25 +766,6 @@ export function usePush({
     danmuStr.value = '';
   }
 
-  /** 结束直播 */
-  function endLive() {
-    disabled.value = false;
-    currMediaTypeList.value = [];
-    localStream.value = null;
-    localVideoRef.value!.srcObject = null;
-    clearInterval(heartbeatTimer.value);
-    const instance = networkStore.wsMap.get(roomId.value);
-    if (instance) {
-      instance.send({
-        msgType: WsMsgTypeEnum.roomNoLive,
-        data: {},
-      });
-    }
-    setTimeout(() => {
-      closeWs();
-      closeRtc();
-    }, 500);
-  }
   async function getAllMediaDevices() {
     const res = await navigator.mediaDevices.enumerateDevices();
     // const audioInput = res.filter(
@@ -801,6 +806,7 @@ export function usePush({
 
   return {
     initPush,
+    isLiving,
     confirmRoomName,
     getSocketId,
     startGetDisplayMedia,
@@ -813,7 +819,6 @@ export function usePush({
     currentMaxBitrate,
     resolutionRatio,
     maxBitrate,
-    disabled,
     danmuStr,
     roomName,
     damuList,

+ 8 - 8
src/interface.ts

@@ -137,14 +137,6 @@ export interface IGoods {
   deleted_at?: string;
 }
 
-/** 直播间类型 */
-export enum LiveRoomTypeEnum {
-  system, // 系统直播
-  user_wertc, // 主播使用webrtc直播(用户只能看webrtc直播)
-  user_srs, // 主播使用srs直播(用户可以看webrtc或flv直播)
-  user_obs, // 主播使用obs/ffmpeg直播(用户只能看flv直播)
-}
-
 export interface ILiveRoom {
   id?: number;
   /** 用户信息 */
@@ -194,6 +186,14 @@ export enum liveTypeEnum {
   webrtcPush = 'webrtcPush',
 }
 
+/** 直播间类型 */
+export enum LiveRoomTypeEnum {
+  system, // 系统直播
+  user_wertc, // 主播使用webrtc直播(用户只能看webrtc直播)
+  user_srs, // 主播使用srs直播(用户可以看webrtc或flv直播)
+  user_obs, // 主播使用obs/ffmpeg直播(用户只能看flv直播)
+}
+
 export interface BilldHtmlWebpackPluginLog {
   pkgName: string;
   pkgVersion: string;

+ 1 - 1
src/utils/index.ts

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

+ 1 - 4
src/views/h5/area/index.vue

@@ -41,7 +41,7 @@ import { onMounted, ref } from 'vue';
 import { useRoute } from 'vue-router';
 
 import { fetchLiveRoomList } from '@/api/area';
-import { ILiveRoom, liveTypeEnum } from '@/interface';
+import { ILiveRoom } from '@/interface';
 import router, { routerName } from '@/router';
 
 const liveRoomList = ref<ILiveRoom[]>([]);
@@ -55,9 +55,6 @@ function goRoom(item: ILiveRoom) {
   router.push({
     name: routerName.h5Room,
     params: { roomId: item.id },
-    query: {
-      liveType: liveTypeEnum.srsHlsPull,
-    },
   });
 }
 

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

@@ -67,7 +67,7 @@
 import { onMounted, ref } from 'vue';
 
 import { fetchAreaLiveRoomList } from '@/api/area';
-import { IArea, IAreaLiveRoom, liveTypeEnum } from '@/interface';
+import { IArea, IAreaLiveRoom } from '@/interface';
 import router, { mobileRouterName, routerName } from '@/router';
 
 const navList = ref([
@@ -116,9 +116,6 @@ function goRoom(item: IAreaLiveRoom) {
   router.push({
     name: routerName.h5Room,
     params: { roomId: item.live_room_id },
-    query: {
-      liveType: liveTypeEnum.srsHlsPull,
-    },
   });
 }
 

+ 41 - 20
src/views/h5/room/index.vue

@@ -31,17 +31,6 @@
           backgroundImage: `url(${liveRoomInfo?.cover_img})`,
         }"
       ></div>
-      <!-- <video
-        ref="canvasRef"
-        autoplay
-        webkit-playsinline="true"
-        playsinline
-        x-webkit-airplay="allow"
-        x5-video-player-type="h5"
-        x5-video-player-fullscreen="true"
-        x5-video-orientation="portraint"
-        :muted="appStore.muted"
-      ></video> -->
       <div ref="canvasRef"></div>
       <div
         v-if="showPlayBtn"
@@ -112,7 +101,12 @@ import { useRoute } from 'vue-router';
 import { fetchFindLiveRoom } from '@/api/liveRoom';
 import { useHlsPlay } from '@/hooks/use-play';
 import { usePull } from '@/hooks/use-pull';
-import { DanmuMsgTypeEnum, ILiveRoom, liveTypeEnum } from '@/interface';
+import {
+  DanmuMsgTypeEnum,
+  ILiveRoom,
+  LiveRoomTypeEnum,
+  liveTypeEnum,
+} from '@/interface';
 import router, { mobileRouterName } from '@/router';
 import { videoToCanvas } from '@/utils';
 
@@ -122,7 +116,7 @@ const bottomRef = ref<HTMLDivElement>();
 const containerRef = ref<HTMLDivElement>();
 const canvasRef = ref<HTMLVideoElement>();
 const localVideoRef = ref<HTMLVideoElement[]>([]);
-const showPlayBtn = ref(true);
+const showPlayBtn = ref(false);
 
 const { hlsVideoEl, startHlsPlay } = useHlsPlay();
 
@@ -138,12 +132,16 @@ const {
   startGetDisplayMedia,
   addTrack,
   addVideo,
+  autoplayVal,
   videoLoading,
   balance,
+  roomLiveType,
+  roomSocketId,
   roomName,
   userName,
   userAvatar,
   currentLiveRoom,
+  hlsurl,
   coverImg,
   roomNoLive,
   damuList,
@@ -156,8 +154,8 @@ const {
 } = usePull({
   localVideoRef,
   canvasRef,
-  liveType: route.query.liveType as liveTypeEnum,
-  isSRS: route.query.liveType === liveTypeEnum.srsWebrtcPull,
+  liveType: liveTypeEnum.srsHlsPull,
+  isSRS: false,
 });
 
 const liveRoomInfo = ref<ILiveRoom>();
@@ -174,16 +172,31 @@ watch(
 );
 
 async function getLiveRoomInfo() {
-  const res = await fetchFindLiveRoom(route.params.roomId as string);
-  if (res.code === 200) {
-    liveRoomInfo.value = res.data;
+  try {
+    videoLoading.value = true;
+    const res = await fetchFindLiveRoom(route.params.roomId as string);
+    if (res.code === 200) {
+      liveRoomInfo.value = res.data;
+      if (res.data.type === LiveRoomTypeEnum.user_wertc) {
+        roomLiveType.value = liveTypeEnum.webrtcPull;
+        autoplayVal.value = true;
+        showPlayBtn.value = false;
+      } else {
+        autoplayVal.value = false;
+        showPlayBtn.value = true;
+      }
+      initPull(autoplayVal.value);
+    }
+  } catch (error) {
+    console.error(error);
+  } finally {
+    videoLoading.value = false;
   }
 }
 
 async function startPull() {
   showPlayBtn.value = false;
   videoLoading.value = true;
-
   const { width, height } = await startHlsPlay({
     hlsurl: liveRoomInfo.value!.hls_url!,
   });
@@ -199,7 +212,6 @@ async function startPull() {
 onMounted(() => {
   scrollTo({ top: 0 });
   getLiveRoomInfo();
-  initPull(false);
   if (containerRef.value && bottomRef.value) {
     const res =
       bottomRef.value.getBoundingClientRect().top -
@@ -254,6 +266,15 @@ onMounted(() => {
 
       inset: 0;
     }
+    :deep(video) {
+      position: absolute;
+      top: 0;
+      left: 50%;
+      height: 100%;
+      transform: translate(-50%);
+
+      user-select: nones;
+    }
     :deep(canvas) {
       position: absolute;
       top: 0;

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

@@ -18,11 +18,6 @@
             })`,
           }"
         ></div>
-        <!-- x-webkit-airplay这个属性应该是使此视频支持ios的AirPlay功能 -->
-        <!-- playsinline、 webkit-playsinline IOS微信浏览器支持小窗内播放 -->
-        <!-- x5-video-player-type 启用H5播放器,是wechat安卓版特性 -->
-        <!-- x5-video-player-fullscreen 全屏设置 -->
-        <!-- x5-video-orientation 声明播放器支持的方向,可选值landscape横屏,portraint竖屏。默认值portraint。 -->
         <div
           v-if="currentLiveRoom?.live_room?.flv_url"
           ref="canvasRef"

+ 5 - 11
src/views/pull/index.vue

@@ -40,17 +40,6 @@
               }"
             ></div>
             <div ref="canvasRef"></div>
-            <!-- <div
-              v-if="
-                route.query.liveType === liveTypeEnum.srsHlsPull &&
-                hlsurl &&
-                showPlayBtn
-              "
-              class="tip-btn"
-              @click="startPull"
-            >
-              点击播放
-            </div> -->
             <VideoControls></VideoControls>
           </div>
 
@@ -64,6 +53,11 @@
               class="item"
               :socketId="item.socketId"
             >
+              <!-- x-webkit-airplay这个属性应该是使此视频支持ios的AirPlay功能 -->
+              <!-- playsinline、 webkit-playsinline IOS微信浏览器支持小窗内播放 -->
+              <!-- x5-video-player-type 启用H5播放器,是wechat安卓版特性 -->
+              <!-- x5-video-player-fullscreen 全屏设置 -->
+              <!-- x5-video-orientation 声明播放器支持的方向,可选值landscape横屏,portraint竖屏。默认值portraint。 -->
               <video
                 :ref="(el) => (localVideoRef[item.socketId] = el)"
                 autoplay

+ 19 - 31
src/views/push/index.vue

@@ -130,22 +130,22 @@
             </span>
           </div>
           <div class="bottom">
-            <n-space>
-              <n-button
-                type="info"
-                size="small"
-                @click="startLive"
-              >
-                开始直播
-              </n-button>
-              <n-button
-                type="info"
-                size="small"
-                @click="endLive"
-              >
-                结束直播
-              </n-button>
-            </n-space>
+            <n-button
+              v-if="!isLiving"
+              type="info"
+              size="small"
+              @click="startLive"
+            >
+              开始直播
+            </n-button>
+            <n-button
+              v-else
+              type="info"
+              size="small"
+              @click="endLive"
+            >
+              结束直播
+            </n-button>
           </div>
         </div>
       </div>
@@ -241,6 +241,7 @@ const remoteVideoRef = ref<HTMLVideoElement[]>([]);
 
 const {
   initPush,
+  isLiving,
   confirmRoomName,
   getSocketId,
   startGetDisplayMedia,
@@ -253,7 +254,6 @@ const {
   currentMaxBitrate,
   resolutionRatio,
   maxBitrate,
-  disabled,
   danmuStr,
   roomName,
   damuList,
@@ -406,6 +406,8 @@ onMounted(() => {
           }
           .down {
             width: 80px;
+
+            user-select: none;
           }
         }
       }
@@ -415,20 +417,6 @@ onMounted(() => {
         justify-content: center;
         font-size: 12px;
         .top {
-          display: flex;
-          align-items: center;
-          .item {
-            display: flex;
-            align-items: center;
-            margin-right: 20px;
-            .ico {
-              display: inline-block;
-              margin-right: 4px;
-              width: 10px;
-              height: 10px;
-              border-radius: 50%;
-            }
-          }
         }
         .bottom {
           margin-top: 10px;