VideoCalling.vue 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. <template>
  2. <view>
  3. <web-view v-if="webviewUrl" @message="handleMessage" :src="webviewUrl"></web-view>
  4. </view>
  5. </template>
  6. <script setup lang="ts">
  7. import {ref, watch} from 'vue'
  8. import {useUserStore} from '@/store/userStore'
  9. import {onLoad,onReady} from "@dcloudio/uni-app";
  10. import {useWsStore} from "@/store/WsStore";
  11. import {storeToRefs} from "pinia";
  12. import {usePeerStore} from "@/store/peerStore";
  13. import SendVideoCode from "@/plugins/video/SendVideoCode";
  14. import UserApi from "@/api/UserApi";
  15. import ChatType from "@/utils/ChatType";
  16. import type VideoSendInfo from "@/plugins/video/mode/VideoSendInfo";
  17. import type VideoCallMessage from "@/plugins/video/mode/VideoCallMessage";
  18. import SendCode from "@/utils/SendCode";
  19. import type VideoClose from "@/plugins/video/mode/VideoClose";
  20. import MessageVideoViewPlugin from "@/plugins/video/MessageVideoViewPlugin";
  21. import {
  22. UTSVoip,
  23. UTSLocalNotification,
  24. UTSCXProvider
  25. } from "@/uni_modules/wrs-uts-voip"
  26. const peerStore = usePeerStore()
  27. const friendId = ref("")
  28. const showVideo = ref(false)
  29. const isConnect = ref(false)
  30. const startTime = ref(0)
  31. const friend = ref()
  32. const peerId = ref()
  33. const checkPermission = function (title: string) {
  34. // #ifndef H5
  35. if (uni.getSystemInfoSync().platform !== 'android') {
  36. return new Promise((resolve) => {
  37. resolve(true)
  38. })
  39. }else {
  40. return new Promise((resolve) => {
  41. plus.android.requestPermissions(
  42. ["android.permission.RECORD_AUDIO", "android.permission.CAMERA"],
  43. function (resultObj) {
  44. if (resultObj.granted.length < 2) {
  45. uni.showToast({
  46. icon: "none",
  47. title,
  48. });
  49. resolve(false)
  50. const timer1 = setTimeout(() => { //没有开对应的权限,打开app的系统权限管理页
  51. let Intent = plus.android.importClass("android.content.Intent");
  52. let Settings = plus.android.importClass("android.provider.Settings");
  53. let Uri = plus.android.importClass("android.net.Uri");
  54. let mainActivity = plus.android.runtimeMainActivity();
  55. let intent = new Intent();
  56. intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
  57. let uri = Uri.fromParts("package", mainActivity.getPackageName(), null);
  58. intent.setData(uri);
  59. mainActivity.startActivity(intent);
  60. clearTimeout(timer1)
  61. }, 1000)
  62. } else {
  63. resolve(true)
  64. }
  65. }
  66. );
  67. })
  68. }
  69. // #endif
  70. // #ifdef H5
  71. return new Promise((resolve) => {
  72. resolve(true)
  73. })
  74. // #endif
  75. }
  76. onLoad((opt) => {
  77. const user = useUserStore().getUser()
  78. friendId.value = opt?.friendId
  79. peerId.value = opt?.peerId
  80. if(typeof opt?.showVideo === 'string'){
  81. showVideo.value = opt?.showVideo === 'true'
  82. }
  83. peerStore.updateBusyStatus(true)
  84. UserApi.getUser(friendId.value).then((res) => {
  85. friend.value = res.data
  86. if (user && friend.value) {
  87. checkPermission("请开启相机和麦克风权限")
  88. .then((res) => {
  89. peerStore.setCallId(friend.value.id)
  90. peerStore.updateCloseStatus(false)
  91. const avatar = encodeURI(friend.value.avatar)
  92. if(showVideo.value){
  93. //转义url
  94. const url = `/hybrid/html/${peerId.value ? 'answer' : 'calling'}.html?id=${user.id}&name=${user.name}&friendId=${friend.value.id}&friendName=${friend.value.name}&friendAvatar=${avatar}&showVideo=${showVideo.value}&peerId=${peerId.value ?? ''}`
  95. webviewUrl.value = encodeURI(url)
  96. }
  97. else{
  98. //转义url
  99. const url = `/hybrid/html/${peerId.value ? 'answerAudio' : 'callingAudio'}.html?id=${user.id}&name=${user.name}&friendId=${friend.value.id}&friendName=${friend.value.name}&friendAvatar=${avatar}&showVideo=${showVideo.value}&peerId=${peerId.value ?? ''}`
  100. webviewUrl.value = encodeURI(url)
  101. }
  102. })
  103. }
  104. })
  105. })
  106. onReady(()=>{
  107. setTimeout(function() {
  108. // 这里写要延时执行的代码
  109. if(uni.getSystemInfoSync().platform == "ios"){
  110. let provider = new UTSCXProvider()
  111. provider.endCall({
  112. uuid: uni.getStorageSync("voip_UUID")
  113. }, (resp: any)=>{
  114. console.log(JSON.stringify(resp))
  115. })
  116. }
  117. }, 500);
  118. });
  119. const wsStore = useWsStore()
  120. const webviewUrl = ref()
  121. const {isClose} = storeToRefs(peerStore)
  122. watch(isClose, (newVal) => {
  123. if (newVal) {
  124. uni.navigateBack()
  125. }
  126. })
  127. /**
  128. * 发送关闭
  129. * @param calling
  130. */
  131. const sendCloseMsg = (calling: boolean) => {
  132. const userId = useUserStore().getUser()?.id
  133. if (userId) {
  134. const closeMessage: VideoSendInfo<VideoClose> = {
  135. code: SendVideoCode.CLOSE,
  136. message: {
  137. chatId: friend.value.id,
  138. fromId: userId,
  139. timestamp: new Date().getTime(),
  140. type: ChatType.FRIEND
  141. }
  142. }
  143. wsStore.send(JSON.stringify(closeMessage))
  144. console.log('发送关闭消息', closeMessage)
  145. //发起方才有发送视频结果消息的权限
  146. if (calling) {
  147. //界面展示视频消息情况,例如:对方是否接受了视频通话,通话时长
  148. const callMessage: VideoSendInfo<VideoCallMessage> = {
  149. code: SendCode.MESSAGE,
  150. message: {
  151. messageType: new MessageVideoViewPlugin().messageType,
  152. chatId: friend.value.id,
  153. fromId: userId,
  154. timestamp: new Date().getTime(),
  155. type: ChatType.FRIEND,
  156. result: isConnect.value,
  157. video: showVideo.value,
  158. duration: isConnect.value ? new Date().getTime() - startTime.value : 0
  159. }
  160. }
  161. //console.log(callMessage)
  162. wsStore.send(JSON.stringify(callMessage))
  163. }
  164. }
  165. }
  166. /**
  167. * 处理消息 接受来自webview的消息
  168. * @param data
  169. */
  170. const handleMessage = (data: any) => {
  171. const message = data.detail.data[0];
  172. console.log('124444',message);
  173. // 创建音频播放器实例
  174. //var player = plus.audio.createPlayer(stream);
  175. switch (message.type) {
  176. case 'ws'://ws消息
  177. useWsStore().send(JSON.stringify(message.data))
  178. break;
  179. case 'changeSpeaker'://ws消息
  180. break;
  181. case 'connect'://ws消息
  182. isConnect.value = true
  183. startTime.value = new Date().getTime()
  184. break;
  185. case 'accept'://接受视频请求
  186. isConnect.value = true
  187. startTime.value = new Date().getTime()
  188. //发送请求,告诉自己的peerId给自己的另一个客户端
  189. const userId = useUserStore().user?.id
  190. usePeerStore().setPeerId(message.data.peerId)
  191. //给自己发一份忙碌状态,多个客户端同一个人不能同时接受,需要客户端接受后,其他客户端就关闭
  192. useWsStore().send(
  193. JSON.stringify({
  194. code: SendVideoCode.BUSY,
  195. message: {
  196. chatId: userId,
  197. fromId: userId,
  198. peerId: message.data.peerId
  199. }
  200. })
  201. )
  202. break;
  203. case 'close'://关闭
  204. sendCloseMsg(message.data.calling)
  205. isConnect.value = false
  206. peerStore.setCallId(undefined)
  207. peerStore.updateBusyStatus(false)
  208. uni.navigateBack()
  209. break;
  210. case 'stream'://
  211. console.log('stream',JSON.stringify(message.data))
  212. break;
  213. }
  214. }
  215. </script>
  216. <style scoped lang="scss">
  217. .chat-container {
  218. display: block;
  219. flex-direction: column;
  220. align-items: center;
  221. justify-content: center;
  222. height: 100vh;
  223. background-color: #000; /* 微信通常使用深色背景 */
  224. position: relative;
  225. }
  226. .main-video {
  227. width: 100%;
  228. height: 100%;
  229. object-fit: cover; /* 确保视频填充整个屏幕 */
  230. position: absolute;
  231. top: 0;
  232. left: 0;
  233. }
  234. .local-video {
  235. width: 50%;
  236. height: 50%;
  237. position: absolute;
  238. top: 50%;
  239. left: 50%;
  240. }
  241. .controls {
  242. position: absolute;
  243. bottom: 50px; /* 将控制按钮放在屏幕底部 */
  244. display: flex;
  245. justify-content: space-around;
  246. width: 100%;
  247. }
  248. button {
  249. border: none;
  250. border-radius: 50%;
  251. background-color: red;
  252. color: white;
  253. font-size: 32upx;
  254. cursor: pointer;
  255. transition: background-color 0.3s;
  256. z-index: 1000000000000000000000000;
  257. width: 20vw;
  258. height: 20vw;
  259. display: flex;
  260. align-items: center;
  261. justify-content: center;
  262. }
  263. button:hover {
  264. background-color: rgba(0, 0, 0, 0.9); /* 鼠标悬停时颜色加深 */
  265. }
  266. button:active {
  267. background-color: rgba(0, 0, 0, 1); /* 点击时颜色更加深 */
  268. }
  269. /* 特定按钮的图标,例如使用Font Awesome或类似图标库 */
  270. .start-call-icon {
  271. /* 添加开始通话图标样式 */
  272. }
  273. .end-call-icon {
  274. /* 添加结束通话图标样式 */
  275. }
  276. </style>