| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333 |
- <template>
- <el-dialog :title="title" :visible.sync="visible" width="800px" :before-close="handleClose">
- <div class="chat-video">
- <div class="chat-video-box">
- <div class="chat-video-friend" v-loading="loading" element-loading-text="等待对方接听..." element-loading-spinner="el-icon-loading"
- element-loading-background="rgba(0, 0, 0, 0.9)">
- <video ref="friendVideo" autoplay=""></video>
- </div>
- <div class="chat-video-mine">
- <video ref="mineVideo" autoplay=""></video>
- </div>
- </div>
- <div class="chat-video-controllbar">
- <div v-show="state=='CONNECTING'" title="取消呼叫" class="icon iconfont icon-phone-reject reject" style="color: red;"
- @click="cancel()"></div>
- <div v-show="state=='CONNECTED'" title="挂断" class="icon iconfont icon-phone-reject reject" style="color: red;"
- @click="handup()"></div>
- </div>
- </div>
- </el-dialog>
- </template>
- <script>
- export default {
- name: 'chatVideo',
- props: {
- visible: {
- type: Boolean
- },
- friend: {
- type: Object
- },
- master: {
- type: Boolean
- },
- offer: {
- type: Object
- }
- },
- data() {
- return {
- stream: null,
- loading: false,
- peerConnection: null,
- state: 'NOT_CONNECTED',
- candidates: [],
- configuration: {
- iceServers: [
- { 'url': 'stun:stun.l.google.com:19302' },
- {
- 'url': 'turn:www.boxim.online:3478',
- 'credential': "admin123",
- 'username': "admin"
- }]
- }
- }
- },
- methods: {
- init() {
- if (!this.hasUserMedia() || !this.hasRTCPeerConnection()) {
- this.$message.error("您的浏览器不支持WebRTC");
- if (!this.master) {
- this.sendFailed("对方浏览器不支持WebRTC")
- }
- return;
- }
- // 打开摄像头
- this.openCamera((stream) => {
- // 初始化webrtc连接
- this.setupPeerConnection(stream);
- if (this.master) {
- // 发起呼叫
- this.call();
- } else {
- // 接受呼叫
- this.accept(this.offer);
- }
- this.timerx && clearInterval(this.timerx);
- this.timerx = setInterval(() => {
- console.log(this.peerConnection.iceConnectionState);
- }, 3000)
- });
- },
- openCamera(callback) {
- navigator.getUserMedia({
- video: true,
- audio: true
- },
- (stream) => {
- this.stream = stream;
- this.$refs.mineVideo.srcObject = stream;
- this.$refs.mineVideo.muted = true;
- callback(stream)
- },
- (error) => {
- this.$message.error("打开摄像头失败:" + error);
- callback()
- });
- },
- closeCamera() {
- if (this.stream) {
- this.stream.getVideoTracks().forEach((track) => {
- track.stop();
- });
- this.$refs.mineVideo.srcObject = null;
- this.stream = null;
- }
- },
- setupPeerConnection(stream) {
- this.peerConnection = new RTCPeerConnection(this.configuration);
- this.peerConnection.ontrack = (e) => {
- console.log("onaddstream")
- this.$refs.friendVideo.srcObject = e.streams[0];
- };
- this.peerConnection.onicecandidate = (event) => {
- if (event.candidate) {
- if (this.state == 'CONNECTED') {
- // 已连接,直接发送
- this.sendCandidate(event.candidate);
- } else {
- // 未连接,缓存起来,连接后再发送
- this.candidates.push(event.candidate)
- }
- }
- }
- if (stream) {
- stream.getTracks().forEach((track) => {
- this.peerConnection.addTrack(track, stream);
- });
- }
- this.peerConnection.IceConnectionStateChange
- },
- handleMessage(msg) {
- if (msg.type == this.$enums.MESSAGE_TYPE.RTC_ACCEPT) {
- this.peerConnection.setRemoteDescription(new RTCSessionDescription(JSON.parse(msg.content)));
- // 关闭等待提示
- this.loading = false;
- // 状态为连接中
- this.state = 'CONNECTED';
- // 发送candidate
- this.candidates.forEach((candidate) => {
- this.sendCandidate(candidate);
- })
- }
- if (msg.type == this.$enums.MESSAGE_TYPE.RTC_REJECT) {
- this.$message.error("对方拒绝了您的视频请求");
- this.peerConnection.close();
- // 关闭等待提示
- this.loading = false;
- // 状态为未连接
- this.state = 'NOT_CONNECTED';
- }
- if (msg.type == this.$enums.MESSAGE_TYPE.RTC_FAILED) {
- this.$message.error(msg.content)
- // 关闭等待提示
- this.loading = false;
- // 状态为未连接
- this.state = 'NOT_CONNECTED';
- }
- if (msg.type == this.$enums.MESSAGE_TYPE.RTC_CANDIDATE) {
- this.peerConnection.addIceCandidate(new RTCIceCandidate(JSON.parse(msg.content)));
- }
- if (msg.type == this.$enums.MESSAGE_TYPE.RTC_HANDUP) {
- this.$message.success("对方已挂断");
- this.close();
- }
- },
- call() {
- this.peerConnection.createOffer((offer) => {
- this.peerConnection.setLocalDescription(offer);
- this.$http({
- url: `/webrtc/private/call?uid=${this.friend.id}`,
- method: 'post',
- data: offer
- }).then(() => {
- this.loading = true;
- this.state = 'CONNECTING';
- });
- },
- (error) => {
- this.$message.error(error);
- });
- },
- accept(offer) {
- this.peerConnection.setRemoteDescription(new RTCSessionDescription(offer));
- this.peerConnection.createAnswer((answer) => {
- this.peerConnection.setLocalDescription(answer);
- this.$http({
- url: `/webrtc/private/accept?uid=${this.friend.id}`,
- method: 'post',
- data: answer
- })
- this.state = 'CONNECTED';
- },
- (error) => {
- this.$message.error(error);
- });
- },
- handup() {
- this.$http({
- url: `/webrtc/private/handup?uid=${this.friend.id}`,
- method: 'post'
- })
- this.close();
- this.$message.success("已挂断视频通话")
- },
- cancel() {
- this.$http({
- url: `/webrtc/private/cancel?uid=${this.friend.id}`,
- method: 'post'
- })
- this.close();
- this.$message.success("已停止呼叫视频通话")
- },
- sendFailed(reason) {
- this.$http({
- url: `/webrtc/private/failed?uid=${this.friend.id}&reason=${reason}`,
- method: 'post'
- })
- },
- sendCandidate(candidate) {
- this.$http({
- url: `/webrtc/private/candidate?uid=${this.friend.id}`,
- method: 'post',
- data: candidate
- })
- },
- close() {
- this.$emit("close");
- this.closeCamera();
- this.loading = false;
- this.state = 'NOT_CONNECTED';
- this.candidates = [];
- this.$store.commit("setUserState", this.$enums.USER_STATE.FREE);
- this.$refs.friendVideo.srcObject = null;
- this.peerConnection.close();
- this.peerConnection.onicecandidate = null;
- this.peerConnection.onaddstream = null;
- },
- handleClose() {
- if (this.state == 'CONNECTED') {
- this.handup()
- } else if (this.state == 'CONNECTING') {
- this.cancel();
- } else {
- this.close();
- }
- },
- hasUserMedia() {
- navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia ||
- navigator.msGetUserMedia;
- return !!navigator.getUserMedia;
- },
- hasRTCPeerConnection() {
- window.RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection;
- window.RTCSessionDescription = window.RTCSessionDescription || window.webkitRTCSessionDescription || window.mozRTCSessionDescription;
- window.RTCIceCandidate = window.RTCIceCandidate || window.webkitRTCIceCandidate || window.mozRTCIceCandidate;
- return !!window.RTCPeerConnection;
- }
- },
- watch: {
- visible: {
- handler(newValue, oldValue) {
- if (newValue) {
- this.init();
- // 用户忙状态
- this.$store.commit("setUserState", this.$enums.USER_STATE.BUSY);
- console.log(this.$store.state.userStore.state)
- }
- }
- }
- },
- computed: {
- title() {
- return `视频聊天-${this.friend.nickName}`;
- }
- }
- }
- </script>
- <style lang="scss">
- .chat-video {
- .chat-video-box {
- position: relative;
- border: #2C3E50 solid 1px;
- background-color: #eeeeee;
- .chat-video-friend {
- height: 600px;
- video {
- width: 100%;
- height: 100%;
- }
- }
- .chat-video-mine {
- position: absolute;
- z-index: 99999;
- width: 200px;
- right: 0;
- bottom: 0;
- video {
- width: 100%;
- }
- }
- }
- .chat-video-controllbar {
- display: flex;
- justify-content: space-around;
- padding: 10px;
- .icon {
- font-size: 50px;
- cursor: pointer;
- }
- }
- }
- </style>
|