Trtccom.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  1. <template>
  2. <div class="trtccom" v-if="show">
  3. <Device @switchDevice="switchDevice" @canUseDevice="canUseDevice" />
  4. <div
  5. class="local"
  6. :class="{ disabledlocal: role == 'customer' || !videoDeviceId }"
  7. id="local"
  8. v-if="isJoined"
  9. >
  10. <div class="micBox">
  11. <img
  12. v-if="muteAudioLeader"
  13. :src="require('@/assets/images/rtcLive/mic_off@2x.png')"
  14. alt=""
  15. />
  16. <i v-else class="speak_mic"></i>
  17. </div>
  18. </div>
  19. <template v-if="isJoined && invitedRemoteStreams.length > 0">
  20. <div
  21. class="local"
  22. :data-role="item.userId_"
  23. :class="{
  24. disabledlocal:
  25. item.userId_.indexOf('customer') > -1 || !videoDeviceId,
  26. }"
  27. v-for="item in invitedRemoteStreams"
  28. :id="item.userId_"
  29. :key="item.userId_"
  30. >
  31. <div class="micBox">
  32. <img
  33. v-if="muteAudioLeader"
  34. :src="require('@/assets/images/rtcLive/mic_off@2x.png')"
  35. alt=""
  36. />
  37. <i v-else class="speak_mic"></i>
  38. </div>
  39. </div>
  40. </template>
  41. <div
  42. class="videoBox userVideo"
  43. v-show="props.videoMuted || muteVideoLeader"
  44. v-if="false"
  45. >
  46. <img
  47. :src="require('@/assets/images/rtcLive/avatar_small@2x.png')"
  48. alt=""
  49. />
  50. <div class="micBox">
  51. <img
  52. v-if="muteAudioLeader"
  53. :src="require('@/assets/images/rtcLive/mic_off@2x.png')"
  54. alt=""
  55. />
  56. <i v-else class="speak_mic"></i>
  57. </div>
  58. </div>
  59. </div>
  60. </template>
  61. <script setup>
  62. import TRTC, { Client, LocalStream } from "trtc-js-sdk";
  63. import { Dialog } from "@/global_components/";
  64. import {
  65. ref,
  66. computed,
  67. watch,
  68. defineEmits,
  69. defineProps,
  70. nextTick,
  71. onUnmounted,
  72. } from "vue";
  73. import Device from "./trtc/Device";
  74. import { useStore } from "vuex";
  75. import browser from "@/utils/browser";
  76. // import * as apis from "@/apis/index.js";
  77. const emit = defineEmits(["audioMuted", "videoMuted", "closeSocket"]);
  78. const store = useStore();
  79. const show = ref(false);
  80. const invitedRemoteStreams = ref([]);
  81. const role = ref(browser.getURLParam("role"));
  82. const muteAudioLeader = ref(false);
  83. const muteVideoLeader = ref(false);
  84. const isJoined = computed(() => store.getters["rtc/isJoined"]);
  85. const isPublished = computed(() => store.getters["rtc/isPublished"]);
  86. const videoDeviceId = computed(() => store.getters["rtc/videoDeviceId"]);
  87. const initParamsStates = computed(
  88. () =>
  89. !!(
  90. store.getters["rtc/sdkAppId"] &&
  91. store.getters["rtc/secretKey"] &&
  92. store.getters["rtc/roomId"] &&
  93. store.getters["rtc/userId"]
  94. )
  95. );
  96. const userSig = computed(() => store.getters["rtc/userSig"]);
  97. let localClient = "";
  98. let localStream = "";
  99. let shareClient = "";
  100. const props = defineProps({
  101. audioMuted: {
  102. default: false,
  103. },
  104. videoMuted: {
  105. default: false,
  106. },
  107. });
  108. watch(
  109. () => props.audioMuted,
  110. () => {
  111. if (props.audioMuted) {
  112. localStream.muteAudio();
  113. } else {
  114. localStream.unmuteAudio();
  115. }
  116. if (role.value == "leader") {
  117. muteAudioLeader.value = props.audioMuted;
  118. }
  119. }
  120. );
  121. watch(
  122. () => props.videoMuted,
  123. () => {
  124. if (props.videoMuted) {
  125. localStream.muteVideo();
  126. } else {
  127. localStream.unmuteVideo();
  128. }
  129. }
  130. );
  131. // watch(
  132. // () => isJoined.value,
  133. // () => {
  134. // if (!isJoined.value) {
  135. // handleLeave();
  136. // }
  137. // }
  138. // );
  139. TRTC.checkSystemRequirements().then((checkResult) => {
  140. if (!checkResult.result) {
  141. Dialog.toast({ content: `您的设备不支持音视频通讯`, type: "error" });
  142. } else {
  143. show.value = true;
  144. }
  145. });
  146. async function createLocalStream() {
  147. try {
  148. localStream = TRTC.createStream({
  149. userId: store.getters["rtc/userId"],
  150. audio: true,
  151. video: false,
  152. microphoneId: store.getters["rtc/audioDeviceId"],
  153. });
  154. await localStream.initialize();
  155. if(props.audioMuted){
  156. localStream.muteAudio();
  157. }
  158. } catch (error) {
  159. console.log(error, "createStream");
  160. }
  161. }
  162. async function handleJoin() {
  163. if (!initParamsStates.value) {
  164. return;
  165. }
  166. try {
  167. // let res = await apis.getSign({ userId: store.getters["rtc/userId"] });
  168. localClient = TRTC.createClient({
  169. mode: "rtc",
  170. sdkAppId: parseInt(store.getters["rtc/sdkAppId"], 10),
  171. userId: store.getters["rtc/userId"],
  172. userSig: userSig.value,
  173. useStringRoomId: true,
  174. enableAutoPlayDialog: false,
  175. });
  176. installEventHandlers();
  177. await localClient.join({ roomId: store.getters["rtc/roomId"] });
  178. store.commit("rtc/setIsJoined", true);
  179. // inviteLink.value = store.commit("rtc/createShareLink");
  180. } catch (error) {
  181. console.error(error, "error-----------");
  182. }
  183. await createLocalStream();
  184. await handlePublish();
  185. localStream
  186. .play("local")
  187. .then(() => {
  188. // addLocalControlView();
  189. })
  190. .catch((e) => {
  191. console.log(stream);
  192. });
  193. localStream.on("error", (error) => {
  194. if (error.getCode() === 0x4043) {
  195. // 自动播放受限导致播放失败,此时引导用户点击页面。
  196. // 在点击事件的回调函数中,执行 stream.resume();
  197. Dialog.confirm({
  198. showCloseIcon: false,
  199. okText: "确定",
  200. content:
  201. "<span style='font-size: 16px; line-height: 1.5;'>在用户与网页产生交互(例如点击、触摸页面等)之前,网页将被禁止播放带有声音的媒体。点击恢复播放<span/>",
  202. title: "隐私条款:",
  203. single: true,
  204. func: (state) => {
  205. if (state == "ok") {
  206. localStream.resume();
  207. }
  208. },
  209. });
  210. }
  211. });
  212. }
  213. async function handlePublish() {
  214. if (!isJoined.value) {
  215. return;
  216. }
  217. if (isPublished.value) {
  218. return;
  219. }
  220. if (role.value != "leader") {
  221. return;
  222. }
  223. try {
  224. await localClient.publish(localStream);
  225. store.commit("rtc/setIsPublished", true);
  226. } catch (error) {
  227. console.error(error, "---------------handlePublish--------------------");
  228. }
  229. }
  230. async function handleStartShare() {
  231. shareClient = new ShareClient({
  232. sdkAppId: parseInt(store.getters["rtc/sdkAppId"], 10),
  233. userId: `share${store.getters["rtc/userId"]}`,
  234. roomId: store.getters["rtc/roomId"],
  235. secretKey: store.getters["rtc/secretKey"],
  236. useStringRoomId: true,
  237. });
  238. try {
  239. await shareClient.join();
  240. await shareClient.publish();
  241. console.log("Start share screen success");
  242. store.isShared = true;
  243. } catch (error) {
  244. console.error(`Start share error: ${error.message_}`);
  245. }
  246. }
  247. async function handleUnpublish() {
  248. if (!isJoined.value) {
  249. return;
  250. }
  251. if (!isPublished.value) {
  252. return;
  253. }
  254. try {
  255. await localClient.unpublish(localStream);
  256. store.commit("rtc/setIsPublished", false);
  257. } catch (error) {
  258. console.error(error, "-----------handleUnpublish--------------");
  259. }
  260. }
  261. async function handleLeave() {
  262. if (isPublished.value) {
  263. await handleUnpublish();
  264. }
  265. try {
  266. uninstallEventHandlers();
  267. await localClient.leave();
  268. localClient.destroy();
  269. localClient = null;
  270. invitedRemoteStreams.value.forEach((item) => {
  271. item.stop();
  272. });
  273. invitedRemoteStreams.value = [];
  274. store.commit("rtc/setVideoDeviceId", "");
  275. store.commit("rtc/setAudioDeviceId", "");
  276. if (localStream) {
  277. localStream.stop();
  278. localStream.close();
  279. localStream = null;
  280. console.log("有执行到这里-------------");
  281. }
  282. } catch (error) {
  283. console.error(error, "-----------handleLeave--------------");
  284. }
  285. }
  286. function installEventHandlers() {
  287. if (!localClient) {
  288. return;
  289. }
  290. localClient.on("error", handleError);
  291. localClient.on("client-banned", handleBanned);
  292. localClient.on("peer-join", handlePeerJoin);
  293. localClient.on("peer-leave", handlePeerLeave);
  294. localClient.on("stream-added", handleStreamAdded);
  295. localClient.on("stream-subscribed", handleStreamSubscribed);
  296. localClient.on("stream-removed", handleStreamRemoved);
  297. localClient.on("stream-updated", handleStreamUpdated);
  298. localClient.on("mute-video", handleMuteVideo);
  299. localClient.on("mute-audio", handleMuteAudio);
  300. localClient.on("unmute-video", handleUnmuteVideo);
  301. localClient.on("unmute-audio", handleUnmuteAudio);
  302. }
  303. function uninstallEventHandlers() {
  304. if (!localClient) {
  305. return;
  306. }
  307. localClient.off("error", handleError);
  308. localClient.off("error", handleError);
  309. localClient.off("client-banned", handleBanned);
  310. localClient.off("peer-join", handlePeerJoin);
  311. localClient.off("peer-leave", handlePeerLeave);
  312. localClient.off("stream-added", handleStreamAdded);
  313. localClient.off("stream-subscribed", handleStreamSubscribed);
  314. localClient.off("stream-removed", handleStreamRemoved);
  315. localClient.off("stream-updated", handleStreamUpdated);
  316. localClient.off("mute-video", handleMuteVideo);
  317. localClient.off("mute-audio", handleMuteAudio);
  318. localClient.off("unmute-video", handleUnmuteVideo);
  319. localClient.off("unmute-audio", handleUnmuteAudio);
  320. }
  321. function handleMuteVideo(event) {
  322. console.log(`[${event.userId}] mute video`);
  323. if (event.userId.indexOf("leader") > -1) {
  324. muteVideoLeader.value = true;
  325. }
  326. }
  327. function handleMuteAudio(event) {
  328. if (event.userId.indexOf("leader") > -1) {
  329. muteAudioLeader.value = true;
  330. }
  331. console.log(event, `[] mute audio`);
  332. }
  333. function handleUnmuteVideo(event) {
  334. console.log(`[${event.userId}] unmute video`);
  335. if (event.userId.indexOf("leader") > -1) {
  336. muteVideoLeader.value = false;
  337. }
  338. }
  339. function handleUnmuteAudio(event) {
  340. console.log(`[${event.userId}] unmute audio`);
  341. if (event.userId.indexOf("leader") > -1) {
  342. muteAudioLeader.value = false;
  343. }
  344. }
  345. function handleError(error) {
  346. console.log(`LocalClient error: ${error.message_}`);
  347. }
  348. function handleBanned(error) {
  349. console.log(`Client has been banned for ${error.message_}`);
  350. }
  351. function handlePeerJoin(event) {
  352. const { userId } = event;
  353. if (userId !== "local-screen") {
  354. console.log(`Peer Client [${userId}] joined`);
  355. }
  356. }
  357. function handlePeerLeave(event) {
  358. const { userId } = event;
  359. if (userId !== "local-screen") {
  360. console.log(`[${userId}] leave`);
  361. }
  362. }
  363. function handleStreamAdded(event) {
  364. const remoteStream = event.stream;
  365. const id = remoteStream.getId();
  366. const userId = remoteStream.getUserId();
  367. console.log(remoteStream, "-------------remoteStream");
  368. if (remoteStream.getUserId() === store.getters["rtc/userId"]) {
  369. // don't need to screen shared by us
  370. localClient.unsubscribe(remoteStream).catch((error) => {
  371. console.info(`unsubscribe failed: ${error.message_}`);
  372. });
  373. } else {
  374. console.log(
  375. `remote stream added: [${userId}] ID: ${id} type: ${remoteStream.getType()}`
  376. );
  377. localClient.subscribe(remoteStream).catch((error) => {
  378. console.info(`subscribe failed: ${error.message_}`);
  379. });
  380. }
  381. }
  382. async function handleStreamSubscribed(event) {
  383. const remoteStream = event.stream;
  384. if (remoteStream.userId_ == store.getters["rtc/userId"]) {
  385. return;
  386. }
  387. console.info(
  388. remoteStream.userId_,
  389. store.getters["rtc/userId"],
  390. "handleStreamSubscribedhandleStreamSubscribed.value"
  391. );
  392. if (
  393. !invitedRemoteStreams.value.some(
  394. (item) => item.userId_ == remoteStream.userId_
  395. )
  396. ) {
  397. invitedRemoteStreams.value.push(remoteStream);
  398. }
  399. console.log(invitedRemoteStreams.value, "invitedRemoteStreams.value");
  400. await nextTick();
  401. setTimeout(() => {
  402. console.log(remoteStream.userId_, "remoteStream.getId()");
  403. remoteStream
  404. .play(remoteStream.userId_)
  405. .then(() => {
  406. console.log(`RemoteStream play success`, 88888888888888888888);
  407. })
  408. .catch((error) => {
  409. console.log(`RemoteStream play failed: error: ${error.message_}`);
  410. });
  411. }, 100);
  412. // const remoteStream = event.stream;
  413. // const userId = remoteStream.getUserId();
  414. // console.log(`RemoteStream subscribed: [${userId}]`);
  415. }
  416. function handleStreamRemoved(event) {
  417. const remoteStream = event.stream;
  418. const userId = remoteStream.getUserId();
  419. console.log(`RemoteStream removed: [${userId}]`);
  420. }
  421. function handleStreamUpdated(event) {
  422. const remoteStream = event.stream;
  423. const userId = remoteStream.getUserId();
  424. console.log(
  425. `RemoteStream updated: [${userId}] audio:${remoteStream.hasAudio()} video:${remoteStream.hasVideo()}`
  426. );
  427. }
  428. let switchDevice = async ({ videoId, audioId }) => {
  429. console.log()
  430. if (!isJoined.value) {
  431. return;
  432. }
  433. if (videoId) {
  434. try {
  435. await localStream.switchDevice("video", videoId);
  436. } catch (error) {}
  437. }
  438. if (audioId) {
  439. try {
  440. await localStream.switchDevice("audio", audioId);
  441. } catch (error) {}
  442. }
  443. };
  444. onUnmounted(() => {
  445. handleLeave();
  446. });
  447. let canUseDevice = () => {
  448. console.log("可用");
  449. handleJoin();
  450. };
  451. </script>
  452. <style lang="scss" scoped>
  453. .trtccom {
  454. .local {
  455. width: 70px;
  456. height: 70px;
  457. position: fixed;
  458. z-index: 9999;
  459. top: 20px;
  460. left: 20px;
  461. border-radius: 50%;
  462. overflow: hidden;
  463. background: url(~@/assets/images/rtcLive/avatar_small@2x.png) center center
  464. no-repeat;
  465. }
  466. .videoBox {
  467. width: 72px;
  468. height: 72px;
  469. top: 19px;
  470. left: 19px;
  471. position: fixed;
  472. z-index: 99999;
  473. border-radius: 50%;
  474. overflow: hidden;
  475. .loadingTip {
  476. position: absolute;
  477. z-index: 101;
  478. left: 0;
  479. top: 0;
  480. bottom: 0;
  481. right: 0;
  482. animation: Rotate 1.5s infinite;
  483. @keyframes Rotate {
  484. 0% {
  485. transform: rotate(0deg);
  486. }
  487. 100% {
  488. transform: rotate(360deg);
  489. }
  490. }
  491. }
  492. > img {
  493. width: 100%;
  494. height: 100%;
  495. }
  496. }
  497. .micBox {
  498. width: 100%;
  499. height: 16px;
  500. background: rgba(0, 0, 0, 0.3);
  501. position: absolute;
  502. left: 0;
  503. bottom: 0;
  504. z-index: 100;
  505. display: flex;
  506. align-items: center;
  507. justify-content: center;
  508. .speak_mic {
  509. display: block;
  510. background-size: 720px auto;
  511. width: 12px;
  512. height: 12px;
  513. background-image: url(~@/assets/images/rtcLive/speed.png);
  514. // width: 0.69rem;
  515. // height: 0.69rem;
  516. animation: myAnimation 3s steps(59) infinite;
  517. }
  518. > img {
  519. width: 12px;
  520. }
  521. }
  522. .disabledlocal {
  523. opacity: 0 !important;
  524. visibility: hidden !important;
  525. }
  526. }
  527. @keyframes myAnimation {
  528. 0% {
  529. background-position: 0px 0px;
  530. }
  531. 100% {
  532. background-position: -708px 0px;
  533. }
  534. }
  535. </style>