use-push.ts 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. import { getRandomString, windowReload } from 'billd-utils';
  2. import { onMounted, onUnmounted, ref, watch } from 'vue';
  3. import { useRoute, useRouter } from 'vue-router';
  4. import {
  5. fetchCreateUserLiveRoom,
  6. fetchUserHasLiveRoom,
  7. } from '@/api/userLiveRoom';
  8. import { DanmuMsgTypeEnum, WsMessageMsgIsFileEnum } from '@/interface';
  9. import { useAppStore } from '@/store/app';
  10. import { useNetworkStore } from '@/store/network';
  11. import { useUserStore } from '@/store/user';
  12. import { ILiveRoom } from '@/types/ILiveRoom';
  13. import {
  14. WsConnectStatusEnum,
  15. WsMessageType,
  16. WsMsgTypeEnum,
  17. WsMsrBlobType,
  18. WsRoomNoLiveType,
  19. } from '@/types/websocket';
  20. import { createVideo, generateBase64 } from '@/utils';
  21. import { handleMaxFramerate } from '@/utils/network/webRTC';
  22. import { commentAuthTip, loginTip } from './use-login';
  23. import { useTip } from './use-tip';
  24. import { useWebsocket } from './use-websocket';
  25. export function usePush() {
  26. const route = useRoute();
  27. const router = useRouter();
  28. const appStore = useAppStore();
  29. const userStore = useUserStore();
  30. const networkStore = useNetworkStore();
  31. const roomId = ref('');
  32. const roomName = ref('');
  33. const danmuStr = ref('');
  34. const liveRoomInfo = ref<ILiveRoom>();
  35. const localStream = ref<MediaStream>();
  36. const videoElArr = ref<HTMLVideoElement[]>([]);
  37. const msgIsFile = ref<WsMessageMsgIsFileEnum>(WsMessageMsgIsFileEnum.no);
  38. const {
  39. roomLiving,
  40. initWs,
  41. handleStartLive,
  42. handleSendGetLiveUser,
  43. connectStatus,
  44. mySocketId,
  45. canvasVideoStream,
  46. lastCoverImg,
  47. liveUserList,
  48. damuList,
  49. currentMaxFramerate,
  50. currentMaxBitrate,
  51. currentResolutionRatio,
  52. } = useWebsocket();
  53. onMounted(() => {
  54. if (!loginTip()) return;
  55. });
  56. onUnmounted(() => {
  57. closeWs();
  58. closeRtc();
  59. });
  60. function closeWs() {
  61. networkStore.wsMap.forEach((ws) => {
  62. networkStore.removeWs(ws.roomId);
  63. });
  64. }
  65. function closeRtc() {
  66. networkStore.rtcMap.forEach((rtc) => {
  67. networkStore.removeRtc(rtc.receiver);
  68. });
  69. }
  70. watch(
  71. () => currentResolutionRatio.value,
  72. (newVal) => {
  73. networkStore.rtcMap.forEach(async (rtc) => {
  74. const res = await rtc.setResolutionRatio(newVal);
  75. if (res === 1) {
  76. window.$message.success('切换分辨率成功!');
  77. } else {
  78. window.$message.success('切换分辨率失败!');
  79. }
  80. });
  81. }
  82. );
  83. watch(
  84. () => currentMaxBitrate.value,
  85. (newVal) => {
  86. networkStore.rtcMap.forEach(async (rtc) => {
  87. const res = await rtc.setMaxBitrate(newVal);
  88. if (res === 1) {
  89. window.$message.success('切换码率成功!');
  90. } else {
  91. window.$message.success('切换码率失败!');
  92. }
  93. });
  94. }
  95. );
  96. watch(
  97. () => localStream.value,
  98. (stream) => {
  99. console.log('localStream变了');
  100. console.log('音频轨:', stream?.getAudioTracks());
  101. console.log('视频轨:', stream?.getVideoTracks());
  102. videoElArr.value.forEach((dom) => {
  103. dom.remove();
  104. });
  105. stream?.getVideoTracks().forEach((track) => {
  106. console.log('视频轨enabled:', track.id, track.enabled);
  107. const video = createVideo({});
  108. video.setAttribute('track-id', track.id);
  109. video.srcObject = new MediaStream([track]);
  110. videoElArr.value.push(video);
  111. });
  112. stream?.getAudioTracks().forEach((track) => {
  113. console.log('音频轨enabled:', track.id, track.enabled);
  114. const video = createVideo({});
  115. video.setAttribute('track-id', track.id);
  116. video.srcObject = new MediaStream([track]);
  117. videoElArr.value.push(video);
  118. });
  119. },
  120. { deep: true }
  121. );
  122. watch(
  123. () => userStore.userInfo,
  124. async (newVal) => {
  125. if (newVal) {
  126. const res = await handleUserHasLiveRoom();
  127. if (!res) {
  128. await useTip({ content: '你还没有直播间,是否立即开通?' });
  129. await handleCreateUserLiveRoom();
  130. } else {
  131. roomName.value = liveRoomInfo.value?.name || '';
  132. roomId.value = `${liveRoomInfo.value?.id || ''}`;
  133. connectWs();
  134. }
  135. }
  136. },
  137. { immediate: true }
  138. );
  139. async function handleUserHasLiveRoom() {
  140. const res = await fetchUserHasLiveRoom(userStore.userInfo?.id!);
  141. if (res.code === 200 && res.data) {
  142. liveRoomInfo.value = res.data.live_room;
  143. router.push({
  144. query: { ...route.query, roomId: liveRoomInfo.value?.id },
  145. });
  146. return true;
  147. }
  148. return false;
  149. }
  150. async function handleCreateUserLiveRoom() {
  151. try {
  152. const res = await fetchCreateUserLiveRoom();
  153. if (res.code === 200) {
  154. window.$message.success('开通直播间成功!');
  155. setTimeout(() => {
  156. windowReload();
  157. }, 500);
  158. }
  159. } catch (error) {
  160. console.log(error);
  161. }
  162. }
  163. function connectWs() {
  164. initWs({
  165. isRemoteDesk: false,
  166. isAnchor: true,
  167. roomId: roomId.value,
  168. currentMaxBitrate: currentMaxBitrate.value,
  169. currentMaxFramerate: currentMaxFramerate.value,
  170. currentResolutionRatio: currentResolutionRatio.value,
  171. });
  172. }
  173. async function startLive({ type, msrDelay, msrMaxDelay }) {
  174. if (!loginTip()) return;
  175. const flag = await handleUserHasLiveRoom();
  176. if (!flag) {
  177. await useTip({ content: '你还没有直播间,是否立即开通?' });
  178. await handleCreateUserLiveRoom();
  179. return;
  180. }
  181. if (!roomNameIsOk()) {
  182. return;
  183. }
  184. if (connectStatus.value !== WsConnectStatusEnum.connect) {
  185. window.$message.warning('websocket未连接');
  186. return;
  187. }
  188. roomLiving.value = true;
  189. const el = appStore.allTrack.find((item) => {
  190. if (item.video === 1) {
  191. return true;
  192. }
  193. });
  194. if (el) {
  195. const res1 = videoElArr.value.find(
  196. (item) => item.getAttribute('track-id') === el.track?.id
  197. );
  198. if (res1) {
  199. // canvas推流的话,不需要再设置预览图了
  200. if (!canvasVideoStream.value) {
  201. lastCoverImg.value = generateBase64(res1);
  202. }
  203. }
  204. }
  205. handleMaxFramerate({
  206. stream: canvasVideoStream.value!,
  207. height: currentResolutionRatio.value,
  208. frameRate: currentMaxFramerate.value,
  209. });
  210. handleStartLive({
  211. name: roomName.value,
  212. type,
  213. msrDelay,
  214. msrMaxDelay,
  215. });
  216. }
  217. /** 结束直播 */
  218. function endLive() {
  219. roomLiving.value = false;
  220. localStream.value = undefined;
  221. const instance = networkStore.wsMap.get(roomId.value);
  222. if (instance) {
  223. instance.send<WsRoomNoLiveType['data']>({
  224. requestId: getRandomString(8),
  225. msgType: WsMsgTypeEnum.roomNoLive,
  226. data: {
  227. live_room: appStore.liveRoomInfo!,
  228. },
  229. });
  230. }
  231. closeRtc();
  232. }
  233. function sendBlob(data: { blob; blobId: string; delay; max_delay }) {
  234. const instance = networkStore.wsMap.get(roomId.value);
  235. if (instance) {
  236. instance.send<WsMsrBlobType['data']>({
  237. requestId: getRandomString(8),
  238. msgType: WsMsgTypeEnum.msrBlob,
  239. data: {
  240. live_room_id: Number(roomId.value),
  241. blob: data.blob,
  242. blob_id: data.blobId,
  243. delay: data.delay,
  244. max_delay: data.max_delay,
  245. },
  246. });
  247. }
  248. }
  249. function roomNameIsOk() {
  250. if (!roomName.value.length) {
  251. window.$message.warning('请输入房间名!');
  252. return false;
  253. }
  254. if (roomName.value.length < 3 || roomName.value.length > 20) {
  255. window.$message.warning('房间名要求3-20个字符!');
  256. return false;
  257. }
  258. return true;
  259. }
  260. function keydownDanmu(event: KeyboardEvent) {
  261. const key = event.key.toLowerCase();
  262. if (key === 'enter') {
  263. event.preventDefault();
  264. sendDanmu();
  265. }
  266. }
  267. function confirmRoomName() {
  268. if (!roomNameIsOk()) return;
  269. }
  270. function sendDanmu() {
  271. if (!loginTip()) {
  272. return;
  273. }
  274. if (!commentAuthTip()) {
  275. return;
  276. }
  277. if (!danmuStr.value.length) {
  278. window.$message.warning('请输入弹幕内容!');
  279. return;
  280. }
  281. const instance = networkStore.wsMap.get(roomId.value);
  282. if (!instance) {
  283. window.$message.error('还没开播,不能发送弹幕!');
  284. return;
  285. }
  286. instance.send<WsMessageType['data']>({
  287. requestId: getRandomString(8),
  288. msgType: WsMsgTypeEnum.message,
  289. data: {
  290. socket_id: '',
  291. msg: danmuStr.value,
  292. msgType: DanmuMsgTypeEnum.danmu,
  293. live_room_id: Number(roomId.value),
  294. msgIsFile: msgIsFile.value,
  295. send_msg_time: +new Date(),
  296. user_agent: navigator.userAgent,
  297. },
  298. });
  299. // damuList.value.push({
  300. // user_agent: navigator.userAgent,
  301. // live_room_id: Number(roomId.value),
  302. // socket_id: mySocketId.value,
  303. // msgType: DanmuMsgTypeEnum.danmu,
  304. // msg: danmuStr.value,
  305. // userInfo: userStore.userInfo!,
  306. // msgIsFile: msgIsFile.value,
  307. // send_msg_time: +new Date(),
  308. // });
  309. danmuStr.value = '';
  310. }
  311. return {
  312. confirmRoomName,
  313. startLive,
  314. endLive,
  315. sendDanmu,
  316. keydownDanmu,
  317. sendBlob,
  318. handleSendGetLiveUser,
  319. roomId,
  320. msgIsFile,
  321. mySocketId,
  322. lastCoverImg,
  323. localStream,
  324. canvasVideoStream,
  325. roomLiving,
  326. currentResolutionRatio,
  327. currentMaxBitrate,
  328. currentMaxFramerate,
  329. danmuStr,
  330. roomName,
  331. damuList,
  332. liveUserList,
  333. liveRoomInfo,
  334. };
  335. }