useTRTC.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  1. import consola from 'consola';
  2. import { computed, nextTick, ref, unref } from 'vue';
  3. import TRTC from 'trtc-js-sdk';
  4. import type { LocalStream, Client, RemoteStream } from 'trtc-js-sdk';
  5. import { useRtcStore } from '/@/store/modules/rtc';
  6. // import Dialog from '/@/components/basic/dialog';
  7. import { useI18n } from './useI18n';
  8. import { useMiniApp } from './useMiniApp';
  9. import { useRoom } from './useRoom';
  10. let localClient: Client;
  11. let localStream: LocalStream;
  12. const invitedRemoteStreams = ref<RemoteStream[]>([]);
  13. const muteAudioLeader = ref(false);
  14. const muteVideoLeader = ref(false);
  15. const { isUsingMiniApp } = useMiniApp();
  16. const globalVideoEnable = computed(
  17. () => Number(import.meta.env.VITE_ENABLE_VIDEO) === 1 && unref(isUsingMiniApp),
  18. );
  19. export const checkDevice = async () => {
  20. try {
  21. const rtcStore = useRtcStore();
  22. const microphoneItems = await TRTC.getMicrophones();
  23. console.log('microphoneItems', microphoneItems);
  24. microphoneItems.forEach((item) => {
  25. item['value'] = item.deviceId;
  26. });
  27. if (microphoneItems?.length) {
  28. rtcStore.setAudioDeviceId(
  29. microphoneItems[0].deviceId || microphoneItems[0].groupId || 'default',
  30. );
  31. } else {
  32. rtcStore.setAudioDeviceId('');
  33. }
  34. const camerasItems = await TRTC.getCameras();
  35. console.log('camerasItems', camerasItems);
  36. camerasItems.forEach((item) => {
  37. item['value'] = item.deviceId;
  38. });
  39. if (camerasItems?.length) {
  40. rtcStore.setVideoDeviceId(camerasItems[0].deviceId || camerasItems[0].groupId || 'default');
  41. } else {
  42. rtcStore.setVideoDeviceId('');
  43. }
  44. } catch (error) {
  45. console.log('error', error);
  46. }
  47. };
  48. const checkSystemRequirements = async () => {
  49. const result = await TRTC.checkSystemRequirements();
  50. console.log('result', result);
  51. const isSmallStreamSupported = await TRTC.isSmallStreamSupported();
  52. console.log('isSmallStreamSupported', isSmallStreamSupported);
  53. };
  54. // interface UseRtcSdkType {
  55. // createRTCSocket: () => Promise<void>
  56. // }
  57. async function createLocalStream() {
  58. try {
  59. const rtcStore = useRtcStore();
  60. const enableVideo =
  61. rtcStore.isLeader && rtcStore.videoDeviceId?.length > 0 && unref(globalVideoEnable);
  62. console.warn('enableVideo', enableVideo, unref(globalVideoEnable));
  63. localStream = TRTC.createStream({
  64. userId: rtcStore.userId,
  65. audio: true,
  66. video: enableVideo,
  67. cameraId: rtcStore.videoDeviceId,
  68. microphoneId: rtcStore.audioDeviceId,
  69. });
  70. //开大小流
  71. const isSmallStreamSupported = TRTC.isSmallStreamSupported();
  72. if (isSmallStreamSupported) {
  73. await localClient.enableSmallStream();
  74. localClient.setSmallStreamProfile({
  75. width: 160,
  76. height: 90,
  77. bitrate: 100,
  78. frameRate: 15,
  79. });
  80. } else {
  81. localStream.setVideoProfile('360p');
  82. }
  83. //
  84. await localStream.initialize();
  85. return Promise.resolve();
  86. // if (rtcStore.audioMuted) {
  87. // localStream.muteAudio();
  88. // }
  89. } catch (error) {
  90. console.log(error, 'createStream');
  91. return Promise.reject(error);
  92. }
  93. }
  94. async function createRTCSocket(): Promise<void> {
  95. try {
  96. if (!unref(isUsingMiniApp)) {
  97. const rtcStore = useRtcStore();
  98. await checkSystemRequirements();
  99. await checkDevice();
  100. console.log('createRTCSocket', rtcStore.videoDeviceId);
  101. await handleJoin();
  102. } else {
  103. console.log('小程序关闭rtc入口');
  104. }
  105. } catch (error) {
  106. consola.error({
  107. tag: 'createRTCSocket',
  108. message: error,
  109. });
  110. }
  111. }
  112. async function handleJoin() {
  113. const rtcStore = useRtcStore();
  114. try {
  115. const { getSign } = useRoom();
  116. const res = await getSign(rtcStore.userId);
  117. console.warn('sign', res);
  118. localClient = TRTC.createClient({
  119. mode: 'rtc',
  120. sdkAppId: res.sdkAppId || parseInt(rtcStore.sdkAppId, 10),
  121. userId: rtcStore.userId,
  122. userSig: res.sign || rtcStore.genUserSig,
  123. useStringRoomId: true,
  124. enableAutoPlayDialog: false,
  125. });
  126. installEventHandlers();
  127. await localClient.join({ roomId: rtcStore.roomId });
  128. } catch (error) {
  129. console.error(error, 'error-----------');
  130. }
  131. await createLocalStream();
  132. await handlePublish();
  133. await nextTick();
  134. //
  135. // setTimeout(() => {
  136. const playLocal = () => {
  137. const playId = 'camera_box_' + rtcStore.userId;
  138. localStream
  139. .play(playId)
  140. .then(() => {
  141. consola.info({
  142. message: '本地采集成功!',
  143. tag: 'rtc:audio',
  144. });
  145. rtcStore.setIsRTCJoined(true);
  146. })
  147. .catch((error) => {
  148. consola.info({
  149. message: '本地采集失败!' + error,
  150. tag: 'rtc:audio',
  151. });
  152. setTimeout(() => {
  153. console.log('再次播放');
  154. playLocal();
  155. }, 500);
  156. });
  157. };
  158. playLocal();
  159. // }, 1000);
  160. // if (!rtcStore.isLeader) {
  161. localStream.muteAudio();
  162. console.log('参加者默认-muteAudio');
  163. // }
  164. localStream.on('error', (error) => {
  165. if (error.getCode() === 0x4043) {
  166. // 自动播放受限导致播放失败,此时引导用户点击页面。
  167. // 在点击事件的回调函数中,执行 stream.resume();
  168. const rtcStore = useRtcStore();
  169. const { t } = useI18n();
  170. rtcStore.showBaseDialog(
  171. {
  172. title: t('base.tips'),
  173. desc: t('base.audioPermission'),
  174. okTxt: t('base.confirm'),
  175. closeTxt: t('base.cancel'),
  176. },
  177. () => {
  178. localStream.resume();
  179. },
  180. );
  181. }
  182. });
  183. }
  184. async function handlePublish() {
  185. const rtcStore = useRtcStore();
  186. if (!rtcStore.isJoined) {
  187. return;
  188. }
  189. if (rtcStore.isPublished) {
  190. return;
  191. }
  192. // if (!rtcStore.isLeader) {
  193. // return;
  194. // }
  195. try {
  196. await localClient.unpublish(localStream);
  197. await localClient.publish(localStream);
  198. rtcStore.setIsPublished(true);
  199. } catch (error) {
  200. console.error(error, '---------------handlePublish--------------------');
  201. }
  202. }
  203. // async function handleStartShare() {
  204. // shareClient = new ShareClient({
  205. // sdkAppId: parseInt(store.getters["rtc/sdkAppId"], 10),
  206. // userId: `share${store.getters["rtc/userId"]}`,
  207. // roomId: store.getters["rtc/roomId"],
  208. // secretKey: store.getters["rtc/secretKey"],
  209. // useStringRoomId: true,
  210. // });
  211. // try {
  212. // await shareClient.join();
  213. // await shareClient.publish();
  214. // console.log("Start share screen success");
  215. // store.isShared = true;
  216. // } catch (error) {
  217. // console.error(`Start share error: ${error.message_}`);
  218. // }
  219. // }
  220. async function handleUnpublish() {
  221. const rtcStore = useRtcStore();
  222. if (!rtcStore.isJoined) {
  223. return;
  224. }
  225. if (!rtcStore.isPublished) {
  226. return;
  227. }
  228. try {
  229. await localClient.unpublish(localStream);
  230. // store.commit("rtc/setIsPublished", false);
  231. rtcStore.setIsPublished(false);
  232. } catch (error) {
  233. console.error(error, '-----------handleUnpublish--------------');
  234. }
  235. }
  236. async function handleLeave() {
  237. const rtcStore = useRtcStore();
  238. if (rtcStore.isPublished) {
  239. await handleUnpublish();
  240. }
  241. try {
  242. uninstallEventHandlers();
  243. localClient && (await localClient.leave());
  244. localClient && localClient.destroy();
  245. // localClient = null;
  246. invitedRemoteStreams.value.forEach((item) => {
  247. item.stop();
  248. });
  249. invitedRemoteStreams.value = [];
  250. rtcStore.setVideoDeviceId('');
  251. rtcStore.setAudioDeviceId('');
  252. // store.commit("rtc/setVideoDeviceId", "");
  253. // store.commit("rtc/setAudioDeviceId", "");
  254. if (localStream) {
  255. localStream.stop();
  256. localStream.close();
  257. // localStream = null;
  258. console.log('有执行到这里-------------');
  259. }
  260. } catch (error) {
  261. console.error(error, '-----------handleLeave--------------');
  262. }
  263. }
  264. function installEventHandlers() {
  265. if (!localClient) {
  266. return;
  267. }
  268. localClient.on('error', handleError);
  269. localClient.on('client-banned', handleBanned);
  270. localClient.on('peer-join', handlePeerJoin);
  271. localClient.on('peer-leave', handlePeerLeave);
  272. localClient.on('stream-added', handleStreamAdded);
  273. localClient.on('stream-subscribed', handleStreamSubscribed);
  274. localClient.on('stream-removed', handleStreamRemoved);
  275. localClient.on('stream-updated', handleStreamUpdated);
  276. localClient.on('mute-video', handleMuteVideo);
  277. localClient.on('mute-audio', handleMuteAudio);
  278. localClient.on('unmute-video', handleUnmuteVideo);
  279. localClient.on('unmute-audio', handleUnmuteAudio);
  280. }
  281. function uninstallEventHandlers() {
  282. if (!localClient) {
  283. return;
  284. }
  285. localClient.off('error', handleError);
  286. localClient.off('client-banned', handleBanned);
  287. localClient.off('peer-join', handlePeerJoin);
  288. localClient.off('peer-leave', handlePeerLeave);
  289. localClient.off('stream-added', handleStreamAdded);
  290. localClient.off('stream-subscribed', handleStreamSubscribed);
  291. localClient.off('stream-removed', handleStreamRemoved);
  292. localClient.off('stream-updated', handleStreamUpdated);
  293. localClient.off('mute-video', handleMuteVideo);
  294. localClient.off('mute-audio', handleMuteAudio);
  295. localClient.off('unmute-video', handleUnmuteVideo);
  296. localClient.off('unmute-audio', handleUnmuteAudio);
  297. }
  298. function handleMuteVideo(event) {
  299. console.log(`[${event.userId}] mute video`);
  300. const rtcStore = useRtcStore();
  301. const roomLeader = rtcStore.getRoomLeader();
  302. if (event.userId === roomLeader?.UserId) {
  303. muteVideoLeader.value = true;
  304. }
  305. }
  306. function handleMuteAudio(event) {
  307. if (event.userId.indexOf('leader') > -1) {
  308. muteAudioLeader.value = true;
  309. }
  310. console.log(event, `[] mute audio`);
  311. }
  312. function handleUnmuteVideo(event) {
  313. console.log(`[${event.userId}] unmute video`);
  314. const rtcStore = useRtcStore();
  315. const roomLeader = rtcStore.getRoomLeader();
  316. if (event.userId === roomLeader?.UserId) {
  317. muteVideoLeader.value = false;
  318. }
  319. }
  320. function handleUnmuteAudio(event) {
  321. console.log(`[${event.userId}] unmute audio`);
  322. if (event.userId.indexOf('leader') > -1) {
  323. muteAudioLeader.value = false;
  324. }
  325. }
  326. function handleError(error) {
  327. console.log(`LocalClient error: ${error.message_}`);
  328. }
  329. function handleBanned(error) {
  330. console.log(`Client has been banned for ${error.message_}`);
  331. }
  332. function handlePeerJoin(event) {
  333. const { userId } = event;
  334. if (userId !== 'local-screen') {
  335. console.log(`Peer Client [${userId}] joined`);
  336. }
  337. }
  338. function handlePeerLeave(event) {
  339. const { userId } = event;
  340. if (userId !== 'local-screen') {
  341. console.log(`[${userId}] leave`);
  342. }
  343. }
  344. function handleStreamAdded(event) {
  345. const remoteStream = event.stream;
  346. const id = remoteStream.getId();
  347. const userId = remoteStream.getUserId();
  348. const rtcStore = useRtcStore();
  349. console.log(remoteStream, '-------------remoteStream');
  350. if (remoteStream.getUserId() === rtcStore.userId) {
  351. // don't need to screen shared by us
  352. localClient.unsubscribe(remoteStream).catch((error) => {
  353. console.info(`unsubscribe failed: ${error.message_}`);
  354. });
  355. } else {
  356. console.log(`remote stream added: [${userId}] ID: ${id} type: ${remoteStream.getType()}`);
  357. localClient.subscribe(remoteStream).catch((error) => {
  358. console.info(`subscribe failed: ${error.message_}`);
  359. });
  360. }
  361. }
  362. async function handleStreamSubscribed(event) {
  363. const remoteStream = event.stream;
  364. const rtcStore = useRtcStore();
  365. // const { t } = useI18n();
  366. // debugger;
  367. const remoteUserId = remoteStream.getUserId();
  368. const remoteId = remoteStream.getId();
  369. if (remoteUserId == rtcStore.userId) {
  370. return;
  371. }
  372. if (!rtcStore.isIdInRemoteStream(remoteId)) {
  373. consola.info({
  374. message: remoteId,
  375. tag: 'rtc:audio',
  376. });
  377. rtcStore.pushRemoteStreams(remoteStream);
  378. // debugger
  379. }
  380. await nextTick();
  381. const playRemote = () => {
  382. // const playId = 'camera_remote_box_' + rtcStore.userId;
  383. const playId = 'cameraRemoteBox';
  384. remoteStream
  385. .play(playId)
  386. .then(() => {
  387. consola.info({
  388. message: '远端采集成功 !playId: ' + playId + ' remoteId:' + remoteId,
  389. tag: 'rtc:audio',
  390. });
  391. })
  392. .catch((error) => {
  393. consola.info({
  394. message: '远端采集失败!' + error,
  395. tag: 'rtc:audio',
  396. });
  397. setTimeout(() => {
  398. console.log('远端再次播放' + remoteId);
  399. playRemote();
  400. }, 500);
  401. });
  402. };
  403. playRemote();
  404. remoteStream.on('error', (error) => {
  405. if (error.getCode() === 0x4043) {
  406. // 自动播放受限导致播放失败,此时引导用户点击页面。
  407. // 在点击事件的回调函数中,执行 stream.resume();
  408. const rtcStore = useRtcStore();
  409. const { t } = useI18n();
  410. rtcStore.showBaseDialog(
  411. {
  412. title: t('base.tips'),
  413. desc: t('base.audioPermission'),
  414. okTxt: t('base.confirm'),
  415. closeTxt: t('base.cancel'),
  416. },
  417. () => {
  418. console.log('手机端', rtcStore.remoteStreams);
  419. rtcStore.remoteStreams.forEach((item) => {
  420. item.resume();
  421. });
  422. remoteStream.resume();
  423. },
  424. );
  425. }
  426. });
  427. // setTimeout(async () => {
  428. // try {
  429. // consola.info({
  430. // message: '客音源-->' + remoteId,
  431. // tag: 'rtc:audio',
  432. // });
  433. // consola.info({
  434. // message: rtcStore.remoteStreams,
  435. // tag: 'rtc:audio',
  436. // });
  437. // await remoteStream.play(remoteId);
  438. // } catch (error) {}
  439. // }, 200);
  440. }
  441. function handleStreamRemoved(event) {
  442. const remoteStream = event.stream;
  443. const userId = remoteStream.getUserId();
  444. const remoteStreamId = remoteStream.getId();
  445. consola.info({
  446. message: `远端流删除: [${userId},${remoteStreamId}]`,
  447. tag: 'rtc:audio',
  448. });
  449. const rtcStore = useRtcStore();
  450. rtcStore.removeRemoteStreams(remoteStreamId);
  451. const node = document.getElementById(`player_` + remoteStreamId);
  452. consola.info({
  453. message: `远端流删除:` + node,
  454. tag: 'rtc:audio',
  455. });
  456. if (node) {
  457. console.log('node', node);
  458. }
  459. }
  460. function handleStreamUpdated(event) {
  461. const remoteStream = event.stream;
  462. const userId = remoteStream.getUserId();
  463. console.log(
  464. `RemoteStream updated: [${userId}] audio:${remoteStream.hasAudio()} video:${remoteStream.hasVideo()}`,
  465. );
  466. }
  467. // const switchDevice = async ({ videoId, audioId }) => {
  468. // const rtcStore = useRtcStore();
  469. // if (!rtcStore.isJoined) {
  470. // return;
  471. // }
  472. // if (videoId) {
  473. // try {
  474. // await localStream.switchDevice('video', videoId);
  475. // } catch (error) {}
  476. // }
  477. // if (audioId) {
  478. // try {
  479. // await localStream.switchDevice('audio', audioId);
  480. // } catch (error) {}
  481. // }
  482. // };
  483. export function useRtcSdk() {
  484. return {
  485. createRTCSocket,
  486. handleJoin,
  487. handleLeave,
  488. localStream,
  489. muteVideoLeader,
  490. invitedRemoteStreams,
  491. client: localClient,
  492. globalVideoEnable,
  493. };
  494. }