index.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613
  1. <template>
  2. <div class="home-wrap">
  3. <div class="play-container">
  4. <div class="bg"></div>
  5. <div class="container">
  6. <div class="left">
  7. <div
  8. v-if="currentLiveRoom?.live_room?.cdn === 1"
  9. class="cdn-ico"
  10. >
  11. <div class="txt">CDN</div>
  12. </div>
  13. <div
  14. class="cover"
  15. :style="{
  16. backgroundImage: `url(${
  17. currentLiveRoom?.live_room?.cover_img ||
  18. currentLiveRoom?.user?.avatar
  19. })`,
  20. }"
  21. ></div>
  22. <div v-loading="videoLoading">
  23. <div
  24. v-if="currentLiveRoom?.live_room?.flv_url"
  25. ref="canvasRef"
  26. ></div>
  27. </div>
  28. <template v-if="currentLiveRoom">
  29. <VideoControls></VideoControls>
  30. <div
  31. class="join-btn"
  32. :style="{
  33. display: !isMobile() ? 'none' : showControls ? 'block' : 'none',
  34. }"
  35. >
  36. <div
  37. v-if="
  38. currentLiveRoom.live_room?.type ===
  39. LiveRoomTypeEnum.user_wertc
  40. "
  41. class="btn webrtc"
  42. @click="joinRtcRoom()"
  43. >
  44. 进入直播(webrtc)
  45. </div>
  46. <div
  47. v-if="
  48. currentLiveRoom.live_room?.type === LiveRoomTypeEnum.user_srs
  49. "
  50. class="btn webrtc"
  51. @click="joinRtcRoom()"
  52. >
  53. 进入直播(srs-webrtc)
  54. </div>
  55. <div
  56. v-if="
  57. currentLiveRoom.live_room?.type !==
  58. LiveRoomTypeEnum.user_wertc
  59. "
  60. class="btn flv"
  61. @click="
  62. joinRoom({
  63. isFlv: true,
  64. roomId: currentLiveRoom.live_room_id!,
  65. })
  66. "
  67. >
  68. 进入直播(flv)
  69. </div>
  70. <div
  71. v-if="
  72. currentLiveRoom.live_room?.type !==
  73. LiveRoomTypeEnum.user_wertc
  74. "
  75. class="btn hls"
  76. @click="
  77. joinRoom({
  78. isFlv: false,
  79. roomId: currentLiveRoom.live_room_id!,
  80. })
  81. "
  82. >
  83. 进入直播(hls)
  84. </div>
  85. </div>
  86. </template>
  87. </div>
  88. <div class="right">
  89. <div
  90. v-if="topLiveRoomList.length"
  91. class="list"
  92. >
  93. <div
  94. v-for="(item, index) in topLiveRoomList"
  95. :key="index"
  96. :class="{
  97. item: 1,
  98. active: item.live_room_id === currentLiveRoom?.live_room_id,
  99. }"
  100. :style="{
  101. backgroundImage: `url(${
  102. item.live_room?.cover_img || item?.user?.avatar
  103. })`,
  104. }"
  105. @click="changeLiveRoom(item)"
  106. >
  107. <div
  108. class="border"
  109. :style="{
  110. opacity:
  111. item.live_room_id === currentLiveRoom?.live_room_id ? 1 : 0,
  112. }"
  113. ></div>
  114. <div
  115. v-if="item.live_room_id === currentLiveRoom?.live_room_id"
  116. class="triangle"
  117. ></div>
  118. <div class="txt">{{ item.live_room?.name }}</div>
  119. </div>
  120. </div>
  121. <div
  122. v-else
  123. class="none"
  124. >
  125. 当前没有在线的直播间
  126. </div>
  127. </div>
  128. </div>
  129. </div>
  130. <div class="area-container">
  131. <div class="area-item">
  132. <div class="title">推荐直播</div>
  133. <div class="live-room-list">
  134. <div
  135. v-for="(iten, indey) in otherLiveRoomList"
  136. :key="indey"
  137. class="live-room"
  138. @click="
  139. joinRoom({
  140. isFlv: false,
  141. roomId: iten.live_room?.id!,
  142. })
  143. "
  144. >
  145. <div
  146. class="cover"
  147. :style="{
  148. backgroundImage: `url('${
  149. iten?.live_room?.cover_img || iten?.user?.avatar
  150. }')`,
  151. }"
  152. >
  153. <div
  154. v-if="iten?.live_room?.cdn === 1"
  155. class="cdn-ico"
  156. >
  157. <div class="txt">CDN</div>
  158. </div>
  159. <div class="txt">{{ iten?.user?.username }}</div>
  160. </div>
  161. <div class="desc">{{ iten?.live_room?.name }}</div>
  162. </div>
  163. <div
  164. v-if="!otherLiveRoomList.length"
  165. class="null"
  166. >
  167. 暂无数据
  168. </div>
  169. </div>
  170. </div>
  171. </div>
  172. <div class="foot">*部分内容来源网络,如有侵权,请联系我删除~</div>
  173. </div>
  174. </template>
  175. <script lang="ts" setup>
  176. import { isMobile } from 'billd-utils';
  177. import { onMounted, ref } from 'vue';
  178. import { useRoute, useRouter } from 'vue-router';
  179. import { fetchLiveList } from '@/api/live';
  180. import { usePull } from '@/hooks/use-pull';
  181. import { ILive, LiveRoomTypeEnum, liveTypeEnum } from '@/interface';
  182. import { routerName } from '@/router';
  183. const route = useRoute();
  184. const router = useRouter();
  185. const canvasRef = ref<Element>();
  186. const showControls = ref(false);
  187. const topNums = ref(6);
  188. const topLiveRoomList = ref<ILive[]>([]);
  189. const otherLiveRoomList = ref<ILive[]>([]);
  190. const currentLiveRoom = ref<ILive>();
  191. const localVideoRef = ref<HTMLVideoElement[]>([]);
  192. const { handleHlsPlay, videoLoading } = usePull({
  193. localVideoRef,
  194. canvasRef,
  195. liveType: route.query.liveType as liveTypeEnum,
  196. isSRS: [
  197. liveTypeEnum.srsWebrtcPull,
  198. liveTypeEnum.srsFlvPull,
  199. liveTypeEnum.srsHlsPull,
  200. ].includes(route.query.liveType as liveTypeEnum),
  201. });
  202. function changeLiveRoom(item: ILive) {
  203. if (item.id === currentLiveRoom.value?.id) return;
  204. currentLiveRoom.value = item;
  205. canvasRef.value?.childNodes?.forEach((item) => {
  206. item.remove();
  207. });
  208. if (
  209. [
  210. LiveRoomTypeEnum.user_srs,
  211. LiveRoomTypeEnum.user_obs,
  212. LiveRoomTypeEnum.system,
  213. ].includes(item.live_room?.type!)
  214. ) {
  215. handleHlsPlay(item.live_room?.hls_url!);
  216. }
  217. }
  218. async function getLiveRoomList() {
  219. try {
  220. const res = await fetchLiveList({
  221. orderName: 'created_at',
  222. orderBy: 'desc',
  223. });
  224. if (res.code === 200) {
  225. topLiveRoomList.value = res.data.rows.slice(0, topNums.value);
  226. otherLiveRoomList.value = res.data.rows.slice(topNums.value);
  227. if (res.data.total) {
  228. currentLiveRoom.value = topLiveRoomList.value[0];
  229. if (
  230. [
  231. LiveRoomTypeEnum.user_srs,
  232. LiveRoomTypeEnum.user_obs,
  233. LiveRoomTypeEnum.system,
  234. ].includes(currentLiveRoom.value?.live_room?.type!)
  235. ) {
  236. handleHlsPlay(currentLiveRoom.value.live_room?.hls_url!);
  237. }
  238. }
  239. }
  240. } catch (error) {
  241. console.log(error);
  242. }
  243. }
  244. onMounted(() => {
  245. getLiveRoomList();
  246. });
  247. function joinRtcRoom() {
  248. if (currentLiveRoom.value?.live_room?.type === LiveRoomTypeEnum.user_srs) {
  249. router.push({
  250. name: routerName.pull,
  251. params: {
  252. roomId: currentLiveRoom.value.live_room_id,
  253. },
  254. query: {
  255. liveType: liveTypeEnum.srsWebrtcPull,
  256. },
  257. });
  258. } else {
  259. router.push({
  260. name: routerName.pull,
  261. params: {
  262. roomId: currentLiveRoom.value?.live_room_id,
  263. },
  264. query: {
  265. liveType: liveTypeEnum.webrtcPull,
  266. },
  267. });
  268. }
  269. }
  270. function joinRoom(data: { roomId: number; isFlv: boolean }) {
  271. router.push({
  272. name: routerName.pull,
  273. params: { roomId: data.roomId },
  274. query: {
  275. liveType: data.isFlv ? liveTypeEnum.srsFlvPull : liveTypeEnum.srsHlsPull,
  276. },
  277. });
  278. }
  279. </script>
  280. <style lang="scss" scoped>
  281. .home-wrap {
  282. .play-container {
  283. position: relative;
  284. .bg {
  285. position: absolute;
  286. top: 0;
  287. right: 0;
  288. left: 0;
  289. z-index: -1;
  290. width: 100%;
  291. height: 100%;
  292. background-color: papayawhip;
  293. background-position: center;
  294. background-repeat: no-repeat;
  295. }
  296. .container {
  297. display: flex;
  298. box-sizing: border-box;
  299. margin: 0 auto;
  300. padding: 15px 0;
  301. justify-content: center;
  302. height: calc($w-1100 / $video-ratio);
  303. .left {
  304. position: relative;
  305. display: inline-block;
  306. overflow: hidden;
  307. box-sizing: border-box;
  308. flex-shrink: 0;
  309. margin-right: 20px;
  310. width: $w-1100;
  311. height: 100%;
  312. border-radius: 4px;
  313. background-color: rgba($color: #000000, $alpha: 0.3);
  314. @extend %coverBg;
  315. .cdn-ico {
  316. position: absolute;
  317. top: -9px;
  318. right: -10px;
  319. z-index: 2;
  320. width: 70px;
  321. height: 32px;
  322. background-color: #f87c48;
  323. color: white;
  324. transform: rotate(45deg);
  325. transform-origin: bottom;
  326. .txt {
  327. margin-top: 11px;
  328. margin-left: 20px;
  329. background-image: initial !important;
  330. font-size: 14px;
  331. }
  332. }
  333. .cover {
  334. position: absolute;
  335. background-position: center center;
  336. background-size: cover;
  337. filter: blur(10px);
  338. inset: 0;
  339. }
  340. :deep(canvas) {
  341. position: absolute;
  342. top: 0;
  343. left: 50%;
  344. width: 100%;
  345. height: 100%;
  346. transform: translate(-50%);
  347. user-select: none;
  348. }
  349. :deep(video) {
  350. position: absolute;
  351. top: 0;
  352. left: 50%;
  353. width: 100%;
  354. height: 100%;
  355. transform: translate(-50%);
  356. user-select: none;
  357. }
  358. .controls {
  359. display: none;
  360. }
  361. &:hover {
  362. .join-btn {
  363. display: inline-flex !important;
  364. }
  365. }
  366. .join-btn {
  367. position: absolute;
  368. top: 50%;
  369. left: 50%;
  370. z-index: 20;
  371. display: none;
  372. align-items: center;
  373. align-items: center;
  374. justify-content: center;
  375. box-sizing: border-box;
  376. width: 80%;
  377. transform: translate(-50%, -50%);
  378. .btn {
  379. padding: 14px 26px;
  380. border: 2px solid rgba($color: $theme-color-gold, $alpha: 0.5);
  381. border-radius: 6px;
  382. background-color: rgba(0, 0, 0, 0.3);
  383. color: $theme-color-gold;
  384. font-size: 16px;
  385. cursor: pointer;
  386. &:hover {
  387. background-color: $theme-color-gold;
  388. color: white;
  389. }
  390. &.webrtc {
  391. margin-right: 10px;
  392. }
  393. &.flv {
  394. margin-right: 10px;
  395. }
  396. }
  397. }
  398. }
  399. .right {
  400. flex-shrink: 0;
  401. display: inline-block;
  402. overflow: scroll;
  403. box-sizing: border-box;
  404. padding: 12px 10px;
  405. height: 100%;
  406. border-radius: 4px;
  407. background-color: rgba($color: #000000, $alpha: 0.3);
  408. @extend %hideScrollbar;
  409. .list {
  410. .item {
  411. position: relative;
  412. box-sizing: border-box;
  413. margin-bottom: 10px;
  414. width: 200px;
  415. height: 110px;
  416. border-radius: 4px;
  417. background-color: rgba($color: #000000, $alpha: 0.3);
  418. cursor: pointer;
  419. @extend %coverBg;
  420. &:last-child {
  421. margin-bottom: 0;
  422. }
  423. .border {
  424. position: absolute;
  425. top: 0;
  426. right: 0;
  427. bottom: 0;
  428. left: 0;
  429. z-index: 1;
  430. border: 2px solid $theme-color-gold;
  431. border-radius: 4px;
  432. }
  433. .triangle {
  434. position: absolute;
  435. top: 50%;
  436. left: 0;
  437. display: inline-block;
  438. border: 5px solid transparent;
  439. border-right-color: $theme-color-gold;
  440. transform: translate(-100%, -50%);
  441. }
  442. &.active {
  443. &::before {
  444. background-color: transparent;
  445. }
  446. }
  447. &:hover {
  448. &::before {
  449. background-color: transparent;
  450. }
  451. }
  452. &::before {
  453. position: absolute;
  454. display: block;
  455. width: 100%;
  456. height: 100%;
  457. border-radius: 4px;
  458. background-color: rgba(0, 0, 0, 0.4);
  459. content: '';
  460. transition: all cubic-bezier(0.22, 0.58, 0.12, 0.98) 0.4s;
  461. }
  462. .txt {
  463. position: absolute;
  464. bottom: 0;
  465. left: 0;
  466. box-sizing: border-box;
  467. padding: 4px 8px;
  468. width: 100%;
  469. border-radius: 0 0 4px 4px;
  470. background-image: linear-gradient(
  471. -180deg,
  472. rgba(0, 0, 0, 0),
  473. rgba(0, 0, 0, 0.6)
  474. );
  475. color: white;
  476. text-align: initial;
  477. font-size: 13px;
  478. @extend %singleEllipsis;
  479. }
  480. }
  481. }
  482. .none {
  483. width: 200px;
  484. color: white;
  485. text-align: center;
  486. font-size: 14px;
  487. }
  488. }
  489. }
  490. }
  491. .area-container {
  492. margin: 10px auto;
  493. width: $w-1350;
  494. box-sizing: border-box;
  495. .area-item {
  496. .title {
  497. padding: 10px 0;
  498. font-size: 26px;
  499. }
  500. .live-room {
  501. display: inline-block;
  502. margin-right: 32px;
  503. margin-bottom: 10px;
  504. width: 300px;
  505. cursor: pointer;
  506. .cover {
  507. position: relative;
  508. overflow: hidden;
  509. width: 100%;
  510. height: 150px;
  511. border-radius: 8px;
  512. background-position: center center;
  513. background-size: cover;
  514. .cdn-ico {
  515. position: absolute;
  516. top: -10px;
  517. right: -10px;
  518. z-index: 2;
  519. width: 70px;
  520. height: 28px;
  521. background-color: #f87c48;
  522. color: white;
  523. transform: rotate(45deg);
  524. transform-origin: bottom;
  525. .txt {
  526. margin-left: 18px;
  527. background-image: initial !important;
  528. font-size: 13px;
  529. }
  530. }
  531. .txt {
  532. position: absolute;
  533. bottom: 0;
  534. left: 0;
  535. box-sizing: border-box;
  536. padding: 4px 8px;
  537. width: 100%;
  538. border-radius: 0 0 4px 4px;
  539. background-image: linear-gradient(
  540. -180deg,
  541. rgba(0, 0, 0, 0),
  542. rgba(0, 0, 0, 0.6)
  543. );
  544. color: white;
  545. text-align: initial;
  546. font-size: 13px;
  547. @extend %singleEllipsis;
  548. }
  549. }
  550. .desc {
  551. margin-top: 4px;
  552. font-size: 14px;
  553. @extend %singleEllipsis;
  554. }
  555. }
  556. }
  557. }
  558. .foot {
  559. margin-top: 10px;
  560. text-align: center;
  561. }
  562. }
  563. // 屏幕宽度小于1330的时候
  564. @media screen and (max-width: 1330px) {
  565. .home-wrap {
  566. .play-container {
  567. .container {
  568. height: calc($w-900 / $video-ratio);
  569. .left {
  570. width: $w-900;
  571. }
  572. .right {
  573. }
  574. }
  575. }
  576. .area-container {
  577. width: $w-1150;
  578. }
  579. }
  580. }
  581. </style>