shuisheng 2 年 前
コミット
c6023c94b9

+ 1 - 0
package.json

@@ -77,6 +77,7 @@
     "copy-webpack-plugin": "^8.1.0",
     "core-js": "^3.17.2",
     "cross-env": "^7.0.3",
+    "crypto-js": "^4.2.0",
     "css-loader": "^6.7.1",
     "css-minimizer-webpack-plugin": "^3.0.0",
     "cz-conventional-changelog": "^3.3.0",

+ 7 - 0
pnpm-lock.yaml

@@ -136,6 +136,9 @@ devDependencies:
   cross-env:
     specifier: ^7.0.3
     version: 7.0.3
+  crypto-js:
+    specifier: ^4.2.0
+    version: 4.2.0
   css-loader:
     specifier: ^6.7.1
     version: 6.7.3(webpack@5.76.2)
@@ -4460,6 +4463,10 @@ packages:
       shebang-command: 2.0.0
       which: 2.0.2
 
+  /crypto-js@4.2.0:
+    resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
+    dev: true
+
   /css-blank-pseudo@3.0.3(postcss@8.4.21):
     resolution: {integrity: sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ==}
     engines: {node: ^12 || ^14 || >=16}

+ 14 - 0
src/api/livePlay.ts

@@ -0,0 +1,14 @@
+import request from '@/utils/request';
+
+export function fetchLivePlayList(params: {
+  orderName: string;
+  orderBy: string;
+  nowPage?: number;
+  pageSize?: number;
+}) {
+  return request.instance({
+    url: '/live_play/list',
+    method: 'get',
+    params,
+  });
+}

+ 0 - 113
src/components/LoginModal/index.vue

@@ -74,61 +74,6 @@
                 登录
               </n-button>
             </n-tab-pane>
-            <!-- <n-tab-pane
-              name="codelogin"
-              tab="免密登录"
-            >
-              <n-form
-                ref="registerFormRef"
-                label-placement="left"
-                size="large"
-                :model="registerForm"
-                :rules="registerRules"
-              >
-                <n-form-item path="email">
-                  <n-input
-                    v-model:value="registerForm.email"
-                    placeholder="请输入邮箱"
-                  >
-                    <template #prefix>
-                      <n-icon
-                        size="20"
-                        class="lang"
-                      >
-                        <MailOutline></MailOutline>
-                      </n-icon>
-                    </template>
-                  </n-input>
-                </n-form-item>
-                <n-form-item path="code">
-                  <n-input-group>
-                    <n-input
-                      v-model:value="registerForm.code"
-                      placeholder="请输入验证码"
-                      @keyup.enter="handleRegisterSubmit"
-                    />
-                    <n-button
-                      type="primary"
-                      ghost
-                      :disabled="downCount !== 0"
-                      :loading="sendCodeLoading"
-                      @click="sendCode()"
-                    >
-                      发送{{ downCount !== 0 ? `(${downCount})` : '' }}
-                    </n-button>
-                  </n-input-group>
-                </n-form-item>
-              </n-form>
-              <n-button
-                type="primary"
-                block
-                secondary
-                strong
-                @click="handleRegisterSubmit"
-              >
-                登录
-              </n-button>
-            </n-tab-pane> -->
           </n-tabs>
         </n-card>
         <div class="other-login">
@@ -160,9 +105,7 @@
 <script lang="ts" setup>
 import { LockClosedOutline, PersonOutline } from '@vicons/ionicons5';
 import { ref } from 'vue';
-import { useRoute, useRouter } from 'vue-router';
 
-import { fetchSendLoginCode, fetchSendRegisterCode } from '@/api/emailUser';
 import { useQQLogin } from '@/hooks/use-login';
 import { useAppStore } from '@/store/app';
 import { useUserStore } from '@/store/user';
@@ -171,13 +114,7 @@ const loginRules = {
   id: { required: true, message: '请输入账号', trigger: 'blur' },
   password: { required: true, message: '请输入密码', trigger: 'blur' },
 };
-const registerRules = {
-  email: { required: true, message: '请输入邮箱', trigger: 'blur' },
-  code: { required: true, message: '请输入验证码', trigger: 'blur' },
-};
 
-const router = useRouter();
-const route = useRoute();
 const userStore = useUserStore();
 const appStore = useAppStore();
 
@@ -190,10 +127,7 @@ const registerForm = ref({
   code: '',
 });
 const loginFormRef = ref(null);
-const registerFormRef = ref(null);
 const currentTab = ref('pwdlogin');
-const sendCodeLoading = ref(false);
-const downCount = ref(0);
 
 const emits = defineEmits(['close']);
 
@@ -228,15 +162,6 @@ const handleLogin = async () => {
     appStore.showLoginModal = false;
   }
 };
-const handleRegister = async () => {
-  const { token } = await userStore.register({
-    email: registerForm.value.email,
-    code: registerForm.value.code,
-  });
-  if (token) {
-    window.$message.success('注册成功!');
-  }
-};
 const handleLoginSubmit = (e) => {
   e.preventDefault();
   // @ts-ignore
@@ -246,44 +171,6 @@ const handleLoginSubmit = (e) => {
     }
   });
 };
-const handleRegisterSubmit = (e) => {
-  e.preventDefault();
-  // @ts-ignore
-  registerFormRef.value.validate((errors) => {
-    if (!errors) {
-      if (currentTab.value === 'register') {
-        handleRegister();
-      } else {
-        handleLogin();
-      }
-    }
-  });
-};
-/** 发送验证码 */
-const sendCode = async () => {
-  if (registerForm.value.email === '')
-    return window.$message.warning('请输入邮箱!');
-  try {
-    sendCodeLoading.value = true;
-    if (currentTab.value === 'codelogin') {
-      await fetchSendLoginCode(registerForm.value.email);
-    } else {
-      await fetchSendRegisterCode(registerForm.value.email);
-    }
-    sendCodeLoading.value = false;
-    window.$message.success('发送成功!');
-    downCount.value = 60;
-    const timer = setInterval(() => {
-      downCount.value -= 1;
-      if (downCount.value === 0) {
-        clearInterval(timer);
-      }
-    }, 1000);
-  } catch (error: any) {
-    sendCodeLoading.value = false;
-    console.log(error);
-  }
-};
 const tabChange = (v) => {
   currentTab.value = v;
 };

+ 29 - 0
src/components/PullAuthTip/index.vue

@@ -0,0 +1,29 @@
+<template>
+  <div class="pull-auth">
+    <div class="auth-txt">需要权限</div>
+  </div>
+</template>
+
+<script lang="ts" setup></script>
+
+<style lang="scss" scoped>
+.pull-auth {
+  position: absolute !important;
+  z-index: 10;
+  border-radius: 4px;
+  background-color: rgba($color: black, $alpha: 0.5);
+
+  @extend %maskBg;
+  .auth-txt {
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    padding: 2px 10px;
+    border-radius: 6px;
+    background-color: rgba($color: $theme-color-gold, $alpha: 0.8);
+    color: white;
+    font-weight: bold;
+    transform: translate(-50%, -50%);
+  }
+}
+</style>

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

@@ -109,11 +109,12 @@ import {
   VolumeMuteOutline,
 } from '@vicons/ionicons5';
 import { debounce } from 'billd-utils';
-import { ref } from 'vue';
+import { ref, watch } from 'vue';
 
 import { LiveLineEnum, LiveRoomTypeEnum } from '@/interface';
 import { useAppStore } from '@/store/app';
 import { usePiniaCacheStore } from '@/store/cache';
+import { useUserStore } from '@/store/user';
 
 withDefaults(
   defineProps<{
@@ -127,11 +128,23 @@ const emits = defineEmits(['refresh']);
 const debounceRefresh = debounce(() => {
   emits('refresh');
 }, 500);
+
+const userStore = useUserStore();
 const cacheStore = usePiniaCacheStore();
 const appStore = useAppStore();
 const showLine = ref(false);
 const showSpeed = ref(false);
 
+watch(
+  () => userStore.userInfo,
+  (newVal) => {
+    console.log('userInfo变了', newVal);
+  },
+  {
+    deep: true,
+  }
+);
+
 function handleTip() {
   window.$message.info('敬请期待!');
 }

+ 2 - 0
src/hooks/use-login.ts

@@ -26,6 +26,7 @@ export async function handleLogin(e) {
   if (!POSTMESSAGE_TYPE.includes(type)) return;
   console.log('收到消息', type, data);
   const userStore = useUserStore();
+  const appStore = useAppStore();
 
   try {
     switch (type) {
@@ -39,6 +40,7 @@ export async function handleLogin(e) {
         }
         userStore.setToken(res.data);
         userStore.getUserInfo();
+        appStore.showLoginModal = false;
         break;
       }
     }

+ 14 - 1
src/hooks/use-play.ts

@@ -1,4 +1,6 @@
 import '@/assets/css/videojs.scss';
+import { getRandomString } from 'billd-utils';
+import md5 from 'crypto-js/md5';
 import mpegts from 'mpegts.js';
 import videoJs from 'video.js';
 import Player from 'video.js/dist/types/player';
@@ -6,6 +8,7 @@ import { onMounted, onUnmounted, ref, watch } from 'vue';
 
 import { useAppStore } from '@/store/app';
 import { usePiniaCacheStore } from '@/store/cache';
+import { useUserStore } from '@/store/user';
 import { createVideo } from '@/utils';
 
 export * as flvJs from 'flv.js';
@@ -149,6 +152,7 @@ export function useHlsPlay() {
   const hlsVideoEl = ref<HTMLVideoElement>();
   const cacheStore = usePiniaCacheStore();
   const appStore = useAppStore();
+  const userStore = useUserStore();
   const retryMax = ref(120);
   const retry = ref(0);
   const retrying = ref(false);
@@ -221,12 +225,21 @@ export function useHlsPlay() {
           muted: cacheStore.muted,
           autoplay: true,
         });
+        const userInfo = userStore.userInfo;
+        const userToken = md5(userStore.token) as string;
+        console.log('userdddd', userInfo);
         hlsPlayer.value = videoJs(
           videoEl,
           {
             sources: [
               {
-                src: data.hlsurl,
+                src: !userInfo
+                  ? data.hlsurl
+                  : `${
+                      data.hlsurl
+                    }?usertoken=${userToken}&userid=${userInfo.id!}&randomid=${getRandomString(
+                      8
+                    )}`,
                 type: 'application/x-mpegURL',
               },
             ],

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

@@ -80,13 +80,13 @@ export function usePull() {
     videoLoading.value = false;
   });
 
-  function handleHlsPlay(url?: string) {
+  function handleHlsPlay(url: string) {
     console.log('handleHlsPlay', url);
     handleStopDrawing();
     videoLoading.value = true;
     appStore.setLiveLine(LiveLineEnum.hls);
     startHlsPlay({
-      hlsurl: url || hlsurl.value,
+      hlsurl: url,
     });
   }
 
@@ -123,28 +123,28 @@ export function usePull() {
         if (appStore.liveLine === LiveLineEnum.flv) {
           handleFlvPlay();
         } else if (appStore.liveLine === LiveLineEnum.hls) {
-          handleHlsPlay(data.hls_url);
+          handleHlsPlay(data.hls_url!);
         }
         break;
       case LiveRoomTypeEnum.user_obs:
         if (appStore.liveLine === LiveLineEnum.flv) {
           handleFlvPlay();
         } else if (appStore.liveLine === LiveLineEnum.hls) {
-          handleHlsPlay(data.hls_url);
+          handleHlsPlay(data.hls_url!);
         }
         break;
       case LiveRoomTypeEnum.user_msr:
         if (appStore.liveLine === LiveLineEnum.flv) {
           handleFlvPlay();
         } else if (appStore.liveLine === LiveLineEnum.hls) {
-          handleHlsPlay(data.hls_url);
+          handleHlsPlay(data.hls_url!);
         }
         break;
       case LiveRoomTypeEnum.system:
         if (appStore.liveLine === LiveLineEnum.flv) {
           handleFlvPlay();
         } else if (appStore.liveLine === LiveLineEnum.hls) {
-          handleHlsPlay(data.hls_url);
+          handleHlsPlay(data.hls_url!);
         }
         break;
       case LiveRoomTypeEnum.user_wertc:
@@ -179,7 +179,7 @@ export function usePull() {
           handleFlvPlay();
           break;
         case LiveLineEnum.hls:
-          handleHlsPlay();
+          handleHlsPlay(hlsurl.value);
           break;
         case LiveLineEnum.rtc:
           break;

+ 23 - 6
src/interface.ts

@@ -1,4 +1,4 @@
-// 这里放项目里面的类型
+/** 这里放项目里面的类型 */
 
 export enum LiveLineEnum {
   rtc = 'rtc',
@@ -143,6 +143,14 @@ export interface IGoods {
   deleted_at?: string;
 }
 
+/** 拉流是否需要鉴权 */
+export enum LiveRoomPullIsShouldAuthEnum {
+  /** 需要鉴权 */
+  yes,
+  /** 不需要鉴权 */
+  no,
+}
+
 export interface ILiveRoom {
   id?: number;
   /** 用户信息 */
@@ -159,8 +167,12 @@ export interface ILiveRoom {
   cdn?: number;
   /** 权重 */
   weight?: number;
+  /** 推流秘钥 */
   key?: string;
+  /** 直播间类型 */
   type?: LiveRoomTypeEnum;
+  /** 拉流是否需要鉴权 */
+  pull_is_should_auth?: LiveRoomPullIsShouldAuthEnum;
   cover_img?: string;
   rtmp_url?: string;
   flv_url?: string;
@@ -185,11 +197,16 @@ export interface IUserLiveRoom {
 
 /** 直播间类型 */
 export enum LiveRoomTypeEnum {
-  system, // 系统直播
-  user_wertc, // 主播使用webrtc直播
-  user_srs, // 主播使用srs直播
-  user_obs, // 主播使用obs/ffmpeg直播
-  user_msr, // 主播使用msr直播
+  /** 系统直播 */
+  system,
+  /** 主播使用webrtc直播 */
+  user_wertc,
+  /** 主播使用srs直播 */
+  user_srs,
+  /** 主播使用obs/ffmpeg直播 */
+  user_obs,
+  /** 主播使用msr直播 */
+  user_msr,
 }
 
 export interface BilldHtmlWebpackPluginLog {

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

@@ -302,13 +302,12 @@ import { openToTarget, windowReload } from 'billd-utils';
 import { onMounted, ref } from 'vue';
 import { useRouter } from 'vue-router';
 
-import { fetchAreaList } from '@/api/area';
 import Dropdown from '@/components/Dropdown/index.vue';
 import VPIconChevronDown from '@/components/icons/VPIconChevronDown.vue';
 import VPIconExternalLink from '@/components/icons/VPIconExternalLink.vue';
 import { COMMON_URL } from '@/constant';
 import { loginTip } from '@/hooks/use-login';
-import { IArea, LiveRoomTypeEnum } from '@/interface';
+import { LiveRoomTypeEnum } from '@/interface';
 import { routerName } from '@/router';
 import { useAppStore } from '@/store/app';
 import { useUserStore } from '@/store/user';
@@ -320,7 +319,6 @@ const githubStar = ref('');
 const dropdownDoc = ref(false);
 const dropdownSys = ref(false);
 const dropdownAbout = ref(false);
-const areaList = ref<IArea[]>([]);
 
 const about = ref([
   {
@@ -396,17 +394,6 @@ function handleLogout() {
   }, 500);
 }
 
-async function getAreaList() {
-  const res = await fetchAreaList();
-  if (res.code === 200) {
-    areaList.value = res.data.rows;
-  }
-}
-
-function changeArea(item: IArea) {
-  router.push({ name: routerName.area, params: { id: item.id } });
-}
-
 function handleJump(item) {
   if (item.url) {
     openToTarget(item.url);
@@ -418,7 +405,6 @@ function handleJump(item) {
 onMounted(() => {
   githubStar.value =
     'https://img.shields.io/github/stars/galaxy-s10/billd-live?label=Star&logo=GitHub&labelColor=white&logoColor=black&style=social&cacheSeconds=3600';
-  getAreaList();
 });
 
 function quickStart() {

+ 6 - 10
src/views/faq/index.vue

@@ -7,27 +7,23 @@
         <div class="item">
           <h2>如何本地运行billd-live?</h2>
           <p>
-            1. 仔细阅读
+            一: 仔细看完
             <span
               class="link"
               @click="
                 openToTarget('https://github.com/galaxy-s10/billd-live#readme')
               "
             >
-              billd-live的readme
+              billd-live的Readme
             </span>
           </p>
           <p>
-            2. 仔细阅读
+            二:仔细看完
             <span
               class="link"
-              @click="
-                openToTarget(
-                  'https://github.com/galaxy-s10/billd-live-server#readme'
-                )
-              "
+              @click="openToTarget(COMMON_URL.bilibiliCollectiondetail)"
             >
-              billd-live-server的readme
+              b站教程(封面是"从零搭建迷你b站直播间")
             </span>
           </p>
         </div>
@@ -119,7 +115,7 @@
 <script lang="ts" setup>
 import { openToTarget } from 'billd-utils';
 
-import { AUTHOR_GITHUB } from '@/constant';
+import { AUTHOR_GITHUB, COMMON_URL } from '@/constant';
 </script>
 
 <style lang="scss" scoped>

+ 21 - 7
src/views/home/index.vue

@@ -12,6 +12,7 @@
       </div>
       <div class="container">
         <div
+          v-loading="videoLoading"
           class="left"
           @click="showJoinBtn = !showJoinBtn"
         >
@@ -30,12 +31,10 @@
               })`,
             }"
           ></div>
-          <div v-loading="videoLoading">
-            <div
-              v-if="currentLiveRoom?.live_room?.flv_url"
-              ref="remoteVideoRef"
-            ></div>
-          </div>
+          <div
+            v-if="currentLiveRoom?.live_room?.flv_url"
+            ref="remoteVideoRef"
+          ></div>
           <template v-if="currentLiveRoom">
             <VideoControls
               @click.stop
@@ -76,6 +75,12 @@
               }"
               @click="changeLiveRoom(item)"
             >
+              <PullAuthTip
+                v-if="
+                  item.live_room?.pull_is_should_auth ===
+                  LiveRoomPullIsShouldAuthEnum.yes
+                "
+              ></PullAuthTip>
               <div class="hidden">
                 <div
                   class="cdn-ico"
@@ -160,7 +165,12 @@ import { useRouter } from 'vue-router';
 import { fetchLiveList } from '@/api/live';
 import { sliderList } from '@/constant';
 import { usePull } from '@/hooks/use-pull';
-import { ILive, LiveLineEnum, LiveRoomTypeEnum } from '@/interface';
+import {
+  ILive,
+  LiveLineEnum,
+  LiveRoomPullIsShouldAuthEnum,
+  LiveRoomTypeEnum,
+} from '@/interface';
 import { routerName } from '@/router';
 import { useAppStore } from '@/store/app';
 
@@ -325,6 +335,8 @@ function joinRoom(data: { roomId: number }) {
           position: absolute;
           top: 50%;
           left: 50%;
+          min-width: 100%;
+          min-height: 100%;
           max-width: $w-1100;
           max-height: calc($w-1100 / $video-ratio);
           transform: translate(-50%, -50%);
@@ -335,6 +347,8 @@ function joinRoom(data: { roomId: number }) {
           position: absolute;
           top: 50%;
           left: 50%;
+          min-width: 100%;
+          min-height: 100%;
           max-width: $w-1100;
           max-height: calc($w-1100 / $video-ratio);
           transform: translate(-50%, -50%);

+ 4 - 4
src/views/pull/index.vue

@@ -398,9 +398,9 @@ watch(
             top: 50%;
             left: 50%;
             display: block;
-            // width: 100%;
-            // height: 100%;
             margin: 0 auto;
+            min-width: 100%;
+            min-height: 100%;
             max-width: $w-1000;
             max-height: 562px;
             transform: translate(-50%, -50%);
@@ -410,9 +410,9 @@ watch(
             top: 50%;
             left: 50%;
             display: block;
-            // width: 100%;
-            // height: 100%;
             margin: 0 auto;
+            min-width: 100%;
+            min-height: 100%;
             max-width: $w-1000;
             max-height: 562px;
             transform: translate(-50%, -50%);