use-pull.ts 10 KB

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