video.nvue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692
  1. <template>
  2. <view class="video_box" :style="'height:' + phoneHeight + 'px'">
  3. <!-- 本地预览视图 -->
  4. <view class="video_me" v-show="allUserViewObjectList.length > 0">
  5. <!-- <view class="video_me" v-if="videoShow"> -->
  6. <zego-local-view :viewMode="publisherViewModeIndex"
  7. style="width: 150px; height: 200px; flex: 1; position: fixed; right: 0; top: 65px;">
  8. </zego-local-view>
  9. </view>
  10. <!-- 服务器传来的预览图 -->
  11. <view class="video_you" v-for="(item, index) in allUserViewObjectList" :key="item.streamID">
  12. <!-- <view class="video_you"> -->
  13. <zego-remote-view v-if="item.streamID" :streamID="item.streamID" :viewMode="item.viewMode"
  14. style="width:600px;height:600px;">
  15. </zego-remote-view>
  16. </view>
  17. <view class="video_btn">
  18. <view class="video_imgList">
  19. <view class="video_imgList1">
  20. <view class="video_view" @click="turn">
  21. <image class="video_view_img" src="../../static/image/9.png" mode=""></image>
  22. </view>
  23. <text class="video_view_txt">翻转镜头</text>
  24. </view>
  25. <!-- <view class="video_imgList2" v-if="CameraShow">
  26. <view class="video_view" @click="closeCamera">
  27. <image class="video_view_img" src="../../static/image/11.png" mode=""></image>
  28. </view>
  29. <text class="video_view_txt" style="margin: 0 0 0 30rpx;">静音</text>
  30. </view>
  31. <view class="video_imgList2" v-else>
  32. <view class="video_view" @click="openCamera">
  33. <image class="video_view_img" src="../../static/image/12.png" mode=""></image>
  34. </view>
  35. <text class="video_view_txt" style="margin: 0 0 0 30rpx;">开启</text>
  36. </view> -->
  37. <image @click="offVideo(false,true)" style="width: 50px; height: 50px; margin: 0 0 0 330rpx;"
  38. src="../../static/image/10.png" mode=""></image>
  39. </view>
  40. <!-- <image @click="offVideo()" style="width: 50px; height: 50px; margin: 0 0 0 330rpx;"
  41. src="../../static/image/10.png" mode=""></image> -->
  42. </view>
  43. <!-- 消息提醒 -->
  44. <u-toast ref="uToast"></u-toast>
  45. </view>
  46. </template>
  47. <script>
  48. import {
  49. mapState,
  50. mapMutations
  51. } from 'vuex'
  52. import store from '@/store/index.js'; //需要引入store
  53. let App = getApp();
  54. // var API = App.globalData.socketTask;
  55. var API = App.globalData;
  56. import permision from "@/zego-express-video-uniapp/permission.js";
  57. import ZegoExpressEngine from '@/zego-express-video-uniapp/lib/ZegoExpressEngine';
  58. import {
  59. ZegoScenario,
  60. ZegoRoomState,
  61. ZegoUpdateType,
  62. // ZegoViewMode,
  63. ZegoVideoCodecID,
  64. ZegoRemoteDeviceState
  65. } from '@/zego-express-video-uniapp/lib/ZegoExpressDefines'
  66. import {
  67. AppID,
  68. AppSign
  69. } from '@/zego-express-video-uniapp/KeyCenter.js'
  70. import ZegoLocalView from '@/zego-express-video-uniapp/zego-view/ZegoLocalView';
  71. import ZegoRemoteView from '@/zego-express-video-uniapp/zego-view/ZegoRemoteView';
  72. import {
  73. assign,
  74. forEach
  75. } from 'lodash-es';
  76. export default {
  77. data() {
  78. return {
  79. phoneHeight: '', // 获取当前的屏幕高度
  80. // 即构
  81. publisherViewModeIndex: 0, // 本地预览图
  82. viewModeA: 0, // 服务器预览图
  83. serveViewModeIndex: "", // 服务器拉流预览图
  84. engine: undefined,
  85. videoObj: null,
  86. userid: "Uni" + Math.floor(Math.random() * 1000000).toString(),
  87. isPublishingStream: false,
  88. shotShow: false, // 翻转镜头
  89. videoShow: false, // 切换视频显示
  90. allStreamList: [],
  91. allUserViewObjectList: [],
  92. CameraShow: true, // 镜头开启关闭
  93. playerStreamID: 0,
  94. isPendingExit: false
  95. }
  96. },
  97. computed: {
  98. ...mapState({
  99. isVideoCallRefused: state => state.isVideoCallRefused,
  100. currentReceiveUserID: state => state.currentReceiveUserID
  101. })
  102. },
  103. components: {
  104. ZegoLocalView: ZegoLocalView,
  105. ZegoRemoteView: ZegoRemoteView
  106. },
  107. watch: {
  108. // allUserViewObjectList(val) {
  109. // var that = this
  110. // if (val.length > 0) {
  111. // val.forEach((item) => {
  112. // if (item.streamID) {
  113. // this.tensile(item)
  114. // return true
  115. // }
  116. // })
  117. // }
  118. // }
  119. isVideoCallRefused(val) {
  120. console.warn(val, 'isVideoCallRefused video nvue')
  121. if (val) {
  122. this.offVideo(true)
  123. }
  124. }
  125. },
  126. methods: {
  127. ...mapMutations(['updateIsRefusedCall', 'updateTalkingStatus','updateOccupyedStatus']),
  128. // 即构 - 视频通话 初始化
  129. async setup() {
  130. var that = this
  131. // 创建引擎
  132. let profile = {
  133. appID: AppID,
  134. appSign: AppSign,
  135. scenario: ZegoScenario.General
  136. }
  137. this.engine = await ZegoExpressEngine.createEngineWithProfile(profile);
  138. this.engine.useFrontCamera(this.shotShow); // 设置前后摄像头
  139. // console.log(this.engine)
  140. if (this.isPendingExit) {
  141. await this.offVideo(true)
  142. }
  143. this.addListeners();
  144. // console.log('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>' + that.videoObj.room_id)
  145. const userID = (that.userid = that.videoObj.room_id + that.videoObj.user_id)
  146. console.log(userID, that.userid)
  147. await this.engine.loginRoom(that.videoObj.room_id, {
  148. userID,
  149. userName: that.videoObj.user_id + '-' +
  150. that.videoObj.real_name
  151. }, {
  152. isUserStatusNotify: true
  153. }).then(res => {
  154. console.log(res, 'login room -------------')
  155. }).catch(err => {
  156. console.error(err, 'login room')
  157. });
  158. },
  159. // 即构 - 翻转镜头
  160. turn() {
  161. this.shotShow = !this.shotShow
  162. this.engine.useFrontCamera(this.shotShow)
  163. },
  164. // 即构 - 关闭镜头 - 停止拉流 、推流
  165. closeCamera() {
  166. this.allStreamList.forEach((item) => {
  167. // this.notTensile(item.streamID); // 停止拉流
  168. item.muteAudio = !item.muteAudio;
  169. this.engine.mutePlayStreamAudio(item.streamID, item.muteAudio);
  170. })
  171. // // 停止推流
  172. this.CameraShow = false
  173. },
  174. // 即构 - 开启镜头 - 开启拉流、推流
  175. openCamera() {
  176. console.log('开启摄像头')
  177. this.allStreamList.forEach((item) => {
  178. // this.engine.startPlayingStream(item.streamID); // 开始拉流
  179. item.muteAudio = !item.muteAudio;
  180. this.engine.mutePlayStreamAudio(item.streamID, item.muteAudio);
  181. })
  182. // // 开始推流
  183. // this.engine.startPreview();
  184. // this.engine.startPublishingStream(this.videoObj.room_id);
  185. this.CameraShow = true
  186. },
  187. // 切换视频显示
  188. changeShow() {
  189. this.videoShow = !this.videoShow
  190. // this.onPublish()
  191. this.engine.startPreview();
  192. this.engine.startPublishingStream(this.userid);
  193. this.publishBtnName = "Stop Publishing";
  194. console.log(this.videoShow)
  195. },
  196. // 即构 - 视频通话 监听房间
  197. addListeners() {
  198. console.log('开始监听房间了啊')
  199. var that = this
  200. // console.log(this.allStreamList, '------------------')
  201. // 房间状态变化通知
  202. this.engine.on("roomStateUpdate", (roomID, state, errorCode, extendedData) => {
  203. console.log('房间状态变化', state)
  204. if (state == ZegoRoomState.Connected) {
  205. // 与房间连接成功,只有当房间状态是连接成功时,才能进行推流、拉流等操作。
  206. // 接下来的“预览并推流”的代码写在这里
  207. console.log("房间连接成功");
  208. }
  209. if (state == ZegoRoomState.DisConnected) {
  210. // 与房间断开了连接
  211. console.log("与房间断开连接");
  212. }
  213. if (state == ZegoRoomState.Connecting) {
  214. // 与房间尝试连接中
  215. console.log("与房间尝试连接中");
  216. }
  217. });
  218. // 房间用户变化通知
  219. this.engine.on("roomUserUpdate", (roomID, updateType, userList) => {
  220. console.log(updateType, ZegoUpdateType.Delete, 'roomUserUpdate')
  221. console.log(userList)
  222. console.log("有其他用户进出房间");
  223. if (updateType == ZegoUpdateType.Add) {
  224. console.log('进入了啊AAAAAAAAAA')
  225. for (let user of userList) {
  226. let payload = assign({
  227. streamID: ''
  228. }, user)
  229. for (let stream of this.allStreamList) {
  230. if (user.userID == stream.user.userID) {
  231. payload.streamID = stream.streamID
  232. if (!this.playerStreamID) {
  233. this.playerStreamID = stream.streamID;
  234. this.tensile(payload)
  235. }
  236. }
  237. }
  238. const index = this.allUserViewObjectList.findIndex(item => item.userID === payload
  239. .userID)
  240. console.log('-----------------------all user view object list ', index, payload)
  241. if (index === -1) {
  242. this.allUserViewObjectList.push(payload);
  243. }
  244. }
  245. } else if (updateType == ZegoUpdateType.Delete) {
  246. this.allUserViewObjectList = this.allUserViewObjectList.filter((object) => {
  247. for (let user of userList) {
  248. if (user.userID == object.userID) {
  249. return false;
  250. }
  251. }
  252. return true;
  253. });
  254. }
  255. if (updateType == "1") {
  256. // 有其他用户退出房间
  257. this.offVideo()
  258. }
  259. });
  260. // 房间内其他用户推的流变化通知
  261. this.engine.on("roomStreamUpdate", (roomID, updateType, streamList) => {
  262. // if (updateType == "0") {
  263. // // 流新增,开始拉流
  264. // this.tensile(streamList[0].streamID)
  265. // } else if (updateType == "1") {
  266. // // 流删除,停止拉流
  267. // this.notTensile(streamList[0].streamID);
  268. // }
  269. console.log('进入了啊BBBBBBBBBBBBBBBBBBBBBBB')
  270. console.log('---------updateType-----------', updateType)
  271. console.log('---------streamList-----------', streamList)
  272. console.log('---------ZegoUpdateType-----------', ZegoUpdateType)
  273. console.log('---------ZegoUpdateType.Add-----------', ZegoUpdateType.Add)
  274. console.log('---------ZegoUpdateType.Delete-----------', ZegoUpdateType.Delete)
  275. console.log('---------allUserViewObjectList-----------', this.allUserViewObjectList)
  276. if (updateType == ZegoUpdateType.Add) {
  277. for (let object of this.allUserViewObjectList) {
  278. for (let stream of streamList) {
  279. if (object.userID == stream.user.userID) {
  280. object.streamID = stream.streamID;
  281. if (!this.playerStreamID) {
  282. this.playerStreamID = stream.streamID
  283. this.tensile(object)
  284. }
  285. }
  286. }
  287. }
  288. this.allStreamList = this.allStreamList.concat(streamList);
  289. console.log(this.allStreamList, '-------------')
  290. } else if (updateType == ZegoUpdateType.Delete) {
  291. for (let object of this.allUserViewObjectList) {
  292. for (let stream of streamList) {
  293. if (object.streamID == stream.streamID) {
  294. object.streamID = undefined;
  295. }
  296. }
  297. }
  298. this.allStreamList = this.allStreamList.filter((object) => {
  299. for (let stream of streamList) {
  300. if (object.streamID == stream.streamID) {
  301. return false;
  302. }
  303. }
  304. return true;
  305. });
  306. }
  307. });
  308. // 拉流质量回调
  309. this.engine.on("playerQualityUpdate", (streamID, quality) => {
  310. console.log('拉流质量回调');
  311. console.log(streamID, quality);
  312. // if (this.videoShow == false) {
  313. // setTimeout(() => {
  314. // that.videoShow = true
  315. // console.log(that.videoShow, '要打开推流的视频窗口了啊')
  316. // }, 3500)
  317. // }
  318. });
  319. // 拉流后的事件回调
  320. this.engine.on("playerStateUpdate", (streamID, state, errorCode, extendedData) => {
  321. console.warn('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>' + '拉流后的事件触发了start')
  322. console.warn(streamID, state, errorCode, extendedData, this.playerStreamID)
  323. console.warn('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>' + '拉流后的事件触发end')
  324. })
  325. this.engine.on('playerRecvVideoFirstFrame', (streamID) => {
  326. console.warn('playerRecvVideoFirstFrame- 拉流端视频接收首帧回调', streamID)
  327. })
  328. this.engine.on('playerRenderVideoFirstFrame', (streamID) => {
  329. console.warn('playerRenderVideoFirstFrame- 拉流端渲染完视频首帧回调', streamID)
  330. })
  331. this.engine.on('publisherStateUpdate', (streamID, state, errorCode, extendedData) => {
  332. console.warn('publisherStateUpdate 推流状态回调', streamID, state, errorCode, extendedData)
  333. })
  334. this.engine.on('publisherCapturedVideoFirstFrame', (channel) => {
  335. console.warn('publisherCapturedVideoFirstFrame 推流端视频采集首帧回调', channel)
  336. })
  337. this.engine.on('remoteCameraStateUpdate', (streamID, state) => {
  338. console.warn('remoteCameraStateUpdate 远端摄像头设备状态通知', streamID, state)
  339. })
  340. },
  341. tensile(item) {
  342. console.warn('---------------------------- 拉流start -----------------------')
  343. console.log(this.allStreamList)
  344. console.log(item, item.streamID)
  345. console.log(this.allUserViewObjectList)
  346. // 拉流点击快于推流时,需要等流更新后重新拉
  347. // startPlayingStream 在 play.js文件
  348. // this.startPlayingStream(this.playStreamID);
  349. this.engine.startPlayingStream(item.streamID)
  350. console.warn('---------------------------- 拉流start1 -----------------------', this.playerStreamID)
  351. console.warn('---------------------------- 拉流start end -----------------------')
  352. },
  353. // 停止拉流
  354. notTensile(roomId) {
  355. this.serveViewModeIndex = roomId
  356. this.engine.stopPlayingStream(roomId)
  357. },
  358. // 推流 - 展示视图 - 本地视频流
  359. onPublish() {
  360. if (this.isPublishingStream) {
  361. // 停止推流
  362. this.engine.stopPreview();
  363. this.engine.stopPublishingStream();
  364. } else {
  365. console.log('开始推流了啊', this.isPublishingStream)
  366. // 开始推流
  367. this.engine.startPreview();
  368. // 设置编码格式
  369. // let videoConfig = {};
  370. // videoConfig.codecID = ZegoVideoCodecID.VP8;
  371. // this.engine.setVideoConfig(videoConfig);
  372. // 设置编码格式
  373. // this.engine.startPublishingStream(this.videoObj.room_id);
  374. console.log(this.userid)
  375. this.engine.startPublishingStream(this.userid).then(res => {
  376. console.log(res, 'start publish stream ------------------1')
  377. }).catch(err => {
  378. console.error(err)
  379. });;
  380. // this.videoShow = true
  381. }
  382. this.isPublishingStream = !this.isPublishingStream;
  383. },
  384. // 挂断退出
  385. async offVideo(isRefused = false, isNotice = true) {
  386. console.warn('进入了啊offVideo', this.engine)
  387. this.updateIsRefusedCall(false);
  388. this.updateTalkingStatus(false);
  389. this.updateOccupyedStatus(false);
  390. if (!this.engine) {
  391. console.warn('挂断退出 ------- 12131312')
  392. this.isPendingExit = true;
  393. } else {
  394. this.isPendingExit = false;
  395. }
  396. var that = this
  397. if (that.videoObj && !that.videoObj.room_id || !that.videoObj) {
  398. return
  399. }
  400. this.engine.logoutRoom(that.videoObj.room_id); // 退出房间
  401. this.$refs.uToast.show({
  402. type: 'default',
  403. message: isRefused ? '对方正忙' : '结束视频通话!',
  404. })
  405. setTimeout(() => {
  406. that.videoShow = true
  407. that.allStreamList = [];
  408. that.allUserViewObjectList = [];
  409. var obj = {}
  410. // obj = {
  411. // 'action': 'read', // 动作标识,必填
  412. // 'send_user_id': that.videoObj.user_id, // 接收人用户id, 非必填
  413. // 'data': {}
  414. // }
  415. console.warn('-----------off video 接收人用户id-------------', that.videoObj.user_id, API)
  416. obj = {
  417. 'action': 'list', // 动作标识,必填
  418. 'type': '当前为挂断',
  419. 'recv_user_id': that.videoObj.user_id, // 接收人用户id, 非必填
  420. 'data': {}
  421. }
  422. if (!isRefused) {
  423. API.socketTask.send({
  424. data: JSON.stringify(obj),
  425. async success(res) {
  426. console.log("消息发送成功");
  427. },
  428. });
  429. }
  430. if (isNotice) {
  431. console.log(this.videoObj, 'is notice')
  432. if (!that.currentReceiveUserID) {
  433. console.warn('当前连接的用户不存在,不是本人发起的通话.')
  434. } else {
  435. const socketData = {
  436. action: 'reject_video', // 动作标识,必填
  437. recv_user_id: that.currentReceiveUserID, // 接收人用户id, 非必填
  438. data: {},
  439. }
  440. API.socketTask.send({
  441. data: JSON.stringify(socketData),
  442. async success(res) {
  443. console.log('reject_video 消息发送成功 reject_video')
  444. },
  445. })
  446. }
  447. }
  448. this.handleBack()
  449. }, 1500)
  450. // uni.redirectTo({
  451. // url: "/pages/response/index"
  452. // })
  453. },
  454. // 删除this.engine对象, 删除即构
  455. async destroyEngine() {
  456. this.engine = undefined;
  457. ZegoExpressEngine.destroyEngine();
  458. },
  459. handleBack() {
  460. const pages = getCurrentPages();
  461. if (pages.length < 3) {
  462. uni.switchTab({
  463. url: '/pages/response/index',
  464. success(res) {
  465. console.warn(res, '返回上一页成功,当前页面:视频通话页面 ')
  466. },
  467. fail(err) {
  468. console.error(err)
  469. },
  470. complete() {
  471. console.warn('返回上一页执行完成,当前页面:视频通话页面 ')
  472. }
  473. })
  474. } else {
  475. uni.navigateBack({
  476. delta: 1
  477. })
  478. }
  479. }
  480. },
  481. async onLoad(optinos) {
  482. console.log('onload -------------- 1213', this.isVideoCallRefused)
  483. if (this.isVideoCallRefused) {
  484. uni.$u.toast('对方正忙')
  485. setTimeout(() => {
  486. this.handleBack();
  487. }, 1500)
  488. return
  489. }
  490. var that = this
  491. that.videoObj = JSON.parse(optinos.videoObj)
  492. // // console.log(that.videoObj)
  493. // // 即构 - 视频通话
  494. // // 获取系统信息同步接口
  495. if (uni.getSystemInfoSync().platform === 'android') {
  496. await permision.requestAndroidPermission('android.permission.RECORD_AUDIO');
  497. await permision.requestAndroidPermission('android.permission.CAMERA');
  498. }
  499. await this.setup()
  500. this.onPublish(); // 推流
  501. uni.getSystemInfo({ //异步获取。
  502. success(res) {
  503. // that.phoneHeight = res.windowHeight - 13; //窗口高度
  504. that.phoneHeight = res.windowHeight; //窗口高度
  505. }
  506. });
  507. },
  508. async onShow() {
  509. await this.setup();
  510. },
  511. // 监听页面返回
  512. onBackPress() {
  513. console.log('返回了!')
  514. this.offVideo(); // 挂断退出
  515. this.destroyEngine(); // 删除this.engine对象,删除即构
  516. },
  517. // 页面卸载 - 生命周期
  518. onUnload() {
  519. this.updateTalkingStatus(false);
  520. this.destroyEngine();
  521. console.log('onUnload');
  522. },
  523. }
  524. </script>
  525. <style lang="scss">
  526. .video_box {
  527. background: #000;
  528. width: 1200rpx;
  529. position: relative;
  530. z-index: 0;
  531. // .video_you {
  532. .video_me {
  533. background: #000;
  534. width: 150px;
  535. height: 200px;
  536. position: fixed;
  537. right: 0;
  538. top: 65px;
  539. z-index: 2000;
  540. }
  541. .video_you {
  542. background: #000;
  543. width: 1200rpx;
  544. height: 600rpx;
  545. position: absolute;
  546. right: 0;
  547. bottom: 0;
  548. z-index: 1;
  549. }
  550. .video_btn {
  551. position: fixed;
  552. bottom: 35rpx;
  553. left: 0;
  554. width: 750rpx;
  555. .video_imgList {
  556. display: flex;
  557. flex-direction: row;
  558. justify-content: space-around;
  559. // 翻转
  560. .video_imgList1 {
  561. .video_view {
  562. background: rgba(0, 0, 0, .5);
  563. border-radius: 50px;
  564. width: 50px;
  565. height: 50px;
  566. margin: 0 auto;
  567. .video_view_img {
  568. height: 15px;
  569. width: 20px;
  570. margin: 36rpx 0 0 33rpx;
  571. }
  572. }
  573. .video_view_txt {
  574. color: #fff;
  575. font-size: 14px;
  576. line-height: 30px;
  577. text-align: center;
  578. }
  579. }
  580. // 语音通话
  581. .video_imgList2 {
  582. .video_view {
  583. background: rgba(0, 0, 0, .5);
  584. border-radius: 50px;
  585. width: 50px;
  586. height: 50px;
  587. margin: 0 0 0 30rpx;
  588. .video_view_img {
  589. height: 25px;
  590. width: 25px;
  591. margin: 24rpx 0 0 25rpx;
  592. }
  593. }
  594. .video_view_txt {
  595. color: #fff;
  596. font-size: 14px;
  597. line-height: 30px;
  598. text-align: center;
  599. }
  600. }
  601. }
  602. }
  603. }
  604. </style>