index.vue 17 KB

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