2021/5/12 vue制作实时音视频

需求: 视频通话

想法: 不会做,笑死   (采用腾讯云第三方,或者使用vue + webRtc + websocket) 暂时只做到一对一,而且还有一些问题,求指教

有想法的可以加个好友互相讨论一下

 

1. 接入腾讯云 - 直接在mounted里引入createClient方法就可以了

<template>
  <!-- 视频会诊 -->
  <div class="videoCon">
    <div class="topBar">
      <el-form
        :inline="true"
        :model="formInline"
        class="demo-form-inline"
        :label-position="labelPosition"
        label-width="100px"
      >
        <el-form-item label="申请医生:">
          <el-input v-model="formInline.doctor" size="mini"></el-input>
        </el-form-item>
        <el-form-item label="手机号:">
          <el-input v-model="formInline.phone" size="mini"></el-input>
        </el-form-item>
        <el-form-item label="请求时间:">
          <el-input v-model="formInline.time" size="mini"></el-input>
        </el-form-item>
        <el-form-item label="申请医院:">
          <el-input v-model="formInline.hospital" size="mini"></el-input>
        </el-form-item>
      </el-form>
      <div class="rightBox">
        <div class="recording">
          <i></i>
          <span>录制中 :</span>
          <span>1:20:02</span>
        </div>
        <el-button type="primary" @click="signOut">退出</el-button>
      </div>
    </div>
    <div class="main">
      <div class="video">
        <div class="bigScreen">
          <span class="iconfont iconicon-03-17"></span>
          
        </div>
        <div style="width: 22px"></div>
        <div class="smallScreem">
          <div>
            <span class="iconfont iconicon-03-17"></span>
            <span class="iconfont iconicon-03-16"></span>
            <div class="center-page">
              <div v-html="remoteStream"
                  :class="remoteStream?'distant-stream':''">
              </div>
            </div>
          </div>
          <!-- <div>
            <span class="iconfont iconicon-03-17"></span>
            <span class="iconfont iconicon-03-16"></span>
          </div>
          <div>
            <span class="iconfont iconicon-03-17"></span>
            <span class="iconfont iconicon-03-16"></span>
          </div>
          <div>
            <span class="iconfont iconicon-03-16"></span>
            <span class="iconfont iconicon-03-17"></span>
          </div>
          <div>
            <span class="iconfont iconicon-03-16"></span>
            <span class="iconfont iconicon-03-17"></span>
          </div>
          <div>
            <span class="iconfont iconicon-03-17"></span>
            <span class="iconfont iconicon-03-16"></span>
          </div>
          <div>
            <span class="iconfont iconicon-03-17"></span>
            <span class="iconfont iconicon-03-16"></span>
          </div>
          <div>
            <span class="iconfont iconicon-03-17"></span>
            <span class="iconfont iconicon-03-16"></span>
          </div>
          <div>
            <span class="iconfont iconicon-03-17"></span>
            <span class="iconfont iconicon-03-16"></span>
          </div> -->
          <div style="marginTop: 10px;">
            <span class="iconfont iconicon-03-17"></span>
            <span class="iconfont iconicon-03-16" @click="snapshot"></span>
            <div id='local_stream'
              class="local-stream">
            </div>
          </div>
        </div>
      </div>
      <div class="operation">
        <div>
          <span class="iconfont iconicon-03-31"></span>
          <p>控制</p>
        </div>
        <div>
          <span class="iconfont iconicon-03-30"></span>
          <p>测量</p>
        </div>
        <div>
          <span class="iconfont iconicon-03-42"></span>
          <p>注释</p>
        </div>
        <div>
          <span class="iconfont iconicon-03-34"></span>
          <p>截图</p>
        </div>
        <div>
          <span class="iconfont iconicon-03-32"></span>
          <p>截图附件</p>
        </div>
        <div>
          <span class="iconfont iconicon-03-33"></span>
          <p>患者查看详情</p>
        </div>
        <div>
          <span class="iconfont iconicon-03-15"></span>
          <p>邀请</p>
        </div>
        <div>
          <span class="iconfont iconicon-03-29"></span>
          <p>聊天</p>
        </div>
        <div>
          <span class="iconfont iconicon-03-28"></span>
          <p>声音</p>
        </div>
        <div>
          <span class="iconfont iconicon-03-14"></span>
          <p>麦克风</p>
        </div>
        <div>
          <span class="iconfont iconicon-03-10"></span>
          <p>视频</p>
        </div>
        <div>
          <span class="iconfont iconicon-03-18"></span>
          <p>录制</p>
        </div>
      </div>
    </div>
    

    <img src="" alt="" ref="picture">
  </div>
</template>

<script>
// 一定要引入这个-去下个demo里面就有
import LibGenerateTestUserSig from '@/utils/lib-generate-test-usersig.min';

import TRTC from 'trtc-js-sdk';
export default {
  data() {
    return {
      formInline: {
        doctor: "",
        phone: "",
        time: "",
        hospital: "",
      },
      labelPosition: "right",


      userId: 'user_' + parseInt(Math.random() * 100000000),//用户id --可更改
      roomId: 88888,//房间号--加入相同房间才能聊
      client: '',//客户端服务
      remoteStream: '',//远方播放流
      localStream: '',//本地流
    };
  },
  methods: {
    // 退出
    signOut() {
      Bus.$emit('code', "true");
      this.$router.go(-1);
    },
    //创建链接
    createClient (userId) {
      //获取签名
      const config = this.genTestUserSig(userId);
      const sdkAppId = config.sdkAppId;
      const userSig = config.userSig;
      this.client = TRTC.createClient({
        mode: 'videoCall',
        sdkAppId,
        userId,
        userSig
      });
      //注册远程监听,要放在加入房间前--这里用了发布订阅模式
      this.subscribeStream(this.client);
      //初始化后才能加入房间
      this.joinRoom(this.client, this.roomId);
    },
    //加入房间
    joinRoom (client, roomId) {
      client.join({ roomId }).then(() => {
          console.log('进房成功');
          //创建本地流
          this.createStream(this.userId);
          //播放远端流
          this.playStream(this.client);
      }).catch(error => {
          console.error('进房失败 ' + error);
        })
    },
    //创建本地音视频流
    createStream (userId) {
      const localStream = TRTC.createStream({ userId, audio: false, video: true });
      this.localStream =localStream;
     
      localStream
        .initialize().then(() => {
          console.log('初始化本地流成功');
          // 创建好后才能播放 本地流播放 local_stream 是div的id
          localStream.play('local_stream');
          //创建好后才能发布
          // this.$nextTick(() => {
            this.publishStream(localStream, this.client);
          // });
        }).catch(error => {
          console.error('初始化本地流失败 ' + error);
        })
    },
    //发布本地音视频流
    publishStream (localStream, client) {
      client.publish(localStream).then(() => {
          console.log('本地流发布成功');
        }).catch(error => {
          console.error('本地流发布失败 ' + error);
        });
    },
    //订阅远端流--加入房间之前
    subscribeStream (client) {
      client.on('stream-added', event => {
        console.log(client,'client9696');
        const remoteStream = event.stream;
        console.log('远端流增加: ' + remoteStream.getId());
        //订阅远端流
        client.subscribe(remoteStream);
      });
    },
    //播放远端流
    playStream (client) {
      client.on('stream-subscribed', event => {

        const remoteStream = event.stream;
        console.log('远端流订阅成功:' + remoteStream.getId());
        // 创建远端流标签,因为id是动态的,所以动态创建,用了v-html
        
        this.remoteStream = `<view id="${'remote_stream-' + remoteStream.getId()}"  ></view>`;
        
        //做了dom操作 需要使用$nextTick(),否则找不到创建的标签无法进行播放
        this.$nextTick(() => {
            //播放
          remoteStream.play('remote_stream-' + remoteStream.getId());
        })
      });
    },
    //退出音视频
    leaveRoom (client) {
      client
        .leave()
        .then(() => {
            console.log('退房成功')
          // 停止本地流,关闭本地流内部的音视频播放器
          this.localStream.stop();
          // 关闭本地流,释放摄像头和麦克风访问权限
          this.localStream.close();
          this.localStream = null;
          this.client = null
          // 退房成功,可再次调用client.join重新进房开启新的通话。
        })
        .catch(error => {
          console.error('退房失败 ' + error);
          // 错误不可恢复,需要刷新页面。
        });
    },
    //获取用户签名--前端测试用
    genTestUserSig (userID) {
      /**
       * 腾讯云 SDKAppId,需要替换为您自己账号下的 SDKAppId。
       *
       * 进入腾讯云实时音视频[控制台](https://console.cloud.tencent.com/rav ) 创建应用,即可看到 SDKAppId,
       * 它是腾讯云用于区分客户的唯一标识。
       */
      const SDKAPPID = 1400371155;
      /**
       * 签名过期时间,建议不要设置的过短
       * <p>
       * 时间单位:秒
       * 默认时间:7 x 24 x 60 x 60 = 604800 = 7 天
       */
      const EXPIRETIME = 604800;
      /**
       * 计算签名用的加密密钥,获取步骤如下:
       *
       * step1. 进入腾讯云实时音视频[控制台](https://console.cloud.tencent.com/rav ),如果还没有应用就创建一个,
       * step2. 单击“应用配置”进入基础配置页面,并进一步找到“帐号体系集成”部分。
       * step3. 点击“查看密钥”按钮,就可以看到计算 UserSig 使用的加密的密钥了,请将其拷贝并复制到如下的变量中
       *
       * 注意:该方案仅适用于调试Demo,正式上线前请将 UserSig 计算代码和密钥迁移到您的后台服务器上,以避免加密密钥泄露导致的流量盗用。
       * 文档:https://cloud.tencent.com/document/product/647/17275#Server
       */
      const SECRETKEY = "41b790cbcc2e6672ebd921abb4a2ad8a75e1c80705f787926ba5a77425f772af";

      // a soft reminder to guide developer to configure sdkAppId/secretKey
      if (SDKAPPID === "" || SECRETKEY === "") {
        alert(
          "请先配置好您的账号信息: SDKAPPID 及 SECRETKEY " +
          "\r\n\r\nPlease configure your SDKAPPID/SECRETKEY in js/debug/GenerateTestUserSig.js"
        );
      }
      const generator = new LibGenerateTestUserSig(SDKAPPID, SECRETKEY, EXPIRETIME);
      const userSig = generator.genTestUserSig(userID);
      return {
        sdkAppId: SDKAPPID,
        userSig: userSig
      };
    },
    // 获取详情
    async getDetails() {
      // this.roomId = this.$route.query.id;
      let id = this.$route.query.id;
      let res = await meetApi.meetInfoApply(id);
      if(res.code != 200) return;
      let s = res.data;
      this.formInline = {
        doctor: s.applyDoctorDicName,
        phone: s.applyDoctorPhone,
        time: s.applyDateTime,
        hospital: s.applyHospitalDicName
      };


      this.createClient(this.userId)
    },
    //截图
    snapshot() {
      let canvas = this.$refs.picture;
      canvas.width = 400;
      canvas.height = 300;
      canvas.getContext("2d").drawImage(this.localVideo, 0, 0, canvas.width, canvas.height);
    },
  },
  mounted() {
    //测试用,所以直接创建了,其他需求可自行更改
    this.getDetails();
  }
};
</script>

<style lang="scss" scoped>
.videoCon::-webkit-scrollbar {
  display: none;
}
.videoCon {
  height: 100%;
  width: 100%;
  overflow-y: scroll;
  .topBar {
    width: 100%;
    height: 135px;
    // opacity: 0.25;
    background: #42464d;
    box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.25);
    padding: 30px 32px;
    display: flex;
    justify-content: space-between;
    .demo-form-inline {
      width: 65%;
      .el-form-item {
        margin: 0 15px;
        width: 375px;
        .el-input__inner {
          background-color: #42464d;
          color: #fff;
        }
      }
    }
    .rightBox {
      width: 35%;
      display: flex;
      justify-content: space-between;
      .recording {
        font-size: 20px;
        color: #fff;
        font-weight: 700;
        margin-top: 52px;
        i {
          display: inline-block;
          width: 15px;
          height: 15px;
          background: #e60012;
          border-radius: 15px;
          margin-right: 16px;
        }
        span {
          margin-right: 5px;
        }
      }
    }
  }
  .main {
    // height: calc(100% - 135px);
    // height: 100%;
    width: 100%;
    background-color: #272e43;
    padding: 30px;
    .video {
      padding: 0 15px;
      display: flex;
      // justify-content: space-between;
      margin-bottom: 40px;
      .bigScreen {
        width: 1290px;
        height: 720px;
        background-color: #ccc;
        span {
          color: #a0a0a0;
          font-size: 48px;
          cursor: pointer;
        }
      }
      .smallScreem {
        width: 492px;
        height: 720px;
        display: flex;
        flex-wrap: wrap;
        justify-content: space-between;
        // align-content: space-between;
        div {
          width: 492px;
          height: 355px;
          background-color: #ccc;
          position: relative;
          span {
            color: #a0a0a0;
            font-size: 24px;
            cursor: pointer;
            float: right;
            z-index: 2;
          }
          //远端流
          .distant-stream {
            width: 100%;
            height: 100%;
            position: absolute;
            top: 0;
            left: 0;
            z-index: 1;
          }

          .iconicon-03-17 {
            position: absolute;
            z-index: 2;
          }

          .iconicon-03-16 {
            position: absolute;
            z-index: 2;
            left: 30px;
          }

          //本地流
          .local-stream {
            width: 100%;
            height: 100%;
            position: absolute;
            top: 0;
            left: 0;
            z-index: 1;
          }
        }
      }
    }
    .operation {
      height: 106px;
      width: 100%;
      background-color: #1d2335;
      border-radius: 20px;
      display: flex;
      align-items: center;
      justify-content: space-around;
      font-size: 20px;
      font-weight: 400;
      color: #fff;
      text-align: center;
      div {
        cursor: pointer;
        span {
          font-size: 36px;
        }
        p {
          margin-top: 12px;
        }
      }
    }
  }
}
</style>
<style lang="scss">
.videoCon {
  .demo-form-inline {
    .el-form-item {
      .el-form-item__label {
        color: #fff;
        font-size: 20px;
      }
      .el-input__inner {
        background-color: #42464d;
        border-color: #42464d;
        color: #fff;
        font-size: 20px;
        height: 40px;
      }
    }
  }
  .rightBox {
    .el-button--primary {
      background-color: #d33594;
      border-color: #d33594;
      width: 116px;
      height: 55px;
      margin-top: 10px;
      padding-top: 10px;
      span {
        font-size: 20px;
        color: #e6e6e6;
        font-weight: 700;
      }
    }
  }
}
</style>

2. 采用vue + webRtc + websocket制作

<template>
  <!-- 视频会诊 -->
  <div class="startConsultation" ref="imageWrapper">
    <div class="topBar">
      <el-form :inline="true" :model="formInline" class="demo-form-inline" :label-position="labelPosition" label-width="100px">
        <el-form-item label="申请医生:">
          <!-- <el-input v-model="formInline.doctor" size="mini"></el-input> -->
          <div>{{formInline.doctor}}</div>
        </el-form-item>
        <el-form-item label="手机号:">
          <!-- <el-input v-model="formInline.phone" size="mini"></el-input> -->
          <div>{{formInline.phone}}</div>
        </el-form-item>
        <el-form-item label="请求时间:">
          <!-- <el-input v-model="formInline.time" size="mini"></el-input> -->
          <div>{{formInline.time}}</div>
        </el-form-item>
        <el-form-item label="申请医院:">
          <!-- <el-input v-model="formInline.hospital" size="mini"></el-input> -->
          <div>{{formInline.hospital}}</div>
        </el-form-item>
      </el-form>
      <div class="rightBox">
        <div class="recording">
          <i></i>
          <span>录制中 :</span>
          <span>1:20:02</span>
        </div>
        <el-button type="primary" @click="signOut">退出</el-button>
      </div>
    </div>
    <div class="main">
      <div class="video">
        <div class="bigScreen">
          <span class="iconfont iconicon-03-17"></span>
        </div>
        <div style="width: 22px"></div>
        <div class="smallScreem">
          <div class="bigBox">
            <div class="icon">
              <span class="iconfont iconicon-03-16"></span>
              <span class="iconfont iconicon-03-17"></span>
            </div>
            <video id="remoteVideo"></video>
          </div>
          <div class="bigBox">
            <div class="icon">
              <span class="iconfont iconicon-03-16"></span>
              <span class="iconfont iconicon-03-17"></span>
            </div>
            <video id="localVideo" autoplay muted></video>
          </div>
        </div>
      </div>
      <div class="operation">
        <div>
          <span class="iconfont iconicon-03-31"></span>
          <p>控制</p>
        </div>
        <div>
          <span class="iconfont iconicon-03-30"></span>
          <p>测量</p>
        </div>
        <div>
          <span class="iconfont iconicon-03-42"></span>
          <p>注释</p>
        </div>
        <div @click="snapshot">
          <span class="iconfont iconicon-03-34"></span>
          <p>截图</p>
        </div>
        <div>
          <span class="iconfont iconicon-03-32"></span>
          <p>截图附件</p>
        </div>
        <div>
          <span class="iconfont iconicon-03-33"></span>
          <p>患者查看详情</p>
        </div>
        <div>
          <span class="iconfont iconicon-03-15"></span>
          <p>邀请</p>
        </div>
        <div>
          <span class="iconfont iconicon-03-29"></span>
          <p>聊天</p>
        </div>
        <div>
          <span class="iconfont iconicon-03-28"></span>
          <p>声音</p>
        </div>
        <div>
          <span class="iconfont iconicon-03-14"></span>
          <p>麦克风</p>
        </div>
        <div>
          <span class="iconfont iconicon-03-10"></span>
          <p>视频</p>
        </div>
        <div>
          <span class="iconfont iconicon-03-18"></span>
          <p>录制</p>
        </div>
      </div>
      <div class="logger"></div>
    </div>

  </div>
</template>

<script>
export default {
  data() {
    return {
      formInline: {
        doctor: "潘远航",
        phone: "13066990173",
        time: "2021-03-08 12:00:11",
        hospital: "普博医院",
      },
      labelPosition: "right",
      //websocket
      socket: null,
      //webrtc
      peer: null,
      //send data
      sendData: {
        Id: "1",
        SenderId: "1",
        Type: "join"
      },
      target: null,
      // videos
      localVideo: {},
      remoteVideo: {},
      logger: {},

      //source
      audioSourceOption: [],
      audioOutputOption: [],
      videoSourceOption: [],

      //Constraints(媒体约束)
      videoConstraints: '',
      // Media config
      constraints: {
        audio: {
          noiseSuppression: true,
          echoCancellation: true
        },
        video: {
          width: 1920,
          height: 1080,
          frameRate: 30,
          facingMode: "environment"
        }
      },
      // local video stream
      localStream: undefined,
      isOpenMediaStream: false
    }
  },
  async created() {
    this.sendData.Id = this.$route.query.id;
    this.sendData.SenderId = this.$route.query.sid;
    if (!this.sendData.SenderId) {
      this.sendData.SenderId = "1";
    }
    this.target = this.$route.query.t > 1 ? "answer" : "offer";
    console.info("target:", this.target);
    await this.getUserMedia();
    await this.getDevices();
    await this.getAudioVideo();
  },
  watch: {

  },
  mounted() {
    this.localVideo = document.getElementById("localVideo");
    this.remoteVideo = document.getElementById("remoteVideo");
    this.logger = document.querySelector('.logger');
    //获取浏览器支持的连接
    const PeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
    if (!PeerConnection) {
      this.error('浏览器不支持WebRTC!');
      return;
    }
    // stun 服务,如果要做到 NAT 穿透,还需要 turn 中转服务	
    let iceServer = { 
        "iceServers": [
            {	
                "url": "stun:stun.l.google.com:19302"	
            },
            // {	
            //     "url": "stun:192.168.1.162:3478"
            // },
        ]	
    };
    this.peer = new PeerConnection(iceServer);
    this.peer.ontrack = e => {
      if (e && e.streams) {
        this.log('收到对方音频/视频流数据...');
        remoteVideo.srcObject = e.streams[0];
        setTimeout(() => {
          remoteVideo.play();
        }, 150);
      }
    };

    this.peer.onicecandidate = e => {
      if (e.candidate) {
        this.log('搜集并发送候选人');
        this.sendData.Type = "send";
        this.sendData.Data = JSON.stringify({
          type: `${this.target}_ice`,
          iceCandidate: e.candidate
        });
        this.socket.send(JSON.stringify(this.sendData));
      } else {
        this.log('候选人收集完成!');
      }
    };


    this.log('信令通道(WebSocket)创建中......');
    var socketUrl = location.protocol + "//" + location.host + "/ws";
    socketUrl = socketUrl.replace("https", "wss").replace("http", "ws");
    this.log("连接地址:" + socketUrl);
    this.socket = new WebSocket(socketUrl);
    this.socket.onopen = () => {
      this.log('信令通道已打开!');
      this.log("当前角色: " + this.target);
      this.sendData.Type = "join";
      this.socket.send(JSON.stringify(this.sendData));
    }

    this.socket.onclose = (e) => {
      this.error("断开连接");
      console.log('断开连接', e);
    }

    this.socket.onerror = () => this.error('信令通道创建失败!');
    //设置别名
    let _this = this;
    this.socket.onmessage = e => {
      const { Type, Code, Msg, Data } = JSON.parse(e.data);
      if (Code != 200) {
        console.warn(Msg);
        return;
      }
      //加入
      if (Type === "join") {
        this.log("当前工作间在线人数:" + Data);
        if (!this.target) {
          this.target = Data > 1 ? "answer" : "offer";
        }
        this.startLive();
        return;
      }
      let resultData;
      if (typeof Data !== "object") {
        resultData = JSON.parse(Data);
      } else {
        resultData = Data;
      }
      const { type, sdp, iceCandidate } = resultData;
      if (type === 'answer') {
        _this.peer.setRemoteDescription(new RTCSessionDescription({ type, sdp }));
      } else if (type === 'answer_ice') {
        _this.peer.addIceCandidate(iceCandidate);
      } else if (type === 'offer') {
        this.startLive(new RTCSessionDescription({ type, sdp }));
      } else if (type === 'offer_ice') {
        _this.peer.addIceCandidate(iceCandidate);
      }
    };
  },
  beforeDestroy() {
    if (this.socket.readyState == WebSocket.OPEN) {
      this.sendData.Type = "leave";
      this.socket.send(JSON.stringify(this.sendData));
      this.socket.close();
    }
    if (this.isOpenMediaStream) {
      this.localStream.getVideoTracks()[0].stop();
      this.localStream.getAudioTracks()[0].stop();
    }
  },

  methods: {
    //拉起本地音视频流
    async startLocalMedia() {
      await this.getUserMedia();
      await this.getAudioVideo();
    },
    //拿到媒体流
    async getUserMedia() {
      // log(`Requesting  video stream`);
      navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
      if ("mediaDevices" in navigator) {
        try {
          const stream = await navigator.mediaDevices.getUserMedia(this.constraints);
          this.localVideo.srcObject = stream;
          this.localStream = stream;
          // log("Received local video stream");
        } catch (error) {
          // log(`getUserMedia error: ${error}`);
        }
      }
    },
    //拿到音视频轨道
    async getAudioVideo() {
      if (this.localStream == undefined) {
        await this.getUserMedia();
      }
      console.log("localStream", this.localStream);
      if (this.localStream !== undefined && this.localStream != null) {
        this.isOpenMediaStream = true;
      } else {
        this.isOpenMediaStream = false;
        return;
      }
      let videoTrack = this.localStream.getVideoTracks()[0];
      let audioTrack = this.localStream.getAudioTracks();
      console.log(videoTrack);
      console.log(audioTrack);

      let videoConstraintsData = videoTrack.getSettings();
      console.log(videoConstraintsData)
      this.videoConstraints = JSON.stringify(videoConstraintsData, null, 4)
      console.log(this.videoConstraints)
    },
    //获取设备信息
    getDevices() {
      if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
        console.log('不支持获取设备信息!');
        this.isOpenMediaStream = false;
      } else {
        navigator.mediaDevices.enumerateDevices().then(this.showDevice).catch((err) => {
          this.error(err.name + ":" + err.message);
        });
      }
    },
    //展示设备信息
    showDevice(deviceInfos) {
      let _this = this;
      deviceInfos.forEach(function (deviceinfo) {
        var option = {
          text: deviceinfo.label,
          value: deviceinfo.deviceId
        }
        if (deviceinfo.kind === "audioinput") {
          _this.audioSourceOption.push(option);
        } else if (deviceinfo.kind === "audiooutput") {
          _this.audioOutputOption.push(option);
        } else if (deviceinfo.kind === "videoinput") {
          _this.videoSourceOption.push(option);
        }
      })
    },
    // 退出
    signOut() {
      Bus.$emit('code', "true");
      this.$router.go(-1);
    },

    async startLive(offerSdp) {
      let stream = this.localStream;
      let _this = this;
      // if (!this.isOpenMediaStream) {
      //   this.error("尚未开启媒体流!");
      //   return;
      // }
      if (stream == undefined) {
        stream = await navigator.mediaDevices.getUserMedia(this.constraints);
      }
      this.log('将媒体轨道添加到轨道集');
      stream.getTracks().forEach(track => {
        //console.info("startLive", _this.peer, track, stream);
        _this.peer.addTrack(track, stream);
      });
      this.sendData.Type = "send";
      if (!offerSdp) {
        this.log('创建本地SDP');
        const offer = await this.peer.createOffer();

        await this.peer.setLocalDescription(offer);
        offer.sdp = offer.sdp;
        this.log(`传输本地SDP`);
        this.sendData.Data = offer;
        this.socket.send(JSON.stringify(this.sendData));
      } else {
        this.log('接收远程SDP');
        //设置接收到的远端offer
        await this.peer.setRemoteDescription(offerSdp);
        this.log('创建回复(应答)SDP');
        //创建answer并发送给对方。
        const answer = await this.peer.createAnswer();
        console.warn(answer);
        this.log(`传输回复接收(应答)SDP`);
        this.sendData.Data = answer;
        this.socket.send(JSON.stringify(this.sendData));
        await this.peer.setLocalDescription(answer);
      }
    },
    log(msg) {
      this.logger.innerHTML += `<span>${new Date().toLocaleTimeString()}:${msg}</span><br/>`;
    },
    error(msg) {
      this.logger.innerHTML += `<span class="error">${new Date().toLocaleTimeString()}:${msg}</span><br/>`;
    }
  }
};
</script>

<style lang="scss" scoped>
.startConsultation::-webkit-scrollbar {
  display: none;
}
.startConsultation {
  width: 100vw;
  height: 100vh;
  display: flex;
  flex-direction: column;
  // overflow-y: scroll;
  .topBar {
    width: 100%;
    height: 105px;
    // opacity: 0.25;
    background: #42464d;
    box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.25);
    padding: 15px 32px;
    box-sizing: border-box;
    display: flex;
    justify-content: space-between;
    .demo-form-inline {
      width: 65%;
      .el-form-item {
        margin: 0 15px;
        width: 375px;
        div {
          color: #fff;
          font-size: 20px;
        }
        // .el-input__inner {
        //   background-color: #42464d;
        //   color: #fff;
        // }
      }
    }
    .rightBox {
      width: 35%;
      display: flex;
      justify-content: space-between;
      .recording {
        font-size: 20px;
        color: #fff;
        font-weight: 700;
        margin-top: 52px;
        i {
          display: inline-block;
          width: 15px;
          height: 15px;
          background: #e60012;
          border-radius: 15px;
          margin-right: 16px;
        }
        span {
          margin-right: 5px;
        }
      }
    }
  }
  .main {
    // height: calc(100% - 135px);
    // height: 100%;
    width: 100%;
    background-color: #272e43;
    padding: 30px;
    box-sizing: border-box;
    flex: 1;
    display: flex;
    flex-direction: column;
    .video {
      flex: 1;
      padding: 0 15px;
      display: flex;
      // justify-content: space-between;
      margin-bottom: 40px;
      .bigScreen {
        width: 1290px;
        height: 100%;
        background-color: #ccc;
        span {
          color: #a0a0a0;
          font-size: 48px;
          cursor: pointer;
        }
      }
      .smallScreem {
        width: 492px;
        height: 100%;
        display: flex;
        flex-wrap: wrap;
        justify-content: space-between;
        align-content: space-between;

        .bigBox {
          width: 500px;
          height: 48%;
          background-color: #ccc;
          position: relative;
          border: 1px solid #ccc;
          .icon {
            position: absolute;
            right: 5px;
            top: 5px;
          }
          video {
            width: 100%;
            height: 100%;
          }
          span {
            color: #a0a0a0;
            font-size: 24px;
            cursor: pointer;
          }
        }
      }
    }
    .operation {
      // height: 106px;
      width: 100%;
      flex-basis: 106px;
      background-color: #1d2335;
      border-radius: 20px;
      display: flex;
      align-items: center;
      justify-content: space-around;
      font-size: 20px;
      font-weight: 400;
      color: #fff;
      text-align: center;
      div {
        cursor: pointer;
        span {
          font-size: 36px;
        }
        p {
          margin-top: 12px;
        }
      }
    }
  }
}
</style>
<style lang="scss">
.startConsultation {
  .demo-form-inline {
    .el-form-item {
      .el-form-item__label {
        color: #fff;
        font-size: 20px;
      }
      .el-input__inner {
        background-color: #42464d;
        border-color: #42464d;
        color: #fff;
        font-size: 20px;
        height: 40px;
      }
    }
  }
  .rightBox {
    .el-button--primary {
      background-color: #d33594;
      border-color: #d33594;
      width: 116px;
      height: 55px;
      margin-top: 10px;
      padding-top: 10px;
      span {
        font-size: 20px;
        color: #e6e6e6;
        font-weight: 700;
      }
    }
  }
  .logger {
    // width: 40%;
    // padding: 14px;
    // line-height: 1.5;
    // color: #4fbf40;
    // border-radius: 6px;
    // background-color: #272727;
    width: 400px;
    height: 600px;
    padding: 14px;
    line-height: 1.5;
    color: #4fbf40;
    border-radius: 6px;
    left: 100px;
    top: 160px;
    z-index: 1;
    position: absolute;
    // transform: translateY(-800px);
    background-color: #272727;
  }
  .logger .error {
    color: #dd4a68;
  }
}

.vue-cropper {
  position: absolute !important;
  top: 0;
  left: 0;
  z-index: -1;
}
</style>

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值