index.vue 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620
  1. <template>
  2. <div
  3. class="pull-wrap"
  4. :class="{ isPageFull: appStore.videoControlsValue.pageFullMode }"
  5. >
  6. <div class="bg-img-wrap">
  7. <video
  8. v-if="configVideo && configVideo !== ''"
  9. class="bg-video"
  10. :src="configVideo"
  11. muted
  12. autoplay
  13. loop
  14. ></video>
  15. <div
  16. v-if="configBg && configBg !== ''"
  17. :style="{
  18. backgroundImage: `url(${configBg})`,
  19. }"
  20. ></div>
  21. </div>
  22. <div class="left">
  23. <div
  24. ref="topRef"
  25. class="head"
  26. >
  27. <div class="info">
  28. <div
  29. class="avatar"
  30. :style="{
  31. backgroundImage: `url(${anchorInfo?.avatar})`,
  32. }"
  33. @click="
  34. router.push({
  35. name: routerName.my,
  36. params: { userId: anchorInfo?.id },
  37. })
  38. "
  39. ></div>
  40. <div class="detail">
  41. <div class="top">
  42. <div class="name">{{ anchorInfo?.username }}</div>
  43. <div class="follow">
  44. <div class="f-left">+关注</div>
  45. <div class="f-right">666</div>
  46. </div>
  47. <span v-if="NODE_ENV === 'development'">
  48. {{ liveRoomTypeEnumMap[appStore.liveRoomInfo?.type!] }}:{{
  49. mySocketId
  50. }}
  51. </span>
  52. <div
  53. class="rtc-info"
  54. v-if="
  55. [
  56. LiveRoomTypeEnum.wertc_live,
  57. LiveRoomTypeEnum.wertc_meeting_one,
  58. LiveRoomTypeEnum.wertc_meeting_two,
  59. ].includes(appStore.liveRoomInfo?.type!)
  60. "
  61. >
  62. <div class="item">延迟:{{ rtcRtt || '-' }}</div>
  63. <div class="item">丢包:{{ rtcLoss || '-' }}</div>
  64. <div class="item">帧率:{{ rtcFps || '-' }}</div>
  65. <div class="item">发送码率:{{ rtcBytesSent || '-' }}</div>
  66. <div class="item">接收码率:{{ rtcBytesReceived || '-' }}</div>
  67. </div>
  68. </div>
  69. <div class="bottom">
  70. <div
  71. class="desc"
  72. v-if="appStore.liveRoomInfo?.desc?.length"
  73. >
  74. <FloatTip
  75. :txt="appStore.liveRoomInfo?.desc"
  76. :max-len="20"
  77. ></FloatTip>
  78. </div>
  79. <span
  80. class="area"
  81. @click="
  82. router.push({
  83. name: routerName.area,
  84. query: { id: appStore.liveRoomInfo?.areas?.[0]?.id },
  85. })
  86. "
  87. >
  88. {{ appStore.liveRoomInfo?.areas?.[0]?.name }}
  89. </span>
  90. </div>
  91. </div>
  92. </div>
  93. <div
  94. class="other"
  95. @click="handlePk"
  96. >
  97. <div class="top">
  98. <div class="item">666人看过</div>
  99. <div class="item">666点赞</div>
  100. <div class="item">当前在线:{{ liveUserList.length }}人</div>
  101. </div>
  102. <div class="bottom">
  103. <n-popover
  104. placement="bottom"
  105. trigger="hover"
  106. >
  107. <template #trigger>
  108. <div class="tag">礼物成就</div>
  109. </template>
  110. <div class="popover-list">
  111. <template v-if="giftGroupList.length">
  112. <div
  113. class="item"
  114. v-for="(item, index) in giftGroupList"
  115. :key="index"
  116. >
  117. <div
  118. class="ico"
  119. :style="{
  120. backgroundImage: `url(${item.goods?.cover})`,
  121. }"
  122. ></div>
  123. <div class="nums">x{{ item.nums }}</div>
  124. </div>
  125. </template>
  126. <span v-else>{{ t('common.nonedata') }}</span>
  127. </div>
  128. </n-popover>
  129. <div class="tag">人气榜</div>
  130. </div>
  131. </div>
  132. </div>
  133. <div
  134. class="video-wrap"
  135. v-loading="videoLoading"
  136. >
  137. <div
  138. class="no-live"
  139. v-if="!roomLiving"
  140. >
  141. 主播还没开播~
  142. </div>
  143. <div
  144. class="cover"
  145. :style="{
  146. backgroundImage: `url(${
  147. appStore.liveRoomInfo?.cover_img || anchorInfo?.avatar
  148. })`,
  149. }"
  150. ></div>
  151. <div
  152. class="remote-video"
  153. ref="remoteVideoRef"
  154. ></div>
  155. <VideoControls
  156. :resolution="videoResolution"
  157. @refresh="handleRefresh"
  158. @full-screen="handleFullScreen"
  159. @picture-in-picture="hanldePictureInPicture"
  160. :control="appStore.videoControls"
  161. ></VideoControls>
  162. </div>
  163. <div
  164. ref="bottomRef"
  165. v-loading="giftLoading"
  166. class="gift-list"
  167. >
  168. <div
  169. v-for="(item, index) in giftGoodsList"
  170. :key="index"
  171. class="item"
  172. @click="handlePay(item)"
  173. >
  174. <div
  175. class="ico"
  176. :style="{
  177. backgroundImage: `url(${item.cover})`,
  178. }"
  179. >
  180. <div
  181. v-if="item.badge"
  182. class="badge"
  183. :style="{ backgroundColor: item.badge_bg }"
  184. >
  185. <span class="txt">{{ item.badge }}</span>
  186. </div>
  187. </div>
  188. <div class="name">{{ item.name }}</div>
  189. <div class="price">¥{{ formatMoney(item.price) }}</div>
  190. </div>
  191. <div
  192. class="item"
  193. @click="handleRecharge"
  194. >
  195. <div class="ico wallet"></div>
  196. <div class="name">
  197. 余额:{{ formatMoney(userStore.userInfo?.wallet?.balance) }}元
  198. </div>
  199. <div class="price">立即充值</div>
  200. </div>
  201. </div>
  202. <div class="ad-wrap-b">
  203. <!-- live-拉流页面广告位2 -->
  204. <ins
  205. class="adsbygoogle"
  206. style="display: block"
  207. data-ad-client="ca-pub-6064454674933772"
  208. data-ad-slot="2315064038"
  209. data-ad-format="auto"
  210. data-full-width-responsive="true"
  211. ></ins>
  212. </div>
  213. </div>
  214. <div class="right">
  215. <div class="rank-wrap">
  216. <div class="tab">
  217. <span>在线用户</span>
  218. <span> | </span>
  219. <span>排行榜</span>
  220. </div>
  221. <div class="user-list">
  222. <div
  223. v-for="(item, index) in liveUserList"
  224. :key="index"
  225. class="item"
  226. >
  227. <div
  228. class="info"
  229. v-if="item.value?.userInfo"
  230. @click="jumpProfile(item.value.userInfo.id!)"
  231. >
  232. <div
  233. class="avatar"
  234. :style="{
  235. backgroundImage: `url(${item.value?.userInfo?.avatar})`,
  236. }"
  237. ></div>
  238. <div class="username">
  239. {{ item.value.userInfo.username }}
  240. </div>
  241. </div>
  242. <div
  243. class="info"
  244. v-else
  245. >
  246. <div class="avatar"></div>
  247. <div class="username">
  248. {{ item.value?.socketId }}
  249. </div>
  250. </div>
  251. </div>
  252. </div>
  253. </div>
  254. <div
  255. ref="danmuListRef"
  256. class="danmu-list"
  257. >
  258. <div
  259. v-for="(item, index) in damuList"
  260. :key="index"
  261. class="item"
  262. >
  263. <template v-if="item.data.msg_type === DanmuMsgTypeEnum.reward">
  264. <div class="reward">
  265. <span>[{{ formatTimeHour(item.time) }}]</span>
  266. <span>
  267. {{ item.user_info?.username }} 打赏了{{ item.data.content }}!
  268. </span>
  269. </div>
  270. </template>
  271. <template v-if="item.data.msg_type === DanmuMsgTypeEnum.danmu">
  272. <span class="time">[{{ formatTimeHour(item.time) }}]</span>
  273. <span class="name">
  274. <span
  275. v-if="
  276. item.user_info && userStore.userInfo?.id === item.user_info.id
  277. "
  278. >
  279. <span>{{ item.user_info.username }}</span>
  280. <span>
  281. [{{ item.user_info.roles?.map((v) => v.role_name).join() }}]
  282. </span>
  283. </span>
  284. <Dropdown
  285. trigger="click"
  286. positon="left"
  287. v-else-if="item.user_info"
  288. >
  289. <template #btn>
  290. <span>{{ item.user_info.username }}</span>
  291. <span>
  292. [{{ item.user_info.roles?.map((v) => v.role_name).join() }}]
  293. </span>
  294. </template>
  295. <template #list>
  296. <div class="list">
  297. <div class="item">{{ item.user_info.username }}</div>
  298. <div
  299. class="item operator"
  300. @click="
  301. handleDisableSpeakingUser({
  302. userId: item.user_info.id,
  303. socketId: item.socket_id,
  304. })
  305. "
  306. >
  307. 禁言该用户
  308. </div>
  309. <div
  310. class="item operator"
  311. @click="
  312. handleRestoreSpeakingUser({
  313. userId: item.user_info.id,
  314. socketId: item.socket_id,
  315. })
  316. "
  317. >
  318. 解除禁言该用户
  319. </div>
  320. <div
  321. class="item operator"
  322. @click="handleKickUser"
  323. >
  324. 踢掉该用户
  325. </div>
  326. </div>
  327. </template>
  328. </Dropdown>
  329. <span v-else>
  330. <span>{{ item.socket_id }}</span>
  331. <span>[游客]</span>
  332. </span>
  333. </span>
  334. <span>:</span>
  335. <span
  336. class="msg"
  337. v-if="item.data.content_type === WsMessageContentTypeEnum.txt"
  338. >
  339. {{ item.data.content }}
  340. </span>
  341. <div
  342. class="msg img"
  343. v-else
  344. >
  345. <img
  346. :src="item.data.content"
  347. alt=""
  348. @load="handleScrollTop"
  349. />
  350. </div>
  351. </template>
  352. <template
  353. v-else-if="item.data.msg_type === DanmuMsgTypeEnum.otherJoin"
  354. >
  355. <span class="name system">系统通知:</span>
  356. <span class="msg">
  357. {{ item.user_info?.username || item.socket_id }}进入直播!
  358. </span>
  359. </template>
  360. <template
  361. v-else-if="item.data.msg_type === DanmuMsgTypeEnum.userLeaved"
  362. >
  363. <span class="name system">系统通知:</span>
  364. <span class="msg">
  365. {{ item.user_info?.username || item.socket_id }}离开直播!
  366. </span>
  367. </template>
  368. </div>
  369. </div>
  370. <div
  371. class="send-msg"
  372. v-loading="msgLoading"
  373. >
  374. <div
  375. class="disableSpeaking"
  376. v-if="appStore.disableSpeaking.get(appStore.liveRoomInfo?.id!)"
  377. >
  378. <div class="bg"></div>
  379. <span class="txt">
  380. 你被禁言了({{
  381. appStore.disableSpeaking.get(appStore.liveRoomInfo?.id!)
  382. }})
  383. </span>
  384. </div>
  385. <div class="control">
  386. <div
  387. class="emoji-list"
  388. v-if="showEmoji"
  389. >
  390. <div
  391. class="item"
  392. v-for="(item, index) in emojiArray"
  393. :key="index"
  394. @click="handlePushStr(item)"
  395. >
  396. {{ item }}
  397. </div>
  398. </div>
  399. <div
  400. class="ico face"
  401. title="表情"
  402. @click="handleEmoji"
  403. ></div>
  404. <div
  405. class="ico img"
  406. title="图片"
  407. @click="mockClick"
  408. >
  409. <input
  410. ref="uploadRef"
  411. type="file"
  412. class="input-upload"
  413. accept=".webp,.png,.jpg,.jpeg,.gif"
  414. @change="uploadChange"
  415. />
  416. </div>
  417. </div>
  418. <textarea
  419. ref="danmuIptRef"
  420. :placeholder="'发个弹幕吧~'"
  421. v-model="danmuStr"
  422. class="ipt"
  423. @keydown="keydownDanmu"
  424. ></textarea>
  425. <div
  426. class="btn"
  427. @click="handleSendDanmu"
  428. >
  429. 发送
  430. </div>
  431. </div>
  432. </div>
  433. <div class="ad-wrap-a">
  434. <!-- live-拉流页面广告位1 -->
  435. <ins
  436. class="adsbygoogle"
  437. style="display: block"
  438. data-ad-client="ca-pub-6064454674933772"
  439. data-ad-slot="6397310081"
  440. data-ad-format="auto"
  441. data-full-width-responsive="true"
  442. ></ins>
  443. </div>
  444. <RechargeCpt
  445. :show="showRecharge"
  446. @close="(v) => (showRecharge = v)"
  447. ></RechargeCpt>
  448. </div>
  449. </template>
  450. <script lang="ts" setup>
  451. import { getRandomString, openToTarget } from 'billd-utils';
  452. import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
  453. import { useI18n } from 'vue-i18n';
  454. import { useRoute } from 'vue-router';
  455. import {
  456. fetchLiveBilibiliPlayUrl,
  457. fetchLiveBilibiliRoomGetInfo,
  458. } from '@/api/bilibili';
  459. import {
  460. fetchGiftGroupList,
  461. fetchGiftRecordCreate,
  462. fetchGiftRecordList,
  463. } from '@/api/giftRecord';
  464. import { fetchGoodsList } from '@/api/goods';
  465. import { fetchLiveRoomOnlineUser } from '@/api/live';
  466. import { fetchGetWsMessageList } from '@/api/wsMessage';
  467. import { liveRoomTypeEnumMap, QINIU_RESOURCE } from '@/constant';
  468. import { emojiArray } from '@/emoji';
  469. import { commentAuthTip, loginTip } from '@/hooks/use-login';
  470. import { useFullScreen, usePictureInPicture } from '@/hooks/use-play';
  471. import { usePull } from '@/hooks/use-pull';
  472. import { useQiniuJsUpload } from '@/hooks/use-upload';
  473. import {
  474. DanmuMsgTypeEnum,
  475. GiftRecordStatusEnum,
  476. GoodsTypeEnum,
  477. IGiftRecord,
  478. IGoods,
  479. LiveLineEnum,
  480. LiveRenderEnum,
  481. WsMessageContentTypeEnum,
  482. WsMessageMsgIsFileEnum,
  483. WsMessageMsgIsShowEnum,
  484. WsMessageMsgIsVerifyEnum,
  485. } from '@/interface';
  486. import router, { routerName } from '@/router';
  487. import { useAppStore } from '@/store/app';
  488. import { useNetworkStore } from '@/store/network';
  489. import { useUserStore } from '@/store/user';
  490. import { LiveRoomTypeEnum } from '@/types/ILiveRoom';
  491. import { WsDisableSpeakingType, WsMsgTypeEnum } from '@/types/websocket';
  492. import { formatMoney, formatTimeHour, handleUserMedia } from '@/utils';
  493. import { initAdsbygoogle } from '@/utils/google-ad';
  494. import { NODE_ENV } from 'script/constant';
  495. import RechargeCpt from './recharge/index.vue';
  496. const route = useRoute();
  497. const userStore = useUserStore();
  498. const appStore = useAppStore();
  499. const networkStore = useNetworkStore();
  500. const { t } = useI18n();
  501. const roomId = ref(route.params.roomId as string);
  502. const configBg = ref();
  503. const configVideo = ref();
  504. const giftGoodsList = ref<IGoods[]>([]);
  505. const giftRecordList = ref<IGiftRecord[]>([]);
  506. const giftGroupList = ref<Array<IGiftRecord & { nums: number }>>([]);
  507. const height = ref(0);
  508. const giftLoading = ref(false);
  509. const showRecharge = ref(false);
  510. const showEmoji = ref(false);
  511. const msgLoading = ref(false);
  512. const topRef = ref<HTMLDivElement>();
  513. const bottomRef = ref<HTMLDivElement>();
  514. const danmuListRef = ref<HTMLDivElement>();
  515. const remoteVideoRef = ref<HTMLDivElement>();
  516. const uploadRef = ref<HTMLInputElement>();
  517. const danmuIptRef = ref<HTMLTextAreaElement>();
  518. const loopGetLiveUserTimer = ref();
  519. const isBilibili = ref(false);
  520. const {
  521. initWs,
  522. initPull,
  523. closeWs,
  524. closeRtc,
  525. keydownDanmu,
  526. sendDanmuReward,
  527. sendDanmuTxt,
  528. sendDanmuImg,
  529. handlePlay,
  530. videoWrapRef,
  531. msgIsFile,
  532. mySocketId,
  533. videoResolution,
  534. videoLoading,
  535. roomLiving,
  536. damuList,
  537. liveUserList,
  538. danmuStr,
  539. anchorInfo,
  540. } = usePull(roomId.value);
  541. const rtcRtt = computed(() => {
  542. const arr: any[] = [];
  543. networkStore.rtcMap.forEach((rtc) => {
  544. arr.push(`${rtc.rtt}ms`);
  545. });
  546. return arr.join();
  547. });
  548. const rtcLoss = computed(() => {
  549. const arr: any[] = [];
  550. networkStore.rtcMap.forEach((rtc) => {
  551. arr.push(`${Number(rtc.loss.toFixed(2))}%`);
  552. });
  553. return arr.join();
  554. });
  555. const rtcFps = computed(() => {
  556. const arr: any[] = [];
  557. networkStore.rtcMap.forEach((rtc) => {
  558. arr.push(`${Number(rtc.inboundFps.toFixed(2))}`);
  559. });
  560. return arr.join();
  561. });
  562. const rtcBytesSent = computed(() => {
  563. const arr: any[] = [];
  564. networkStore.rtcMap.forEach((rtc) => {
  565. arr.push(`${Number(rtc.bytesSent.toFixed(0))}kb/s`);
  566. });
  567. return arr.join();
  568. });
  569. const rtcBytesReceived = computed(() => {
  570. const arr: any[] = [];
  571. networkStore.rtcMap.forEach((rtc) => {
  572. arr.push(`${Number(rtc.bytesReceived.toFixed(0))}kb/s`);
  573. });
  574. return arr.join();
  575. });
  576. onMounted(() => {
  577. initAdsbygoogle();
  578. appStore.videoControls.fps = true;
  579. appStore.videoControls.fullMode = true;
  580. appStore.videoControls.kbs = true;
  581. appStore.videoControls.line = true;
  582. appStore.videoControls.networkSpeed = true;
  583. appStore.videoControls.pageFullMode = true;
  584. appStore.videoControls.pipMode = true;
  585. appStore.videoControls.renderMode = LiveRenderEnum.video;
  586. appStore.videoControls.resolution = true;
  587. appStore.videoControls.speed = true;
  588. videoWrapRef.value = remoteVideoRef.value;
  589. setTimeout(() => {
  590. scrollTo(0, 0);
  591. }, 100);
  592. handleHistoryMsg();
  593. getGoodsList();
  594. if (topRef.value && bottomRef.value && remoteVideoRef.value) {
  595. const res =
  596. bottomRef.value.getBoundingClientRect().top -
  597. (topRef.value.getBoundingClientRect().top +
  598. topRef.value.getBoundingClientRect().height);
  599. height.value = res;
  600. }
  601. getBg();
  602. if (route.query.is_bilibili !== '1') {
  603. isBilibili.value = false;
  604. initPull({});
  605. } else {
  606. initWs({
  607. roomId: roomId.value,
  608. isRemoteDesk: false,
  609. isBilibili: true,
  610. isAnchor: false,
  611. });
  612. isBilibili.value = true;
  613. handleBilibil();
  614. }
  615. getGiftRecord();
  616. getGiftGroupList();
  617. handleSendGetLiveUser(Number(roomId.value));
  618. });
  619. onUnmounted(() => {
  620. closeWs();
  621. closeRtc();
  622. clearInterval(loopGetLiveUserTimer.value);
  623. });
  624. async function handleBilibil() {
  625. if (route.query.is_bilibili === '1') {
  626. const flv = await fetchLiveBilibiliPlayUrl({
  627. cid: route.params.roomId,
  628. platform: 'web',
  629. });
  630. const hls = await fetchLiveBilibiliPlayUrl({
  631. cid: route.params.roomId,
  632. platform: 'h5',
  633. });
  634. const roomInfo = await fetchLiveBilibiliRoomGetInfo({
  635. room_id: route.params.roomId,
  636. });
  637. console.log('roomInfo', roomInfo);
  638. console.log(flv?.data?.data?.durl?.[0].url, 'flv');
  639. console.log(hls?.data?.data?.durl?.[0].url, 'hls');
  640. roomLiving.value = true;
  641. appStore.liveLine = LiveLineEnum.hls;
  642. anchorInfo.value = {
  643. avatar: roomInfo?.data?.data?.user_cover,
  644. username: roomInfo?.data?.data?.title,
  645. };
  646. appStore.liveRoomInfo = {
  647. type: LiveRoomTypeEnum.system,
  648. flv_url: flv?.data?.data?.durl?.[0].url,
  649. hls_url: hls?.data?.data?.durl?.[0].url,
  650. areas: [{ name: roomInfo?.data?.data?.area_name }],
  651. desc: roomInfo?.data?.data?.description,
  652. };
  653. handleRefresh();
  654. }
  655. }
  656. function handleSendGetLiveUser(liveRoomId: number) {
  657. async function main() {
  658. const res = await fetchLiveRoomOnlineUser({ live_room_id: liveRoomId });
  659. if (res.code === 200) {
  660. liveUserList.value = res.data;
  661. }
  662. }
  663. main();
  664. loopGetLiveUserTimer.value = setInterval(() => {
  665. main();
  666. }, 1000 * 3);
  667. }
  668. function handleSendDanmu() {
  669. sendDanmuTxt(danmuStr.value);
  670. }
  671. async function getGiftGroupList() {
  672. const res = await fetchGiftGroupList({
  673. live_room_id: Number(roomId.value),
  674. status: GiftRecordStatusEnum.ok,
  675. });
  676. if (res.code === 200) {
  677. // @ts-ignore
  678. giftGroupList.value = res.data.rows.map((item) => {
  679. try {
  680. const json = JSON.parse(item.goods_snapshot!);
  681. item.goods = json;
  682. } catch (error) {
  683. console.log(error);
  684. }
  685. return item;
  686. });
  687. }
  688. }
  689. async function getGiftRecord() {
  690. const res = await fetchGiftRecordList({
  691. live_room_id: Number(roomId.value),
  692. status: GiftRecordStatusEnum.ok,
  693. });
  694. if (res.code === 200) {
  695. giftRecordList.value = res.data.rows;
  696. }
  697. }
  698. async function handleHistoryMsg() {
  699. try {
  700. const res = await fetchGetWsMessageList({
  701. nowPage: 1,
  702. pageSize: appStore.liveRoomInfo?.history_msg_total || 10,
  703. orderName: 'created_at',
  704. orderBy: 'desc',
  705. live_room_id: Number(roomId.value),
  706. is_show: WsMessageMsgIsShowEnum.yes,
  707. is_verify: WsMessageMsgIsVerifyEnum.yes,
  708. });
  709. if (res.code === 200) {
  710. res.data.rows.forEach((v) => {
  711. damuList.value.unshift({
  712. request_id: '',
  713. socket_id: '',
  714. time: v.send_msg_time!,
  715. user_agent: v.user_agent!,
  716. user_info: v.user,
  717. data: {
  718. live_room_id: v.live_room_id!,
  719. msg_id: v.id!,
  720. content: v.content!,
  721. content_type: v.content_type!,
  722. msg_type: v.msg_type!,
  723. redbag_send_id: v.redbag_send_id,
  724. },
  725. });
  726. });
  727. if (
  728. appStore.liveRoomInfo?.system_msg &&
  729. appStore.liveRoomInfo?.system_msg !== ''
  730. ) {
  731. damuList.value.push({
  732. request_id: '',
  733. socket_id: '',
  734. time: +new Date(),
  735. user_agent: navigator.userAgent,
  736. data: {
  737. live_room_id: Number(roomId.value),
  738. msg_id: -1,
  739. content: appStore.liveRoomInfo.system_msg,
  740. content_type: WsMessageContentTypeEnum.txt,
  741. msg_type: DanmuMsgTypeEnum.system,
  742. },
  743. });
  744. }
  745. }
  746. } catch (error) {
  747. console.log(error);
  748. }
  749. }
  750. async function handlePk() {
  751. const stream = await handleUserMedia({ video: true, audio: true });
  752. const rtc = networkStore.rtcMap.get(`${roomId.value}`)!;
  753. if (rtc?.peerConnection) {
  754. rtc.peerConnection.onnegotiationneeded = () => {
  755. console.log('onnegotiationneeded');
  756. };
  757. stream?.getTracks().forEach((track) => {
  758. rtc.peerConnection?.addTrack(track, stream);
  759. });
  760. }
  761. }
  762. watch(
  763. () => damuList.value.length,
  764. () => {
  765. setTimeout(() => {
  766. handleScrollTop();
  767. }, 0);
  768. }
  769. );
  770. watch(
  771. () => appStore.liveRoomInfo,
  772. () => {
  773. getBg();
  774. },
  775. {
  776. deep: true,
  777. }
  778. );
  779. /**
  780. * 禁言用户逻辑:
  781. * 主播开播了,可以禁言所有看自己直播的用户
  782. * 使用redis存储记录,key是主播直播间id,value是禁言用户id
  783. */
  784. function handleDisableSpeakingUser({ socketId, userId }) {
  785. console.log('handleDisableSpeakingUser');
  786. const instance = networkStore.wsMap.get(roomId.value);
  787. if (instance) {
  788. instance.send<WsDisableSpeakingType['data']>({
  789. requestId: getRandomString(8),
  790. msgType: WsMsgTypeEnum.disableSpeaking,
  791. data: {
  792. socket_id: socketId,
  793. user_id: userId,
  794. live_room_id: Number(roomId.value),
  795. duration: 60 * 5,
  796. },
  797. });
  798. }
  799. }
  800. function handleRestoreSpeakingUser({ socketId, userId }) {
  801. console.log('handleRestoreSpeakingUser');
  802. const instance = networkStore.wsMap.get(roomId.value);
  803. if (instance) {
  804. instance.send<WsDisableSpeakingType['data']>({
  805. requestId: getRandomString(8),
  806. msgType: WsMsgTypeEnum.disableSpeaking,
  807. data: {
  808. socket_id: socketId,
  809. user_id: userId,
  810. live_room_id: Number(roomId.value),
  811. restore: true,
  812. },
  813. });
  814. }
  815. }
  816. function jumpProfile(userId: number) {
  817. const url = router.resolve({
  818. name: routerName.my,
  819. params: { userId },
  820. });
  821. openToTarget(url.href);
  822. }
  823. function handleKickUser() {
  824. console.log('handleKickUser');
  825. }
  826. function getBg() {
  827. try {
  828. const reg = /.+\.mp4$/g;
  829. const url = appStore.liveRoomInfo?.bg_img;
  830. if (url) {
  831. if (reg.exec(url)) {
  832. configVideo.value = url;
  833. } else {
  834. configBg.value = url;
  835. }
  836. }
  837. } catch (error) {
  838. console.log(error);
  839. }
  840. }
  841. function handlePushStr(str) {
  842. danmuStr.value += str;
  843. showEmoji.value = false;
  844. danmuIptRef.value?.focus();
  845. }
  846. function handleEmoji() {
  847. if (!loginTip()) {
  848. return;
  849. }
  850. if (!commentAuthTip()) {
  851. return;
  852. }
  853. showEmoji.value = !showEmoji.value;
  854. }
  855. function mockClick() {
  856. if (!loginTip()) {
  857. return;
  858. }
  859. if (!commentAuthTip()) {
  860. return;
  861. }
  862. uploadRef.value?.click();
  863. }
  864. async function uploadChange() {
  865. const fileList = uploadRef.value?.files;
  866. if (fileList?.length) {
  867. try {
  868. msgLoading.value = true;
  869. msgIsFile.value = WsMessageMsgIsFileEnum.yes;
  870. const res = await useQiniuJsUpload({
  871. prefix: QINIU_RESOURCE.prefix['billd-live/msg-image/'],
  872. file: fileList[0],
  873. });
  874. if (res?.resultUrl) {
  875. sendDanmuImg(res.resultUrl || '错误图片');
  876. }
  877. } catch (error) {
  878. console.log(error);
  879. } finally {
  880. msgIsFile.value = WsMessageMsgIsFileEnum.no;
  881. msgLoading.value = false;
  882. if (uploadRef.value) {
  883. uploadRef.value.value = '';
  884. }
  885. }
  886. }
  887. }
  888. async function handlePay(item: IGoods) {
  889. if (!loginTip()) {
  890. return;
  891. }
  892. const res = await fetchGiftRecordCreate({
  893. goodsId: item.id!,
  894. goodsNums: 1,
  895. liveRoomId: Number(roomId.value),
  896. isBilibili: false,
  897. });
  898. if (res.code === 200) {
  899. window.$message.success('打赏成功!');
  900. sendDanmuReward(item.name || '');
  901. }
  902. userStore.updateMyWallet();
  903. getGiftGroupList();
  904. }
  905. function handleFullScreen() {
  906. const el = remoteVideoRef.value?.childNodes[0];
  907. if (el) {
  908. useFullScreen(el);
  909. }
  910. }
  911. async function hanldePictureInPicture() {
  912. if (appStore.videoControlsValue.pipMode) {
  913. document.exitPictureInPicture();
  914. } else {
  915. const el = remoteVideoRef.value?.childNodes[0];
  916. if (el && remoteVideoRef.value) {
  917. await usePictureInPicture(el, remoteVideoRef.value);
  918. }
  919. }
  920. }
  921. function handleRefresh() {
  922. if (appStore.liveRoomInfo) {
  923. handlePlay(appStore.liveRoomInfo);
  924. }
  925. }
  926. async function getGoodsList() {
  927. try {
  928. giftLoading.value = true;
  929. const res = await fetchGoodsList({
  930. type: GoodsTypeEnum.gift,
  931. orderName: 'created_at',
  932. orderBy: 'desc',
  933. });
  934. if (res.code === 200) {
  935. giftGoodsList.value = res.data.rows;
  936. }
  937. } catch (error) {
  938. console.log(error);
  939. } finally {
  940. giftLoading.value = false;
  941. }
  942. }
  943. function handleRecharge() {
  944. if (!loginTip()) return;
  945. showRecharge.value = true;
  946. }
  947. function handleScrollTop() {
  948. if (danmuListRef.value) {
  949. danmuListRef.value.scrollTop = danmuListRef.value.scrollHeight + 10000;
  950. }
  951. }
  952. </script>
  953. <style lang="scss" scoped>
  954. .popover-list {
  955. display: flex;
  956. align-items: center;
  957. flex-wrap: wrap;
  958. width: 140px;
  959. .item {
  960. margin-top: 10px;
  961. margin-right: 10px;
  962. text-align: center;
  963. &:nth-child(1),
  964. &:nth-child(2),
  965. &:nth-child(3) {
  966. margin-top: 5px;
  967. }
  968. &:nth-child(3n) {
  969. margin-right: 0px;
  970. }
  971. .ico {
  972. position: relative;
  973. width: 40px;
  974. height: 40px;
  975. background-position: center center;
  976. background-size: cover;
  977. background-repeat: no-repeat;
  978. }
  979. .nums {
  980. margin-top: 5px;
  981. color: #18191c;
  982. }
  983. }
  984. }
  985. .pull-wrap {
  986. position: relative;
  987. z-index: 1;
  988. display: flex;
  989. justify-content: space-around;
  990. margin: 15px auto 0;
  991. width: $w-1200;
  992. .bg-img-wrap {
  993. position: absolute;
  994. top: $layout-head-h;
  995. left: 50%;
  996. max-width: 1920px;
  997. max-height: 890px;
  998. width: 100%;
  999. height: 100%;
  1000. background-position: center center;
  1001. background-size: cover;
  1002. background-repeat: no-repeat;
  1003. transform: translateX(-50%);
  1004. .bg-img {
  1005. position: absolute;
  1006. top: 0;
  1007. right: 0;
  1008. left: 0;
  1009. z-index: -1;
  1010. width: 100%;
  1011. height: 100%;
  1012. background-position: center;
  1013. background-size: cover;
  1014. background-repeat: no-repeat;
  1015. }
  1016. .bg-video {
  1017. position: absolute;
  1018. top: 0;
  1019. right: 0;
  1020. left: 0;
  1021. z-index: -1;
  1022. width: 100%;
  1023. height: 100%;
  1024. }
  1025. }
  1026. .left {
  1027. position: relative;
  1028. display: inline-block;
  1029. // overflow: hidden;
  1030. box-sizing: border-box;
  1031. width: $w-900;
  1032. height: 740px;
  1033. border-radius: 6px;
  1034. background-color: $theme-color-papayawhip;
  1035. color: #61666d;
  1036. vertical-align: top;
  1037. .head {
  1038. display: flex;
  1039. justify-content: space-between;
  1040. box-sizing: border-box;
  1041. padding: 10px 20px;
  1042. height: 80px;
  1043. color: #18191c;
  1044. .info {
  1045. display: flex;
  1046. align-items: center;
  1047. text-align: initial;
  1048. .avatar {
  1049. margin-right: 20px;
  1050. width: 50px;
  1051. height: 50px;
  1052. border-radius: 50%;
  1053. cursor: pointer;
  1054. @extend %containBg;
  1055. }
  1056. .detail {
  1057. .top {
  1058. display: flex;
  1059. margin-bottom: 10px;
  1060. .follow {
  1061. display: flex;
  1062. align-items: center;
  1063. margin-right: 10px;
  1064. margin-left: 10px;
  1065. height: 20px;
  1066. border-radius: 12px;
  1067. background-color: $theme-color-gold;
  1068. font-size: 12px;
  1069. .f-left {
  1070. display: flex;
  1071. align-items: center;
  1072. padding: 0 10px;
  1073. color: white;
  1074. cursor: pointer;
  1075. }
  1076. .f-right {
  1077. display: flex;
  1078. align-items: center;
  1079. padding: 0 10px;
  1080. height: 100%;
  1081. border-radius: 0 12px 12px 0;
  1082. background-color: #e3e5e7;
  1083. }
  1084. }
  1085. .rtc-info {
  1086. display: flex;
  1087. align-items: center;
  1088. .item {
  1089. margin-right: 10px;
  1090. font-size: 14px;
  1091. }
  1092. }
  1093. }
  1094. .bottom {
  1095. display: flex;
  1096. font-size: 14px;
  1097. .area {
  1098. margin: 0 10px;
  1099. color: #9499a0;
  1100. cursor: pointer;
  1101. }
  1102. }
  1103. }
  1104. }
  1105. .other {
  1106. display: flex;
  1107. flex-direction: column;
  1108. justify-content: center;
  1109. font-size: 14px;
  1110. .top {
  1111. display: flex;
  1112. margin-bottom: 10px;
  1113. .item {
  1114. margin-right: 10px;
  1115. }
  1116. }
  1117. .bottom {
  1118. font-size: 12px;
  1119. .tag {
  1120. display: inline-block;
  1121. margin-right: 10px;
  1122. padding: 4px 10px;
  1123. border-radius: 10px;
  1124. background-color: $theme-color-gold;
  1125. color: white;
  1126. text-align: center;
  1127. line-height: 1;
  1128. cursor: pointer;
  1129. }
  1130. }
  1131. }
  1132. }
  1133. .video-wrap {
  1134. position: relative;
  1135. display: flex;
  1136. overflow: hidden;
  1137. align-items: center;
  1138. justify-content: space-between;
  1139. height: calc(100% - 80px - 100px);
  1140. background-color: rgba($color: #000000, $alpha: 0.5);
  1141. .remote-video {
  1142. position: relative;
  1143. width: 100%;
  1144. height: 100%;
  1145. :deep(video) {
  1146. position: absolute;
  1147. top: 50%;
  1148. left: 50%;
  1149. display: block;
  1150. margin: 0 auto;
  1151. height: calc(100% - 80px - 100px);
  1152. transform: translate(-50%, -50%);
  1153. }
  1154. :deep(canvas) {
  1155. position: absolute;
  1156. top: 50%;
  1157. left: 50%;
  1158. display: block;
  1159. margin: 0 auto;
  1160. height: calc(100% - 80px - 100px);
  1161. transform: translate(-50%, -50%);
  1162. }
  1163. }
  1164. .cover {
  1165. position: absolute;
  1166. background-position: center center;
  1167. background-size: cover;
  1168. filter: blur(10px);
  1169. inset: 0;
  1170. }
  1171. .no-live {
  1172. position: absolute;
  1173. top: 50%;
  1174. left: 50%;
  1175. z-index: 20;
  1176. color: white;
  1177. font-size: 28px;
  1178. transform: translate(-50%, -50%);
  1179. }
  1180. }
  1181. .gift-list {
  1182. position: absolute;
  1183. right: 0;
  1184. bottom: 0;
  1185. left: 0;
  1186. display: flex;
  1187. align-items: center;
  1188. justify-content: space-around;
  1189. box-sizing: border-box;
  1190. box-sizing: border-box;
  1191. padding: 5px 0;
  1192. height: 100px;
  1193. > :last-child {
  1194. position: absolute;
  1195. }
  1196. .item {
  1197. display: flex;
  1198. align-items: center;
  1199. flex-direction: column;
  1200. box-sizing: border-box;
  1201. width: 90px;
  1202. height: 88px;
  1203. text-align: center;
  1204. cursor: pointer;
  1205. &:hover {
  1206. background-color: #ebe0ce;
  1207. }
  1208. .ico {
  1209. position: relative;
  1210. margin-top: 12px;
  1211. width: 40px;
  1212. height: 40px;
  1213. background-position: center center;
  1214. background-size: cover;
  1215. background-repeat: no-repeat;
  1216. &.wallet {
  1217. background-image: url('@/assets/img/wallet.webp');
  1218. }
  1219. .badge {
  1220. position: absolute;
  1221. top: -8px;
  1222. right: -10px;
  1223. display: flex;
  1224. align-items: center;
  1225. justify-content: center;
  1226. padding: 2px;
  1227. border-radius: 2px;
  1228. color: white;
  1229. .txt {
  1230. display: inline-block;
  1231. line-height: 1;
  1232. transform-origin: center !important;
  1233. @include minFont(10);
  1234. }
  1235. }
  1236. }
  1237. .name {
  1238. color: #18191c;
  1239. font-size: 12px;
  1240. }
  1241. .price {
  1242. color: #9499a0;
  1243. font-size: 12px;
  1244. }
  1245. }
  1246. }
  1247. .ad-wrap-b {
  1248. position: absolute;
  1249. bottom: -10px;
  1250. left: 0;
  1251. z-index: 999;
  1252. width: 100%;
  1253. // height: 150px;
  1254. border-radius: 10px;
  1255. // background-color: red;
  1256. transform: translateY(100%);
  1257. ins {
  1258. width: 100%;
  1259. height: 100%;
  1260. }
  1261. }
  1262. }
  1263. .right {
  1264. position: relative;
  1265. display: inline-block;
  1266. box-sizing: border-box;
  1267. width: $w-250;
  1268. height: 740px;
  1269. border-radius: 6px;
  1270. background-color: $theme-color-papayawhip;
  1271. color: #9499a0;
  1272. .rank-wrap {
  1273. .tab {
  1274. display: flex;
  1275. align-items: center;
  1276. justify-content: space-evenly;
  1277. height: 30px;
  1278. font-size: 12px;
  1279. }
  1280. .user-list {
  1281. overflow-y: scroll;
  1282. box-sizing: border-box;
  1283. padding: 0 15px;
  1284. height: 100px;
  1285. @extend %customScrollbar;
  1286. .item {
  1287. display: flex;
  1288. align-items: center;
  1289. justify-content: space-between;
  1290. margin-bottom: 10px;
  1291. font-size: 12px;
  1292. .info {
  1293. display: flex;
  1294. align-items: center;
  1295. cursor: pointer;
  1296. .avatar {
  1297. margin-right: 5px;
  1298. width: 25px;
  1299. height: 25px;
  1300. border-radius: 50%;
  1301. @extend %containBg;
  1302. }
  1303. .username {
  1304. color: black;
  1305. }
  1306. }
  1307. }
  1308. }
  1309. }
  1310. .danmu-list {
  1311. overflow-y: scroll;
  1312. box-sizing: border-box;
  1313. padding-top: 4px;
  1314. height: calc(100% - 30px - 100px - 135px);
  1315. background-color: #f6f7f8;
  1316. text-align: initial;
  1317. @extend %customScrollbar;
  1318. .item {
  1319. box-sizing: border-box;
  1320. margin-bottom: 4px;
  1321. padding: 2px 10px;
  1322. white-space: normal;
  1323. word-wrap: break-word;
  1324. font-size: 13px;
  1325. .reward {
  1326. color: $theme-color-gold;
  1327. font-weight: bold;
  1328. }
  1329. .name,
  1330. .time {
  1331. color: #9499a0;
  1332. &.system {
  1333. color: red;
  1334. }
  1335. .dropdown-wrap {
  1336. :deep(.container) {
  1337. width: 120px;
  1338. }
  1339. }
  1340. .list {
  1341. .item {
  1342. &:hover {
  1343. &.operator {
  1344. color: $theme-color-gold;
  1345. cursor: pointer;
  1346. }
  1347. }
  1348. }
  1349. }
  1350. }
  1351. .msg {
  1352. margin-top: 4px;
  1353. color: #61666d;
  1354. &.img {
  1355. img {
  1356. width: 80%;
  1357. }
  1358. }
  1359. }
  1360. }
  1361. }
  1362. .send-msg {
  1363. position: absolute;
  1364. bottom: 0;
  1365. left: 0;
  1366. box-sizing: border-box;
  1367. padding: 2px 10px;
  1368. width: 100%;
  1369. height: 135px;
  1370. .disableSpeaking {
  1371. cursor: no-drop;
  1372. .bg {
  1373. position: absolute !important;
  1374. @extend %maskBg;
  1375. }
  1376. .txt {
  1377. position: absolute;
  1378. top: 50%;
  1379. left: 50%;
  1380. width: 100%;
  1381. text-align: center;
  1382. font-size: 14px;
  1383. transform: translate(-50%, -50%);
  1384. }
  1385. }
  1386. .control {
  1387. display: flex;
  1388. margin: 4px 0;
  1389. .emoji-list {
  1390. position: absolute;
  1391. top: 0;
  1392. right: 0;
  1393. left: 0;
  1394. overflow: scroll;
  1395. box-sizing: border-box;
  1396. padding: 3px;
  1397. padding-right: 0;
  1398. height: 160px;
  1399. background-color: #fff;
  1400. transform: translateY(-100%);
  1401. @extend %customScrollbar;
  1402. .item {
  1403. display: inline-flex;
  1404. align-items: center;
  1405. justify-content: center;
  1406. box-sizing: border-box;
  1407. width: 14%;
  1408. height: 18%;
  1409. border: 1px solid #f8f8f8;
  1410. font-size: 20px;
  1411. cursor: pointer;
  1412. }
  1413. }
  1414. .ico {
  1415. margin-right: 6px;
  1416. width: 24px;
  1417. height: 24px;
  1418. cursor: pointer;
  1419. .input-upload {
  1420. width: 0;
  1421. height: 0;
  1422. opacity: 0;
  1423. }
  1424. &.face {
  1425. @include setBackground('@/assets/img/msg-face.webp');
  1426. }
  1427. &.img {
  1428. @include setBackground('@/assets/img/msg-img.webp');
  1429. }
  1430. }
  1431. }
  1432. .ipt {
  1433. display: block;
  1434. box-sizing: border-box;
  1435. margin: 0 auto;
  1436. padding: 10px;
  1437. width: 100%;
  1438. height: 60px;
  1439. outline: none;
  1440. border: 1px solid hsla(0, 0%, 60%, 0.2);
  1441. border-radius: 4px;
  1442. background-color: #fff;
  1443. font-size: 14px;
  1444. &::placeholder {
  1445. font-size: 13px;
  1446. }
  1447. }
  1448. .btn {
  1449. box-sizing: border-box;
  1450. margin-top: 10px;
  1451. margin-left: auto;
  1452. padding: 4px;
  1453. width: 70px;
  1454. height: 24px;
  1455. border-radius: 4px;
  1456. background-color: $theme-color-gold;
  1457. color: white;
  1458. text-align: center;
  1459. font-size: 12px;
  1460. cursor: pointer;
  1461. }
  1462. }
  1463. }
  1464. .ad-wrap-a {
  1465. position: fixed;
  1466. top: 300px;
  1467. left: 10px;
  1468. z-index: 999;
  1469. width: 250px;
  1470. // height: 150px;
  1471. border-radius: 10px;
  1472. // background-color: red;
  1473. ins {
  1474. width: 100%;
  1475. height: 100%;
  1476. }
  1477. }
  1478. &.isPageFull {
  1479. position: fixed;
  1480. top: 0;
  1481. left: 0;
  1482. z-index: 100;
  1483. justify-content: space-between;
  1484. margin: 0;
  1485. width: 100vw;
  1486. height: 100vh;
  1487. .left {
  1488. width: calc(100vw - 300px);
  1489. height: 100%;
  1490. border-radius: 0;
  1491. .head {
  1492. display: none;
  1493. }
  1494. .video-wrap {
  1495. height: calc(100% - 100px);
  1496. .remote-video {
  1497. :deep(video) {
  1498. max-width: 100%;
  1499. }
  1500. :deep(canvas) {
  1501. max-width: 100%;
  1502. }
  1503. }
  1504. }
  1505. .gift-list {
  1506. background-color: #8ec5fc;
  1507. background-image: linear-gradient(62deg, #8ec5fc 0%, #e0c3fc 100%);
  1508. .item {
  1509. .name {
  1510. color: white;
  1511. }
  1512. .price {
  1513. color: black;
  1514. }
  1515. }
  1516. }
  1517. }
  1518. .right {
  1519. width: 300px;
  1520. height: 100%;
  1521. border-radius: 0;
  1522. .rank-wrap {
  1523. background-color: #8ec5fc;
  1524. background-image: linear-gradient(62deg, #8ec5fc 0%, #e0c3fc 100%);
  1525. }
  1526. .send-msg {
  1527. background-color: #0093e9;
  1528. background-image: linear-gradient(328deg, #0093e9 0%, #80d0c7 100%);
  1529. }
  1530. }
  1531. }
  1532. }
  1533. // 屏幕宽度大于1500的时候
  1534. @media screen and (min-width: $w-1500) {
  1535. .pull-wrap {
  1536. width: $w-1450;
  1537. .left {
  1538. width: $w-1100;
  1539. :deep(video) {
  1540. max-width: $w-1100;
  1541. }
  1542. :deep(canvas) {
  1543. max-width: $w-1100;
  1544. }
  1545. }
  1546. .right {
  1547. width: $w-300;
  1548. }
  1549. }
  1550. }
  1551. </style>