webRTC(十四):webrtc 端到端文本聊天

webrtc 端到端文本聊天是在webRTC(十):webrtc 實現web端對端視頻基礎上實現的,web端到端純文本聊天,後面有講到!

文本傳輸主要使用的是createDataChannel的api實現

  • 創建 createDataChannel
socket.on('otherjoin',(roomid,id)=>{
		
		...
		
		var dataChannelOptions = {
		  ordered: true, //保證到達順序
		};
		//文本聊天
		dc=pc.createDataChannel('dataChannel',dataChannelOptions);
		dc.onmessage=reveivemsg;
		dc.onopen = dataChannelStateChange;
		dc.onclose = dataChannelStateChange;
		dc.onerror = dataChannelError;
		...
	});

在監聽對方進入房間後創建與對方連接的 DataChannel


//創建本地流媒體鏈接
function createPeerConnection(){
	console.log('create RTCPeerConnection!');
	if(!pc){
		pc = new RTCPeerConnection(pcConfig);
		...
		
		//文本聊天
		pc.ondatachannel = e =>{
			// if(!dc){
				dc= e.channel;
				dc.onmessage =reveivemsg;
				dc.onopen = dataChannelStateChange;
				dc.onclose = dataChannelStateChange;
				dc.onerror = dataChannelError;
			// }
		}
		...
	}
}
創建本地流媒體鏈接時創建DataChannel
  • 接收數據
//文本對方傳過來的數據
function reveivemsg(e){
	var msg = e.data;
	console.log('recreived msg is :'+e.data);
	if(msg){
		chat.value += '->' + msg + '\r\n';
	}else{
		console.error('recreived msg is null');
	}
}
  • 監聽通道是否打通
function dataChannelStateChange(){
	var readyState=dc.readyState;
	if(readyState==='open'){
		send_txt.disabled =false;
		btnSend.disabled=false;
	}else{
		send_txt.disabled = true;
		btnSend.disabled =true;
	}
}
  • 發送消息
//發送消息
function sendText(){
	var data = send_txt.value;
	if(data){
		dc.send(data);
	}
	send_txt.value="";
	chat.value += '<-' +data+'\r\n';
}

  • 效果

在這裏插入圖片描述


源碼

  • html
<html>
	<head>
		<title>WebRTC PeerConnection</title>
		<style>
			.preview{
				display: flex;
			}
			.remote{
				margin-left: 20px;
			}
			.text_chat{
				display: flex;
			}
			.text_chat textarea{
				width: 350px;
				height: 350px;
			}
			.send{
				margin-top: 20px;
			}
		</style>
	</head>
	<body>
		<div>
			<div>
				<button id="connserver">Connect Sig Server</button>
				<button id="leave" disabled>Leave</button>
			</div>
			
			<div class="preview">
				<div>
					<h2>Local:</h2>
					<video id="localvideo" autoplay playsinline></video>
				</div>
				<div class="remote">
					<h2>Remote:</h2>
					<video id="remotevideo" autoplay playsinline></video>
				</div>
			</div>
			<!--文本聊天-->
			<h2>Chat:</h2>
			<div class="text_chat">
				<div>
					<textarea id="chat" disabled></textarea>
				</div>
				<div class="remote">
					<textarea id="sendtext" disabled></textarea>
				</div>
			</div>
			<div class="send">
				<button id="send" disabled>Send</button>
			</div>
		</div>
		<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.3/socket.io.js"></script>
		<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
		<script src="./js/client.js"></script>
	</body>
</html>
  • js
'use strict'

var localVideo = document.querySelector('video#localvideo');
var remoteVideo = document.querySelector('video#remotevideo');

var btnConn = document.querySelector('button#connserver');
var btnLeave = document.querySelector('button#leave');

// 文本聊天
var chat = document.querySelector('textarea#chat');
var send_txt = document.querySelector('textarea#sendtext');
var btnSend = document.querySelector('button#send');

var localStream = null;

var roomid = '555555';
var socket =null;

var state = 'init';

var pc = null;
var dc = null;

var pcConfig={
		'iceServers':[{
			'urls':'turn:121.41.76.43:3478',
			'credential':'123456',
			'username':'huang'
		}]
	}

function sendMessage(roomid,data){
	if(socket){
		socket.emit('message',roomid,data);
	}
}

function getAnswer(desc){
	pc.setLocalDescription(desc);
	sendMessage(roomid,desc);
}

function handleAnswerError(err){
	console.error('Failed to get Answer!',err);
}

function getOffer(desc){
	pc.setLocalDescription(desc);
	sendMessage(roomid,desc)
}
function handleOfferError(err){
	console.error('Failed to get Offer!',err);
}

//接收遠端流通道
function call(){
	if(state === 'joined_conn'){
		if(pc){
			var options = {
				offerToReceiveAudio:1,
				offerToReceiveVideo:1
			}
			pc.createOffer(options)
			  .then(getOffer)
			  .catch(handleOfferError);
		}
	}	
}

// 第一步:開始服務
function connSignalServer(){
	//開啓本地視頻
	start();
	return true;
}

//文本對方傳過來的數據
function reveivemsg(e){
	var msg = e.data;
	console.log('recreived msg is :'+e.data);
	if(msg){
		chat.value += '->' + msg + '\r\n';
	}else{
		console.error('recreived msg is null');
	}
}

function dataChannelStateChange(){
	var readyState=dc.readyState;
	if(readyState==='open'){
		send_txt.disabled =false;
		btnSend.disabled=false;
	}else{
		send_txt.disabled = true;
		btnSend.disabled =true;
	}
}

function dataChannelError(error){
	console.log("Data Channel Error:", error);
}

function conn(){
	//1 觸發socke連接
	socket = io.connect();
	
	//2 加入房間後的回調
	socket.on('joined',(roomid,id)=>{
		
		state = 'joined';
		
		createPeerConnection();
		
		btnConn.disabled = true;
		btnLeave.disabled =false;
		
		console.log("reveive joined message:state=",state);	
	});
	socket.on('otherjoin',(roomid,id)=>{
		
		if (state === 'joined_unbind') {
			createPeerConnection();
		}
		
		var dataChannelOptions = {
		  ordered: true, //保證到達順序
		};
		//文本聊天
		dc=pc.createDataChannel('dataChannel',dataChannelOptions);
		dc.onmessage=reveivemsg;
		dc.onopen = dataChannelStateChange;
		dc.onclose = dataChannelStateChange;
		dc.onerror = dataChannelError;
		
		
		state = 'joined_conn';
		
		//媒體協商
		call();
		console.log("reveive otherjoin message:state=",state);	
	});
	socket.on('full',(roomid,id)=>{
		console.log('receive full message ', roomid, id);

		closePeerConnection();
		closeLocalMedia();
		
		state = 'leaved';
		
		btnConn.disabled = false;
		btnLeave.disabled = true;
		console.log("reveive full message:state=",state);
		alert("the room is full!");
	});
	
	socket.on('leaved',(roomid,id)=>{
		
		state = 'leaved';
		socket.disconnect();
		btnConn.disabled = false;
		btnLeave.disabled = true;
		console.log("reveive leaved message:state=",state);
	});
	
	socket.on('bye',(roomid,id)=>{
		
		state = 'joined_unbind';
		closePeerConnection();
		console.log("reveive bye message:state=",state);	
	});
	socket.on('disconnect', (socket) => {
		console.log('receive disconnect message!', roomid);
		if(!(state === 'leaved')){
			closePeerConnection();
			closeLocalMedia();
		}
		state = 'leaved';
	
	});
	socket.on('message',(roomid,id,data)=>{
		console.log(" message=====>",data);
		//媒體協商
		if(data){
			if(data.type === 'offer'){
				pc.setRemoteDescription(new RTCSessionDescription(data));
				pc.createAnswer()
				  .then(getAnswer)
				  .catch(handleAnswerError);
			}else if(data.type === 'answer'){
				console.log("reveive client message=====>",data);
				pc.setRemoteDescription(new RTCSessionDescription(data));
			}else if(data.type === 'candidate'){
				var candidate = new RTCIceCandidate({
					sdpMLineIndex:data.label,
					candidate:data.candidate
				});
				pc.addIceCandidate(candidate);
				
			}else{
				console.error('the message is invalid!',data)
			}
		}
		
		console.log("reveive client message",roomid,id,data);	
	});
	
	socket.emit('join',roomid);
	return;
}

// 撲捉本地視頻
function getMediaStream(stream){
	
	localStream =stream;
	//2 ===============顯示本地視頻===============
	localVideo.srcObject = localStream;
	
	//這個函數的調用時機特別重要 一定要在getMediaStream之後再調用,否則會出現綁定失敗的情況
	conn();
}

function handleError(err){
	if(err){
		console.error("getUserMedia  error:",err);	
	}
}

// 第二步:採集本地視頻
function start(){
	
	
	if (!navigator.mediaDevices||
			!navigator.mediaDevices.getUserMedia) {			
		  console.log("getUserMedia is not supported!")
		  return;
	} else {
		
		//1 ===============配置音視頻參數===============
		var constraints={
			video:true,
			audio: false
		}
		
		navigator.mediaDevices.getUserMedia(constraints)
							  .then(getMediaStream)
							  .catch(handleError)
	}
}

//關閉流通道
function closeLocalMedia(){
	if (localStream&&localStream.getTracks()) {
		localStream.getTracks().forEach((track)=>{
			track.stop();	
		});
	}
	localStream = null;
}


function leave(){
	if(socket){
		socket.emit('leave',roomid);
	}

	//釋放資源
	closePeerConnection();
	closeLocalMedia();
	
	btnConn.disabled = false;
	btnLeave.disabled = true;
}

//創建本地流媒體鏈接
function createPeerConnection(){
	console.log('create RTCPeerConnection!');
	if(!pc){
		pc = new RTCPeerConnection(pcConfig);
		pc.onicecandidate = (e) =>{
			if(e.candidate){
				sendMessage(roomid,{
					type:'candidate',
					label:e.candidate.sdpMLineIndex,
					id:e.candidate.sdpMid,
					candidate:e.candidate.candidate
				});
			}
		}
		
		//文本聊天
		pc.ondatachannel = e =>{
			// if(!dc){
				dc= e.channel;
				dc.onmessage =reveivemsg;
				dc.onopen = dataChannelStateChange;
				dc.onclose = dataChannelStateChange;
				dc.onerror = dataChannelError;
			// }
		}
		
		pc.ontrack = (e)=>{
			remoteVideo.srcObject = e.streams[0];
		}
	}
	if(pc === null || pc === undefined){
			console.error('pc is null or undefined!');
			return;
	}

	if(localStream === null || localStream === undefined){
		console.error('localStream is null or undefined!');
		return;
	}

	if(localStream){
		localStream.getTracks().forEach((track)=>{
			pc.addTrack(track,localStream);
		})
	}
}

//關閉本地媒體流鏈接
function closePeerConnection(){
	console.log('close RTCPeerConnection!');
	if(pc){
		pc.close();
		pc = null;
	}
}



//發送文本
function sendText(){
	var data = send_txt.value;
	if(data){
		dc.send(data);
	}
	send_txt.value="";
	chat.value += '<-' +data+'\r\n';
}


btnConn.onclick = connSignalServer;

btnLeave.onclick = leave;

btnSend.onclick=sendText;




測試地址:https://www.huangxiaoguo.club/textchat/room.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章