detailmetting.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. <template>
  2. <div id="detailmetting">
  3. <el-row>
  4. <el-col :span="24" class="info">
  5. <el-col :span="4" class="left">
  6. <el-col :span="24" class="leftTop">
  7. <el-image :src="roomInfo.filedir"></el-image>
  8. <p>{{ roomInfo.title }}</p>
  9. <p>{{ roomInfo.content }}</p>
  10. </el-col>
  11. <el-col :span="24" class="leftDown">
  12. <el-col :span="8" class="btn" @click.native="shexiangBtn()">
  13. <i class="iconfont iconshexiangtou"></i>
  14. <p>摄像头</p>
  15. </el-col>
  16. <el-col :span="8" class="btn" @click.native="tianchongBtn()">
  17. <i class="iconfont iconmaikefeng-tianchong"></i>
  18. <p>麦克风</p>
  19. </el-col>
  20. <el-col :span="8" class="btn" @click.native="lookuserBtn()">
  21. <i class="el-icon-user"></i>
  22. <p>成员</p>
  23. </el-col>
  24. </el-col>
  25. </el-col>
  26. <el-col :span="20" class="right">
  27. <el-col :span="24" class="rightTop">
  28. <span @click="liveon"><i class="iconfont iconshexiangtou"></i>直播</span>
  29. <span @click="liveclose">关闭</span>
  30. </el-col>
  31. <el-col :span="18" class="video">
  32. <el-col :span="24" class="videoMeet">
  33. <el-col :span="18" class="one">
  34. <div id="main-video" class="video-box col-div" style="justify-content: flex-end"></div>
  35. </el-col>
  36. <el-col :span="6" class="two">
  37. <el-col :span="24" class="twoOne">
  38. <div id="look-video-1" class="video-box col-div lookvideo" style="justify-content: flex-end"></div>
  39. </el-col>
  40. <el-col :span="24" class="twoOne">
  41. <div id="look-video-2" class="video-box col-div lookvideo" style="justify-content: flex-end"></div>
  42. </el-col>
  43. <el-col :span="24" class="twoOne">
  44. <div id="look-video-3" class="video-box col-div lookvideo" style="justify-content: flex-end"></div>
  45. </el-col>
  46. </el-col>
  47. <el-col :span="6" class="three">
  48. <div id="look-video-4" class="video-box col-div lookvideo" style="justify-content: flex-end"></div>
  49. </el-col>
  50. <el-col :span="6" class="three">
  51. <div id="look-video-5" class="video-box col-div lookvideo" style="justify-content: flex-end"></div>
  52. </el-col>
  53. <el-col :span="6" class="three">
  54. <div id="look-video-6" class="video-box col-div lookvideo" style="justify-content: flex-end"></div>
  55. </el-col>
  56. <el-col :span="6" class="three">
  57. <div id="look-video-7" class="video-box col-div lookvideo" style="justify-content: flex-end"></div>
  58. </el-col>
  59. </el-col>
  60. </el-col>
  61. <el-col :span="6" class="noVideo">
  62. <el-col :span="24" class="chatList">
  63. <el-col :span="24" class="list" v-for="(item, index) in dataList" :key="index">
  64. <p>
  65. <span :class="item.sendname == user.name ? 'selfColor' : ''">{{ item.sendname }}</span>
  66. <span>{{ item.content }}</span>
  67. </p>
  68. </el-col>
  69. </el-col>
  70. <el-col :span="24" class="chatInput">
  71. <el-col :span="19" class="input">
  72. <el-input type="textarea" maxlength="5000" show-word-limit v-model="content"></el-input>
  73. </el-col>
  74. <el-col :span="5" class="btn">
  75. <el-button type="primary" size="mini" @click="chatCreate">发送</el-button>
  76. </el-col>
  77. </el-col>
  78. </el-col>
  79. <el-col :span="24" class="rightDown"> <!-- 开始直播 --> </el-col>
  80. </el-col>
  81. </el-col>
  82. </el-row>
  83. <el-dialog title="摄像头" :visible.sync="shexiangDia" width="30%" :before-close="handleClose">
  84. <el-select @change="cameraChange" v-model="cameraId" filterable placeholder="请选择摄像头">
  85. <el-option v-for="item in cameras" :key="item.deviceId" :label="item.label" :value="item.deviceId"> </el-option>
  86. </el-select>
  87. </el-dialog>
  88. <el-dialog title="麦克风" :visible.sync="tianchongDia" width="30%" :before-close="handleClose">
  89. <el-select @change="micrChange" v-model="microphoneId" filterable placeholder="请选择麦克风">
  90. <el-option v-for="item in microphones" :key="item.deviceId" :label="item.label" :value="item.deviceId"> </el-option>
  91. </el-select>
  92. </el-dialog>
  93. <el-dialog title="成员" :visible.sync="lookuserDia" width="30%" :before-close="handleClose">
  94. <el-row>
  95. <el-col :span="24" class="chatList">
  96. <el-col :span="24" class="list" v-for="(item, index) in userList" :key="index">
  97. <p>
  98. <i class="el-icon-user"></i>
  99. <span class="selfColor">{{ item.username }}</span>
  100. <span v-if="item.switchrole === 'anchor'"
  101. ><el-button type="primary" size="mini" @click="lookuserUpdate(item.id, 'audience')">移除</el-button></span
  102. >
  103. <span v-else><el-button type="primary" size="mini" @click="lookuserUpdate(item.id, 'anchor')">连麦</el-button></span>
  104. </p>
  105. </el-col>
  106. </el-col>
  107. </el-row>
  108. </el-dialog>
  109. </div>
  110. </template>
  111. <script>
  112. import Vue from 'vue';
  113. import { mapState, createNamespacedHelpers } from 'vuex';
  114. const { mapActions: gensign } = createNamespacedHelpers('gensign');
  115. const { mapActions: chat } = createNamespacedHelpers('chat');
  116. const { mapActions: lookuser } = createNamespacedHelpers('lookuser');
  117. import TRTC from 'trtc-js-sdk';
  118. export default {
  119. name: 'detailmetting',
  120. props: {
  121. roomInfo: null,
  122. },
  123. components: {},
  124. data: function() {
  125. return {
  126. // 摄像头
  127. shexiangDia: false,
  128. cameraId: '',
  129. cameras: [],
  130. // 麦克风
  131. tianchongDia: false,
  132. microphoneId: '',
  133. microphones: [],
  134. client_: '',
  135. localStream_: '',
  136. sdkAppId_: '1400380125',
  137. content: '',
  138. userId_: '',
  139. open_: false,
  140. dataList: [],
  141. index: 0,
  142. userList: [],
  143. lookuserDia: false,
  144. isanchor: true,
  145. };
  146. },
  147. created() {
  148. this.initclient();
  149. this.getDevices();
  150. this.chatSearch();
  151. },
  152. mounted() {
  153. this.channel();
  154. },
  155. methods: {
  156. ...gensign(['gensignFetch']),
  157. ...chat(['query', 'create', 'fetch']),
  158. ...lookuser(['lookquery', 'lookupdate']),
  159. async lookuserBtn() {
  160. this.lookuserDia = true;
  161. this.lookuserSearch();
  162. },
  163. async lookuserSearch({ skip = 0, limit = 1000 } = {}) {
  164. const info = { roomid: this.id };
  165. let res = await this.lookquery({ skip, limit, ...info });
  166. this.$set(this, `userList`, res.data);
  167. },
  168. async lookuserUpdate(_id, _switchrole) {
  169. console.log(_id);
  170. let data = {};
  171. data.id = _id;
  172. data.switchrole = _switchrole;
  173. const res = await this.lookupdate(data);
  174. if (this.$checkRes(res)) {
  175. console.log(res.data);
  176. this.$message({
  177. message: '操作成功',
  178. type: 'success',
  179. });
  180. this.lookuserSearch();
  181. } else {
  182. this.$message.error(res.errmsg);
  183. }
  184. },
  185. async chatSearch({ skip = 0, limit = 1000 } = {}) {
  186. const info = { roomid: this.id };
  187. let res = await this.query({ skip, limit, ...info });
  188. this.$set(this, `dataList`, res.data);
  189. },
  190. async chatCreate() {
  191. let data = {};
  192. data.roomid = this.id;
  193. data.type = '1';
  194. data.content = this.content;
  195. data.sendid = this.user.uid;
  196. data.sendname = this.user.name;
  197. const res = await this.create(data);
  198. if (this.$checkRes(res)) {
  199. console.log(res.data);
  200. }
  201. },
  202. channel() {
  203. console.log('in function:');
  204. this.$stomp({
  205. [`/exchange/public_chat_` + this.id]: this.onMessage,
  206. });
  207. },
  208. onMessage(message) {
  209. // console.log('receive a message: ', message.body);
  210. let body = _.get(message, 'body');
  211. if (body) {
  212. body = JSON.parse(body);
  213. this.dataList.push(body);
  214. this.content = '';
  215. }
  216. // const { content, contenttype, sendid, sendname, icon, groupid, sendtime, type } = message.headers;
  217. // let object = { content, contenttype, sendid, sendname, icon, groupid, sendtime, type };
  218. // this.list.push(object);
  219. },
  220. async getDevices() {
  221. this.cameras = await TRTC.getCameras();
  222. this.microphones = await TRTC.getMicrophones();
  223. },
  224. async initclient() {
  225. console.log(this.user.uid);
  226. this.userId_ = this.user.uid;
  227. const res = await this.gensignFetch({ userid: this.userId_ });
  228. if (this.$checkRes(res)) {
  229. console.log(res.data);
  230. this.client_ = TRTC.createClient({
  231. useCloud: 1,
  232. mode: 'live',
  233. sdkAppId: this.sdkAppId_,
  234. userId: this.userId_,
  235. userSig: res.data,
  236. });
  237. }
  238. },
  239. async liveon() {
  240. this.open_ = true;
  241. console.log('8888--' + this.userId_);
  242. if (this.cameraId === '' || this.microphoneId === '') {
  243. this.$message({
  244. message: '请选择摄像头和麦克风',
  245. type: 'warning',
  246. });
  247. return;
  248. }
  249. await this.client_.join({ roomId: this.name, role: 'anchor' });
  250. this.localStream_ = await TRTC.createStream({
  251. audio: true,
  252. video: true,
  253. cameraId: this.cameraId,
  254. microphoneId: this.microphoneId,
  255. userId: this.userId_,
  256. });
  257. this.localStream_.setVideoProfile('480p');
  258. await this.localStream_.initialize();
  259. console.log('initialize local stream success');
  260. // publish the local stream
  261. await this.client_.publish(this.localStream_);
  262. this.localStream_.play('main-video');
  263. //$('#mask_main').appendTo($('#player_' + this.localStream_.getId()));
  264. // 订阅其他用户音视频
  265. this.client_.on('stream-subscribed', event => {
  266. const remoteStream = event.stream;
  267. // 远端流订阅成功,播放远端音视频流
  268. console.log('111' + remoteStream.getType());
  269. this.index = this.index + 1;
  270. console.log('111--index--->' + this.index);
  271. if (this.index < 8) {
  272. const id_ = 'look-video-' + this.index;
  273. remoteStream.play(id_);
  274. }
  275. });
  276. // 监听远端流增加事件
  277. this.client_.on('stream-added', event => {
  278. const remoteStream = event.stream;
  279. console.log('222' + remoteStream.getType());
  280. // 订阅远端音频和视频流
  281. this.client_.subscribe(remoteStream, { audio: true, video: true }).catch(e => {
  282. console.error('failed to subscribe remoteStream');
  283. });
  284. });
  285. this.client_.on('mute-video', event => {
  286. const remoteStream = event.stream;
  287. // 订阅远端音频和视频流
  288. if (this.index > 0) {
  289. this.index = this.index - 1;
  290. }
  291. console.log('333--index---->' + this.index);
  292. });
  293. },
  294. async liveclose() {
  295. // 关闭视频通话
  296. console.log(this.open_);
  297. if (this.open_) {
  298. const videoTrack = this.localStream_.getVideoTrack();
  299. if (videoTrack) {
  300. this.localStream_.removeTrack(videoTrack).then(() => {
  301. console.log('remove video call success');
  302. // 关闭摄像头
  303. videoTrack.stop();
  304. this.client_.unpublish(this.localStream_).then(() => {
  305. // 取消发布本地流成功
  306. });
  307. });
  308. }
  309. }
  310. },
  311. async cameraChange() {
  312. //await this.localStream_.switchDevice('video', this.cameraId);
  313. },
  314. async micrChange() {
  315. //await this.localStream_.switchDevice('audio', this.microphoneId);
  316. },
  317. // 选择打开摄像头
  318. shexiangBtn() {
  319. this.shexiangDia = true;
  320. },
  321. // 选择打开麦克风
  322. tianchongBtn() {
  323. this.tianchongDia = true;
  324. },
  325. // 关闭摄像头&麦克风
  326. handleClose(done) {
  327. done();
  328. },
  329. },
  330. computed: {
  331. ...mapState(['user']),
  332. id() {
  333. return this.$route.query.id;
  334. },
  335. name() {
  336. return this.$route.query.name;
  337. },
  338. pageTitle() {
  339. return `${this.$route.meta.title}`;
  340. },
  341. },
  342. metaInfo() {
  343. return { title: this.$route.meta.title };
  344. },
  345. };
  346. </script>
  347. <style lang="less" scoped>
  348. .info {
  349. background-color: #2a2b30;
  350. min-height: 840px;
  351. .left {
  352. .leftTop {
  353. min-height: 540px;
  354. padding: 0 15px;
  355. margin: 20px 0;
  356. p {
  357. color: #ccc;
  358. }
  359. p:nth-child(2) {
  360. padding: 10px 0;
  361. }
  362. p:nth-child(3) {
  363. line-height: 25px;
  364. }
  365. }
  366. .leftDown {
  367. padding: 10px 0 0 0;
  368. border-top: 1px solid #000;
  369. .btn {
  370. margin: 0 0 15px 0;
  371. color: #cccccc;
  372. text-align: center;
  373. }
  374. .btn:hover {
  375. cursor: pointer;
  376. }
  377. }
  378. }
  379. .right {
  380. .rightTop {
  381. height: 100px;
  382. background-color: #232428;
  383. text-align: right;
  384. color: #ccc;
  385. line-height: 100px;
  386. span {
  387. margin: 0 10px 0 0;
  388. }
  389. span:hover {
  390. cursor: pointer;
  391. color: #409eff;
  392. }
  393. }
  394. .video {
  395. min-height: 640px;
  396. background-color: #000;
  397. overflow: hidden;
  398. position: relative;
  399. .videoMeet {
  400. .one {
  401. height: 480px;
  402. overflow: hidden;
  403. }
  404. .two {
  405. height: 480px;
  406. overflow: hidden;
  407. .twoOne {
  408. height: 160px;
  409. overflow: hidden;
  410. }
  411. }
  412. .three {
  413. height: 160px;
  414. overflow: hidden;
  415. }
  416. }
  417. }
  418. .noVideo {
  419. position: relative;
  420. min-height: 640px;
  421. background-color: #000;
  422. border-left: 1px solid #fff;
  423. color: #fff;
  424. .chatList {
  425. height: 598px;
  426. padding: 5px 5px 5px 0px;
  427. overflow-y: auto;
  428. .list {
  429. margin: 0 0 10px 0;
  430. span:first-child {
  431. float: left;
  432. width: 17%;
  433. text-align: center;
  434. overflow: hidden;
  435. text-overflow: ellipsis;
  436. white-space: nowrap;
  437. // font-weight: bold;
  438. }
  439. span:last-child {
  440. float: right;
  441. width: 82%;
  442. }
  443. }
  444. }
  445. .chatInput {
  446. position: absolute;
  447. bottom: 0;
  448. .el-button {
  449. width: 100%;
  450. padding: 13px 0;
  451. }
  452. }
  453. }
  454. .rightDown {
  455. height: 100px;
  456. background-color: #232428;
  457. }
  458. }
  459. }
  460. /deep/.el-dialog__body {
  461. min-height: 100px;
  462. }
  463. #main-video {
  464. width: 100%;
  465. height: 480px;
  466. min-height: 480px;
  467. grid-area: 1/1/3/4;
  468. }
  469. .lookvideo {
  470. width: 100%;
  471. height: 160px;
  472. min-height: 160px;
  473. grid-area: 1/1/3/4;
  474. }
  475. /deep/.el-textarea__inner {
  476. padding: 0 15px;
  477. line-height: 20px;
  478. border-radius: 0;
  479. }
  480. .selfColor {
  481. color: #ff0000;
  482. }
  483. </style>