h5播放WebRTC,WebRTC(Web Real-Time Communication)是一种基于网页浏览器的开源项目,提供了实时音视频传输、数据共享等功能,现在各大浏览器已经逐渐加大对WebRTC技术的支持,实现WebRTC的视频推流,播放WebRTC流。
一、初始化连接
1、初始化WebRTC连接。主要涉及到获取本地媒体流、设置ICE服务器配置、创建PeerConnection对象等
this.pc =newRTCPeerConnection(null);this.pc.ontrack=(event)=>{this._mediaElement['srcObject']= event.streams[0];};this.pc.addTransceiver('audio',{direction:'recvonly'});this.pc.addTransceiver('video',{direction:'recvonly'});this.sendChannel =this.pc.createDataChannel('keepalive');this.sendChannel.onclose =this.onChannelClose.bind(this);this.sendChannel.onopen =this.onChannelOpen.bind(this);this.sendChannel.onmessage =this.onChannelMessage.bind(this);this.pc.createOffer({
offerToReceiveVideo:!0,
offerToReceiveAudio:!0}).then((offer)=>{returnthis.pc.setLocalDescription(offer).then(()=>{return offer;});}).then((offer)=>{returnnewPromise((resolve, reject)=>{this.HttpPost(url, window.btoa(offer.sdp)).then((res)=>{resolve(res);},function(rej){reject(rej);});});}).then((answerSdp)=>{returnthis.pc.setRemoteDescription(newRTCSessionDescription({
type:'answer',
sdp: window.atob(answerSdp)}));}).then(()=>{this._isLoad =true;}).catch((reason)=>{throw reason;});
2、ontrack回调中将媒体播放地址,绑定到video上。
3、createOffer方法,这个方法返回本地会话描述。
4、setLocalDescription方法。
5、需要先与服务端建立一个连接。HttpPost(),发送:offer.sdp 到推流端,服务端收到offer.sdp,再返回回来。
HttpPost(url, data){returnnewPromise((resolve, reject)=>{var xhr =newXMLHttpRequest();
xhr.onreadystatechange=()=>{if(xhr.readyState ===4&&(xhr.status >=200&& xhr.status <300)){var respone = xhr.responseText;
xhr.onreadystatechange =newFunction;
xhr =null;resolve(respone);}};
xhr.open('POST', url.replace('webrtc','http'),true);
xhr.send(data);});}
6、收到应答返回的offer.sdp, 设置为你的远端连接。
this.pc.setRemoteDescription(newRTCSessionDescription({
type:'answer',
sdp: window.atob(answerSdp)}));
7、监听 sendChannel.onopen 连接是否建立成功。
8、前端播放的过程中需要与服务器通信保持连接,可以 sendChannel.send(msg)来保持持续拉流 。
9、服务器推流,前端开始播放。
二、完整代码
import PlayerEvents from'../FlvPlayer/flv.js/player/player-events';import EventEmitter from'events';import*as common from'../common/common';exportdefaultclassWebRtcPlayer{constructor(url, options){this.TAG='WebRtcPlayer';this._type ='WebRtcPlayer';this._emitter =newEventEmitter();// this.options = options || {};this.pc =null;this._mediaElement =null;this.url = url;this.autoplay =!!options.autoplay ||false;this.typeCallback = options.typeCallback;this.statusCallback = options.statusCallback;if(!url.match(/^webrtc?:\/\/||^webrtcs?:\/\//)){throw('JSWebrtc just work with webrtc');}this.timer =null;this.sendChannel =null;this._mediaInfo =null;this._statisticsInfo =null;this.isPlaying =false;this._isLoad =false;this._isDestroy =false;this.e ={
onvLoadedMetadata:this._onvLoadedMetadata.bind(this),
onvSeeking:this._onvSeeking.bind(this),
onvCanPlay:this._onvCanPlay.bind(this),
onvPlay:this._onvPlay.bind(this),
onvStalled:this._onvStalled.bind(this),
onvProgress:this._onvProgress.bind(this),
onvWaiting:this._onvWaiting.bind(this),
onvPlaying:this._onvPlaying.bind(this)};this.attachMediaElement(options.mediaElement, url);}on(event, listener){if(event === PlayerEvents.MEDIA_INFO){if(this._mediaInfo !=null){
Promise.resolve().then(()=>{this._emitter.emit(PlayerEvents.MEDIA_INFO,this.mediaInfo);});}}elseif(event === PlayerEvents.STATISTICS_INFO){if(this._statisticsInfo !=null){
Promise.resolve().then(()=>{this._emitter.emit(PlayerEvents.STATISTICS_INFO,this.statisticsInfo);});}}this._emitter.addListener(event, listener);}off(event, listener){this._emitter.removeListener(event, listener);}attachMediaElement(mediaElement, url){this._mediaElement = mediaElement;this._mediaElement = mediaElement;
mediaElement.addEventListener('loadedmetadata',this.e.onvLoadedMetadata);
mediaElement.addEventListener('seeking',this.e.onvSeeking);
mediaElement.addEventListener('canplay',this.e.onvCanPlay);
mediaElement.addEventListener('play',this.e.onvPlay);
mediaElement.addEventListener('stalled',this.e.onvStalled);
mediaElement.addEventListener('progress',this.e.onvProgress);
mediaElement.addEventListener('waiting',this.e.onvWaiting);
mediaElement.addEventListener('playing',this.e.onvPlaying);this.typeCallback(!false);this.load(url);}load(url){this.statusCallback(0);if(this.pc){this.pc.close();}this.pc =newRTCPeerConnection(null);this.pc.ontrack=(event)=>{this._mediaElement['srcObject']= event.streams[0];};this.pc.addTransceiver('audio',{direction:'recvonly'});this.pc.addTransceiver('video',{direction:'recvonly'});this.sendChannel =this.pc.createDataChannel('keepalive');this.sendChannel.onclose =this.onChannelClose.bind(this);this.sendChannel.onopen =this.onChannelOpen.bind(this);this.sendChannel.onmessage =this.onChannelMessage.bind(this);this.pc.createOffer({
offerToReceiveVideo:!0,
offerToReceiveAudio:!0}).then((offer)=>{returnthis.pc.setLocalDescription(offer).then(()=>{return offer;});}).then((offer)=>{returnnewPromise((resolve, reject)=>{this.HttpPost(url, window.btoa(offer.sdp)).then((res)=>{resolve(res);},function(rej){reject(rej);});});}).then((answerSdp)=>{returnthis.pc.setRemoteDescription(newRTCSessionDescription({
type:'answer',
sdp: window.atob(answerSdp)}));}).then(()=>{this._isLoad =true;}).catch((reason)=>{throw reason;});}onChannelClose(){clearInterval(this.timer);if(!this._isDestroy){
common.sleep(3000).then(()=>{this.load(this.url);});}}onChannelOpen(){this.timer =setInterval((()=>{if(this.sendChannel){this.sendChannel.send('ping');}}),3000);}onChannelMessage(){// console.log('onmessage');}HttpPost(url, data){returnnewPromise((resolve, reject)=>{var xhr =newXMLHttpRequest();
xhr.onreadystatechange=()=>{if(xhr.readyState ===4&&(xhr.status >=200&& xhr.status <300)){var respone = xhr.responseText;
xhr.onreadystatechange =newFunction;
xhr =null;resolve(respone);}};
xhr.open('POST', url.replace('webrtc','http'),true);
xhr.send(data);});}_onvLoadedMetadata(){}_onvSeeking(){}_onvCanPlay(){if(this.autoplay){this._mediaElement.play();}}_onvPlay(){this.isPlaying =true;this.statusCallback(100);}_onvStalled(){}_onvProgress(){}_onvWaiting(){}_onvPlaying(){}unload(){}play(){if(!this.isPlaying){this._mediaElement.play();this.isPlaying =true;}}pause(){if(this.isPlaying){this._mediaElement.pause();this.isPlaying =false;}}resume(){}openAudio(){}closeAudio(){}seek(){}stop(){this.destroy();}destroy(){this._isDestroy =true;if(this.pc){this.pc.close();this.pc =null;}if(this._mediaElement){this._mediaElement =null;}if(this.sendChannel){this.sendChannel =null;}this.autoplay =null;this.typeCallback =null;this.statusCallback =null;this.isPlaying =false;this._isLoad =false;clearInterval(this.timer);}getisLoad(){returnthis._isLoad;}}
player-events.js
const PlayerEvents ={ERROR:'error',LOADING_COMPLETE:'loading_complete',RECOVERED_EARLY_EOF:'recovered_early_eof',MEDIA_INFO:'media_info',METADATA_ARRIVED:'metadata_arrived',SCRIPTDATA_ARRIVED:'scriptdata_arrived',STATISTICS_INFO:'statistics_info'};exportdefault PlayerEvents;
common.js
exportfunctionsleep(ms){returnnewPromise(resolve=>setTimeout(resolve, ms));}
版权归原作者 打响第一枪 所有, 如有侵权,请联系我们删除。