1. <script>
    2. export default {
    3. components: {},
    4. data() {
    5. return {
    6. fromIm: Math.floor(Math.random()*1000),
    7. toIm: '',
    8. ws: '',
    9. rtcPeer: '',
    10. localVideo: '',
    11. remoteVideo: '',
    12. offerIces: [],
    13. answerIces: [],
    14. iceFlag: false
    15. }
    16. },
    17. mounted() {
    18. this.initWs()
    19. },
    20. methods:{
    21. sender(){
    22. if(!this.toIm){
    23. alert("toIm不能为空")
    24. return;
    25. }
    26. this.initMedia('offer')
    27. },
    28. initWs(){
    29. let _this = this
    30. //修改成你自己websocket服务端的ip和端口
    31. _this.ws = new WebSocket("wss://www.xxxxxx.com/ws?username="+this.fromIm);
    32. _this.ws.onopen = function(){
    33. // Web Socket 已连接上,使用 send() 方法发送数据
    34. alert("WebSocket连接成功");
    35. //心跳检测
    36. setInterval(() => {
    37. _this.ws.send("{msgType: 'PING'}")
    38. },5000)
    39. };
    40. //我自己定义的格式,你们可以根据自己修改
    41. //ret = {msgType: 'RTC',msg: '消息体',toIm: '接收人', fromIm: '发送人'}
    42. _this.ws.onmessage = function (ret){
    43. var data = JSON.parse(ret.data)
    44. console.log("收到消息",data)
    45. if(data.msgType!=='RTC'){
    46. return;
    47. }
    48. const { type, sdp, iceCandidate } = JSON.parse(data.msg)
    49. console.log("收到消息",type,iceCandidate)
    50. if (type === 'answer') {
    51. _this.rtcPeer.setRemoteDescription(new RTCSessionDescription({ type, sdp }));
    52. } else if (type === 'answer_ice') {
    53. _this.answerIces.push(iceCandidate)
    54. } else if (type === 'offer') {
    55. _this.toIm = data.fromIm
    56. _this.initMedia("answer",new RTCSessionDescription({ type, sdp }));
    57. } else if (type === 'offer_ice') {
    58. _this.offerIces.push(iceCandidate)
    59. }
    60. };
    61. _this.ws.onclose = function(){
    62. // 关闭 websocket
    63. alert("WebSocket连接已关闭...");
    64. };
    65. },
    66. //接收拨打方的消息,判断后添加候选人(因为addIceCandidate必须在设置描述remoteDescription之后,如果还没设置描述,我们先把它存起来,添加描述后再添加候选人)
    67. intervalAddIce(){
    68. let _this = this
    69. setInterval(() => {
    70. if(_this.rtcPeer && _this.rtcPeer.remoteDescription && _this.rtcPeer.remoteDescription.type){
    71. if(!_this.iceFlag){
    72. _this.iceFlag = true;
    73. while(_this.offerIces.length>0){
    74. let iceCandidate = _this.offerIces.shift();
    75. _this.rtcPeer.addIceCandidate(iceCandidate).then(_=>{
    76. console.log("success addIceCandidate()");
    77. }).catch(e=>{
    78. console.log("Error: Failure during addIceCandidate()",e);
    79. });
    80. }
    81. while(_this.answerIces.length>0){
    82. let iceCandidate = _this.answerIces.shift();
    83. _this.rtcPeer.addIceCandidate(iceCandidate).then(_=>{
    84. console.log("success addIceCandidate()");
    85. }).catch(e=>{
    86. console.log("Error: Failure during addIceCandidate()",e);
    87. });
    88. }
    89. _this.iceFlag = false;
    90. }
    91. }
    92. }, 1000);
    93. },
    94. //初始化媒体源
    95. async initMedia(iceType,offerSdp){
    96. var _this = this
    97. //ios浏览器不判断这部分会提示不支持 (navigator.mediaDevices && navigator.mediaDevices.getUserMedia)
    98. const UserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia || (navigator.mediaDevices && navigator.mediaDevices.getUserMedia);
    99. if(!UserMedia){
    100. alert("media,您的浏览器不支持访问用户媒体设备,请换一个浏览器")
    101. return;
    102. }
    103. //RTCPeerConnection 接口代表一个由本地计算机到远端的WebRTC连接。该接口提供了创建,保持,监控,关闭连接的方法的实现。
    104. const PeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
    105. if(!PeerConnection){
    106. alert("peer,您的浏览器不支持访问用户媒体设备,请换一个浏览器")
    107. return;
    108. }
    109. _this.localVideo = document.getElementById('localVideo');
    110. navigator.mediaDevices.getUserMedia({
    111. audio: {
    112. channelCount: {ideal: 2,min: 1}, //双声道
    113. echoCancellation: true, //回声消除
    114. autoGainControl: true, //修改麦克风输入音量,自动增益
    115. noiseSuppression: true //消除背景噪声
    116. }
    117. // video: { //可以指定获取传入摄像头影像的宽高
    118. // width: 400,
    119. // height: 500
    120. // }
    121. ,video: true
    122. }).then(async stream => {
    123. await _this.initPeer(iceType,PeerConnection)
    124. _this.intervalAddIce()
    125. //成功打开音视频流
    126. try {
    127. _this.localVideo.srcObject = stream;
    128. } catch (error) {
    129. _this.localVideo.src = await window.URL.createObjectURL(stream);
    130. }
    131. stream.getTracks().forEach( async track => {
    132. await _this.rtcPeer.addTrack(track, stream);
    133. });
    134. if (!offerSdp) {
    135. console.log('创建本地SDP');
    136. const offer = await _this.rtcPeer.createOffer();
    137. await _this.rtcPeer.setLocalDescription(offer);
    138. console.log(`传输发起方本地SDP`,offer);
    139. await _this.ws.send(_this.getMsgObj(offer));
    140. } else {
    141. console.log('接收到发送方SDP');
    142. await _this.rtcPeer.setRemoteDescription(offerSdp);
    143. console.log('创建接收方(应答)SDP');
    144. const answer = await _this.rtcPeer.createAnswer();
    145. console.log(`传输接收方(应答)SDP`);
    146. await _this.ws.send(_this.getMsgObj(answer));
    147. await _this.rtcPeer.setLocalDescription(answer);
    148. }
    149. }).catch(error => {
    150. alert("无法开启本地媒体源:"+error);
    151. })
    152. },
    153. //初始化webRtc连接
    154. async initPeer(iceType,PeerConnection){
    155. let _this = this
    156. _this.remoteVideo = document.getElementById('remoteVideo');
    157. if(!_this.rtcPeer){
    158. var stun = "stun:120.24.202.127:3478"
    159. var turn = "turn:120.24.202.127:3478"
    160. var peerConfig = {
    161. "iceServers": [{
    162. "urls": stun
    163. }, {
    164. "urls": turn,
    165. "username": "admin",
    166. "credential": "123456"
    167. }]
    168. };
    169. //如果是局域网,不需要配置穿透服务,直接传null即可
    170. _this.rtcPeer = new PeerConnection(peerConfig);
    171. _this.rtcPeer.onicecandidate = async (e) => {
    172. if (e.candidate) {
    173. console.log("搜集并发送候选人")
    174. await _this.ws.send(_this.getMsgObj({
    175. type: iceType+'_ice',
    176. iceCandidate: e.candidate
    177. }));
    178. }else{
    179. console.log("候选人收集完成")
    180. }
    181. };
    182. _this.rtcPeer.ontrack = async(e) => {
    183. console.log("ontrack",e)
    184. if (e && e.streams) {
    185. _this.remoteVideo.srcObject = await e.streams[0];
    186. }
    187. };
    188. }
    189. },
    190. getMsgObj(msg){
    191. var msgObj = {
    192. fromIm: this.fromIm,
    193. toIm: this.toIm,
    194. msg: msg,
    195. msgType: "RTC",
    196. }
    197. console.log(msgObj)
    198. return JSON.stringify(msgObj);
    199. }
    200. }
    201. }
    202. </script>
    203. <style scoped>
    204. .video {
    205. transform: rotateY(180deg);
    206. }
    207. .remote{
    208. float: left;
    209. }
    210. .local{
    211. }
    212. </style>