use-push.ts 10 KB

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