实现1V1音视频实时互动直播系统 十二、第十节 直播客户端的实现

今天我们继续完成我们上节没有完成的任务,上节我们已经将连接和信令的处理逻辑都处理完成了,今天我们来完成媒体协商。那么首先我们要写一个媒体协商的call方法,这个call方法也不干什么特别重要的事,它就是创建一个Offer,然后最终这个Offer创建成功之后通过端对端消息发送给对端,如果我们想创建这个Offer,那首先我们要判断一下我们的状态,那必须是要在joinconnction这个状态下我们才能调用call;而这个call是必须在调用者,如果两端的话调用者,如果是两端的话,调用者才能调用这个call,就是作为发起端才能调用call,那么作为接收端它是不能调用这个call;所以我们要做一个判断,如果state等于joined_conn那处于这种状态下,我们才能去做这件事,那这件比较简单,就是说用PC调用createOffer,这是传入一个offerOptions,如果回调成功那么调用getOffer如果出错了就是 catch,第三个是iceStart,第四个是静音检测,那首先我们把音频和视频打开,这样我们就在 SDP里设置了 我要接收远端的视频和音频;作用这两个选型就是我是否能控制远端的视频和音频的;那么这样我们这里创建Offer就创建完成了。

function call(){
	
	if(state === 'joined_conn'){

		var offerOptions = {
			offerToRecieveAudio: 1, // 表示是否接收视频
			offerToRecieveVideo: 1 // 表示是否接收音频
		}

		pc.createOffer(offerOptions)
			.then(getOffer)
			.catch(handleOfferError);
	}
}

它是一个异步操作,如果成功了会调用getOffer,那getOffer其实比较简单,它首先有个参数desc,首先我们要调用pc.setLocalDescription来触发收集candidate, 收集完了之后我们要sendMessage发送一个端对端消息给另一端,第一个参数就是roomid,第二个参数就是数据offerdesc,这样我们就把消息发送给对端了,让他知道我这个Offer已经创建好了,对端收到这个Offer之后它会创建一个Answer再给我们返回来。

function getOffer(desc){
	pc.setLocalDescription(desc);
	offer.value = desc.sdp;
	offerdesc = desc;

	//send offer sdp
	sendMessage(roomid, offerdesc);	

}

在发送之前我们还有这个sendMessage没实现, 它主要是传一个roomid和一个data,这里实际调用的是socket.emit,我们首先打印 一个console表示我们已经进入这个函数了,

function sendMessage(roomid, data){

	console.log('send message to other end', roomid, data);
	if(!socket){
		console.log('socket is null');
	}
	socket.emit('message', roomid, data);
}

还有一个是错误处理, 当出现这个错误的时候 我们就打印一下错误信息 。

function handleOfferError(err){
	console.error('Failed to create offer:', err);
}

当我们收到Offer之后,先setLocalDescription然后紧接着是sendMessage,那当发送到服务器端进行转发,转发完了又会回到信令处理这里,所以在message要处理很多的逻辑了。

那首先我们要判断一下这里传过来的数据是不是对的,如果数据不对我们也就不用在做了,如果data是OK的,我们要接着做下面的处理,data.type有好几种,第一种就是offer,如果遇到offer应该怎么处理,否则data.type等于answer会怎么样,还有一个是data.type等于candidate会如何,如果都不是,这里就要打印一个错误信息了。

那我们来一个个处理,如果收到的是Offer,那首先对端的pc已经创建好了,那它要调用setRemoteDescription,并且将这个desc设置进去,这个完了之后它紧接着要调用createAnswer,这个函数里如果成功就要getAnswer,如果出错了就调用handleAnswerError;

稍后我们再来实现getAnswer,在这里这个data,比如说它在通过这个信令发过来的时候,它就已经不再是一个对象了,那在我们在getOffer的是它是一个对象,getOffer的时候它拿到这个desc是一个对象,但是我们把这个sendMessage在发送过来的时候实际已经转化成文本了,这时候我们还要给它生成一个对象,所以这里我们不能就简单的desc传进去,我们要new RTCSessionDescription(desc),把它当作一个钩子函数设置进去,这样就OK了,

如果类型是Answer,那就是pc.setRemoteDescription(new RTCSessionDescription(desc)); 那么这时候我们就将这个远端设置好了,

那再接下来就是candidate,我们设置了setLocalDescription之后,双方都可以进行收集candidate了,每当收到一个candidate都是触发了这个pc的onIceCandidate这个事件,在这个事件触发之后它会触发sendMessage,那发送过来之后,当我们处理这个candidate,那我们也有new RTCIceCandidate,那在这里我们要给它传一些参数,第一个是sdpMLineIndex: data.label, 也就是说我们媒体行的行号是多少,第二个是candidate: data.candidate,这样我们生成一个新的candidate,那有了这个新的candidate之后我们要加入本端,也就是pc.addIceCandidate,这样就将candidate加入到我们的PeerConnection当中去了,那它下面就会进行连接性检测,

socket.on('message', (roomid, data) => {
		console.log('receive message!', roomid, data);

		if(data === null || data === undefined){
			console.error('the message is invalid!');
			return;	
		}

		if(data.hasOwnProperty('type') && data.type === 'offer') {
			
			offer.value = data.sdp;

			pc.setRemoteDescription(new RTCSessionDescription(desc));

			//create answer
			pc.createAnswer()
				.then(getAnswer)
				.catch(handleAnswerError);

		}else if(data.hasOwnProperty('type') && data.type == 'answer'){
			answer.value = data.sdp;
			pc.setRemoteDescription(new RTCSessionDescription(desc));
		
		}else if (data.hasOwnProperty('type') && data.type === 'candidate'){
			var candidate = new RTCIceCandidate({
				sdpMLineIndex: data.label,
				candidate: data.candidate
			});
			pc.addIceCandidate(candidate);	
		
		}else{
			console.log('the message is invalid!', data);
		
		}
	
	});

下面我们来实现getAnswer函数,它也是要传过来一个desc,所以在我们获取到本地的Answer之后,我们要做setLocalDescription,就是通知本地要收集这个candidate了,也就是我们要发送消息也就是sendMessage(roomid, desc);

也就是说被调用方首先收到这个Offer之后,要设置setRemoteDescription才能调用这个getAnswer,这个逻辑是没问题的。

function getAnswer(desc){
	pc.setLocalDescription(desc);
	answer.value = desc.sdp;

	//send answer sdp
	sendMessage(roomid, desc);
}

我们再实现一下handleAnswerError,那创建Answer失败之后我们要打印一个console,那这个错误信息就写完了,

function handleAnswerError(err){
	console.error('Failed to create answer:', err);
}

在实现一下createPeerConnection,也就是说当我们发现candidate的时候也就是说我们找到了一个candidate,这个时候我们要sendMessage,将这个candidata要要发送出去,发送出去这个candidata应该是一个什么样的格式呢?这里其实它包括了几个类型,

首先它要输入一个roomid,然后第一个就是说它肯定要有一个type,那这个 type就是candidate, 然后就是label,  label的值就是event.candidate.sdpMLineIndex,那么再下一个是id,这个id就是event.candidate.sdpMid,最后一个candidate就是event.candidate.candidate,

对candidate来说,那它发送一个消息,要我们组装,那我们实际要用的时候主要是用label:event.candidate.sdpMLineIndex, 和candidate: event.candidate.candidate,那这样我们candidate这个消息也构造好了,

function createPeerConnection(){

	//如果是多人的话,在这里要创建一个新的连接.
	//新创建好的要放到一个map表中。
	//key=userid, value=peerconnection
	console.log('create RTCPeerConnection!');
	if(!pc){
		pc = new RTCPeerConnection(pcConfig);

		pc.onicecandidate = (e)=>{

			if(e.candidate) {
				sendMessage(roomid, {
					type: 'candidate',
					label:event.candidate.sdpMLineIndex, 
					id:event.candidate.sdpMid, 
					candidate: event.candidate.candidate
				});
			}else{
				console.log('this is the end candidate');
			}
		}

		pc.ontrack = getRemoteStream;
	}else {
		console.warning('the pc have be created!');
	}

	return;	
}

 

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值