use-pull.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. import { onUnmounted, ref, watch } from 'vue';
  2. import { useRoute } from 'vue-router';
  3. import { commentAuthTip, loginTip } from '@/hooks/use-login';
  4. import { useFlvPlay, useHlsPlay } from '@/hooks/use-play';
  5. import { useSrsWs } from '@/hooks/use-srs-ws';
  6. import {
  7. DanmuMsgTypeEnum,
  8. IDanmu,
  9. ILiveRoom,
  10. LiveLineEnum,
  11. LiveRoomTypeEnum,
  12. } from '@/interface';
  13. import { WsMessageType, WsMsgTypeEnum } from '@/interface-ws';
  14. import { useAppStore } from '@/store/app';
  15. import { usePiniaCacheStore } from '@/store/cache';
  16. import { useNetworkStore } from '@/store/network';
  17. import { useUserStore } from '@/store/user';
  18. import { createVideo, videoToCanvas } from '@/utils';
  19. export function usePull() {
  20. const route = useRoute();
  21. const userStore = useUserStore();
  22. const networkStore = useNetworkStore();
  23. const cacheStore = usePiniaCacheStore();
  24. const appStore = useAppStore();
  25. const roomId = ref(route.params.roomId as string);
  26. const localStream = ref<MediaStream>();
  27. const danmuStr = ref('');
  28. const msgIsFile = ref(false);
  29. const autoplayVal = ref(false);
  30. const videoLoading = ref(false);
  31. const isPlaying = ref(false);
  32. const flvurl = ref('');
  33. const hlsurl = ref('');
  34. const videoHeight = ref();
  35. const sidebarList = ref<
  36. {
  37. socketId: string;
  38. }[]
  39. >([]);
  40. const videoElArr = ref<HTMLVideoElement[]>([]);
  41. const remoteVideo = ref<HTMLElement[]>([]);
  42. const {
  43. isPull,
  44. mySocketId,
  45. initSrsWs,
  46. roomLiving,
  47. anchorInfo,
  48. liveUserList,
  49. damuList,
  50. } = useSrsWs();
  51. isPull.value = true;
  52. const { flvVideoEl, flvIsPlaying, startFlvPlay, destroyFlv } = useFlvPlay();
  53. const { hlsVideoEl, hlsIsPlaying, startHlsPlay, destroyHls } = useHlsPlay();
  54. const stopDrawingArr = ref<any[]>([]);
  55. onUnmounted(() => {
  56. handleStopDrawing();
  57. });
  58. function handleStopDrawing() {
  59. destroyFlv();
  60. destroyHls();
  61. stopDrawingArr.value.forEach((cb) => cb());
  62. stopDrawingArr.value = [];
  63. remoteVideo.value.forEach((el) => el.remove());
  64. remoteVideo.value = [];
  65. }
  66. watch(hlsVideoEl, () => {
  67. stopDrawingArr.value = [];
  68. stopDrawingArr.value.forEach((cb) => cb());
  69. const { canvas, stopDrawing } = videoToCanvas({
  70. videoEl: hlsVideoEl.value!,
  71. resize: ({ w, h }) => {
  72. videoHeight.value = `${w}x${h}`;
  73. },
  74. });
  75. stopDrawingArr.value.push(stopDrawing);
  76. remoteVideo.value.push(canvas);
  77. videoLoading.value = false;
  78. });
  79. function handleHlsPlay(url: string) {
  80. console.log('handleHlsPlay', url);
  81. handleStopDrawing();
  82. videoLoading.value = true;
  83. appStore.setLiveLine(LiveLineEnum.hls);
  84. startHlsPlay({
  85. hlsurl: url,
  86. });
  87. }
  88. watch(flvVideoEl, () => {
  89. stopDrawingArr.value = [];
  90. stopDrawingArr.value.forEach((cb) => cb());
  91. const { canvas, stopDrawing } = videoToCanvas({
  92. videoEl: flvVideoEl.value!,
  93. resize: ({ w, h }) => {
  94. videoHeight.value = `${w}x${h}`;
  95. },
  96. });
  97. stopDrawingArr.value.push(stopDrawing);
  98. remoteVideo.value.push(canvas);
  99. videoLoading.value = false;
  100. });
  101. function handleFlvPlay() {
  102. console.log('handleFlvPlay');
  103. handleStopDrawing();
  104. videoLoading.value = true;
  105. appStore.setLiveLine(LiveLineEnum.flv);
  106. startFlvPlay({
  107. flvurl: flvurl.value,
  108. });
  109. }
  110. function handlePlay(data: ILiveRoom) {
  111. roomLiving.value = true;
  112. flvurl.value = data.flv_url!;
  113. hlsurl.value = data.hls_url!;
  114. switch (data.type) {
  115. case LiveRoomTypeEnum.user_srs:
  116. if (appStore.liveLine === LiveLineEnum.flv) {
  117. handleFlvPlay();
  118. } else if (appStore.liveLine === LiveLineEnum.hls) {
  119. handleHlsPlay(data.hls_url!);
  120. }
  121. break;
  122. case LiveRoomTypeEnum.user_obs:
  123. if (appStore.liveLine === LiveLineEnum.flv) {
  124. handleFlvPlay();
  125. } else if (appStore.liveLine === LiveLineEnum.hls) {
  126. handleHlsPlay(data.hls_url!);
  127. }
  128. break;
  129. case LiveRoomTypeEnum.user_msr:
  130. if (appStore.liveLine === LiveLineEnum.flv) {
  131. handleFlvPlay();
  132. } else if (appStore.liveLine === LiveLineEnum.hls) {
  133. handleHlsPlay(data.hls_url!);
  134. }
  135. break;
  136. case LiveRoomTypeEnum.system:
  137. if (appStore.liveLine === LiveLineEnum.flv) {
  138. handleFlvPlay();
  139. } else if (appStore.liveLine === LiveLineEnum.hls) {
  140. handleHlsPlay(data.hls_url!);
  141. }
  142. break;
  143. case LiveRoomTypeEnum.user_wertc:
  144. appStore.setLiveLine(LiveLineEnum.rtc);
  145. break;
  146. }
  147. }
  148. watch(
  149. () => roomLiving.value,
  150. (val) => {
  151. if (val) {
  152. if (appStore.liveRoomInfo?.type !== LiveRoomTypeEnum.user_wertc) {
  153. handlePlay(appStore.liveRoomInfo!);
  154. }
  155. } else {
  156. closeRtc();
  157. handleStopDrawing();
  158. }
  159. }
  160. );
  161. watch(
  162. () => appStore.liveLine,
  163. (newVal) => {
  164. console.log('liveLine变了', newVal);
  165. if (!roomLiving.value) {
  166. return;
  167. }
  168. switch (newVal) {
  169. case LiveLineEnum.flv:
  170. handleFlvPlay();
  171. break;
  172. case LiveLineEnum.hls:
  173. handleHlsPlay(hlsurl.value);
  174. break;
  175. case LiveLineEnum.rtc:
  176. break;
  177. }
  178. }
  179. );
  180. watch(
  181. () => cacheStore.muted,
  182. (newVal) => {
  183. videoElArr.value.forEach((el) => {
  184. el.muted = newVal;
  185. });
  186. if (!newVal) {
  187. cacheStore.setVolume(cacheStore.volume || appStore.normalVolume);
  188. } else {
  189. cacheStore.setVolume(0);
  190. }
  191. }
  192. );
  193. watch(
  194. () => cacheStore.volume,
  195. (newVal) => {
  196. videoElArr.value.forEach((el) => {
  197. el.volume = newVal / 100;
  198. });
  199. if (!newVal) {
  200. cacheStore.setMuted(true);
  201. } else {
  202. cacheStore.setMuted(false);
  203. }
  204. }
  205. );
  206. watch(
  207. () => appStore.play,
  208. (newVal) => {
  209. videoElArr.value.forEach((el) => {
  210. if (newVal) {
  211. el.play();
  212. } else {
  213. el.pause();
  214. }
  215. });
  216. }
  217. );
  218. watch(
  219. () => hlsIsPlaying.value,
  220. (newVal) => {
  221. isPlaying.value = newVal;
  222. }
  223. );
  224. watch(
  225. () => flvIsPlaying.value,
  226. (newVal) => {
  227. isPlaying.value = newVal;
  228. }
  229. );
  230. watch(
  231. () => networkStore.rtcMap,
  232. (newVal) => {
  233. if (appStore.liveRoomInfo?.type === LiveRoomTypeEnum.user_wertc) {
  234. newVal.forEach((item) => {
  235. videoLoading.value = false;
  236. const { canvas } = videoToCanvas({
  237. videoEl: item.videoEl,
  238. resize: ({ w, h }) => {
  239. videoHeight.value = `${w}x${h}`;
  240. },
  241. });
  242. videoElArr.value.push(item.videoEl);
  243. remoteVideo.value.push(canvas);
  244. });
  245. }
  246. },
  247. {
  248. deep: true,
  249. immediate: true,
  250. }
  251. );
  252. watch(
  253. () => localStream.value,
  254. (val) => {
  255. if (val) {
  256. console.log('localStream变了');
  257. console.log('音频轨:', val?.getAudioTracks());
  258. console.log('视频轨:', val?.getVideoTracks());
  259. if (appStore.liveRoomInfo?.type === LiveRoomTypeEnum.user_wertc) {
  260. videoElArr.value.forEach((dom) => {
  261. dom.remove();
  262. });
  263. val?.getVideoTracks().forEach((track) => {
  264. console.log('视频轨enabled:', track.id, track.enabled);
  265. const video = createVideo({});
  266. video.setAttribute('track-id', track.id);
  267. video.srcObject = new MediaStream([track]);
  268. remoteVideo.value.push(video);
  269. videoElArr.value.push(video);
  270. });
  271. val?.getAudioTracks().forEach((track) => {
  272. console.log('音频轨enabled:', track.id, track.enabled);
  273. const video = createVideo({});
  274. video.setAttribute('track-id', track.id);
  275. video.srcObject = new MediaStream([track]);
  276. remoteVideo.value.push(video);
  277. videoElArr.value.push(video);
  278. });
  279. videoLoading.value = false;
  280. } else {
  281. videoElArr.value.forEach((dom) => {
  282. dom.remove();
  283. });
  284. val?.getVideoTracks().forEach((track) => {
  285. console.log('视频轨enabled:', track.id, track.enabled);
  286. const video = createVideo({});
  287. video.setAttribute('track-id', track.id);
  288. video.srcObject = new MediaStream([track]);
  289. remoteVideo.value.push(video);
  290. videoElArr.value.push(video);
  291. });
  292. val?.getAudioTracks().forEach((track) => {
  293. console.log('音频轨enabled:', track.id, track.enabled);
  294. const video = createVideo({});
  295. video.setAttribute('track-id', track.id);
  296. video.srcObject = new MediaStream([track]);
  297. remoteVideo.value.push(video);
  298. videoElArr.value.push(video);
  299. });
  300. videoLoading.value = false;
  301. }
  302. } else {
  303. videoElArr.value?.forEach((item) => {
  304. item.remove();
  305. });
  306. }
  307. },
  308. { deep: true }
  309. );
  310. watch(
  311. [
  312. () => userStore.userInfo,
  313. () => networkStore.wsMap.get(roomId.value)?.socketIo?.connected,
  314. ],
  315. ([userInfo, connected]) => {
  316. if (userInfo && connected) {
  317. const instance = networkStore.wsMap.get(roomId.value);
  318. if (!instance) return;
  319. }
  320. }
  321. );
  322. function initPull(autolay = true) {
  323. autoplayVal.value = autolay;
  324. if (autoplayVal.value) {
  325. videoLoading.value = true;
  326. }
  327. initSrsWs({
  328. roomId: roomId.value,
  329. isAnchor: false,
  330. });
  331. }
  332. function closeWs() {
  333. const instance = networkStore.wsMap.get(roomId.value);
  334. instance?.close();
  335. }
  336. function closeRtc() {
  337. networkStore.rtcMap.forEach((rtc) => {
  338. rtc.close();
  339. networkStore.removeRtc(rtc.roomId);
  340. });
  341. }
  342. function addVideo() {
  343. sidebarList.value.push({ socketId: mySocketId.value });
  344. }
  345. function keydownDanmu(event: KeyboardEvent) {
  346. const key = event.key.toLowerCase();
  347. if (key === 'enter') {
  348. event.preventDefault();
  349. sendDanmu();
  350. }
  351. }
  352. function sendDanmu() {
  353. if (!loginTip()) {
  354. return;
  355. }
  356. if (!commentAuthTip()) {
  357. return;
  358. }
  359. if (!danmuStr.value.trim().length) {
  360. window.$message.warning('请输入弹幕内容!');
  361. return;
  362. }
  363. const instance = networkStore.wsMap.get(roomId.value);
  364. if (!instance) return;
  365. const danmu: IDanmu = {
  366. socket_id: mySocketId.value,
  367. userInfo: userStore.userInfo!,
  368. msgType: DanmuMsgTypeEnum.danmu,
  369. msg: danmuStr.value,
  370. msgIsFile: msgIsFile.value,
  371. };
  372. const messageData: WsMessageType['data'] = {
  373. msg: danmuStr.value,
  374. msgType: DanmuMsgTypeEnum.danmu,
  375. live_room_id: Number(roomId.value),
  376. msgIsFile: msgIsFile.value,
  377. };
  378. instance.send({
  379. msgType: WsMsgTypeEnum.message,
  380. data: messageData,
  381. });
  382. damuList.value.push(danmu);
  383. danmuStr.value = '';
  384. }
  385. return {
  386. handlePlay,
  387. handleStopDrawing,
  388. initPull,
  389. closeWs,
  390. closeRtc,
  391. keydownDanmu,
  392. sendDanmu,
  393. addVideo,
  394. isPlaying,
  395. msgIsFile,
  396. mySocketId,
  397. videoHeight,
  398. remoteVideo,
  399. roomLiving,
  400. autoplayVal,
  401. videoLoading,
  402. damuList,
  403. liveUserList,
  404. sidebarList,
  405. danmuStr,
  406. anchorInfo,
  407. };
  408. }