rebuild webrtc connections

remake index
add debuglog
This commit is contained in:
vdalex
2022-04-24 21:47:48 +03:00
parent af58edc459
commit 36ea430510
4 changed files with 244 additions and 55 deletions

View File

@@ -3,7 +3,6 @@
- [RTSPtoWeb](https://github.com/deepch/RTSPtoWeb)
- [RTSPtoWebRTC](https://github.com/deepch/RTSPtoWebRTC)
- [RTSPtoWSMP4f](https://github.com/deepch/RTSPtoWSMP4f)
- [RTSPtoImage](https://github.com/deepch/RTSPtoImage)
- [RTSPtoHLS](https://github.com/deepch/RTSPtoHLS)
- [RTSPtoHLSLL](https://github.com/deepch/RTSPtoHLSLL)
@@ -74,5 +73,74 @@ default: false
default: empty;
full list of config you can see on [API dicumentation hls.js](https://github.com/video-dev/hls.js/blob/master/docs/API.md#fine-tuning)
## Methods
#### `webrtcconfig`
default:
```js
{
iceServers: [{
urls: [
"stun:stun.l.google.com:19302"
]}
],
sdpSemantics: "unified-plan",
bundlePolicy: "max-compat",
iceTransportPolicy: "all"//for option "relay" need use turn server
}
```
full list of config you can see on [RTCPeerConnection](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/RTCPeerConnection#parameters)
## Methods
#### `load(source)`
breaking previos connections and load new source
```js
const server='127.0.0.1:8083';//server and port where is running one of mediaserver
const uuid='test';//stream uuid
const channel=0;//stream channel optional
//Project RTSPtoWeb[MSE]
const source=`ws://${server}/stream/${uuid}/channel/${channel}/mse?uuid=${uuid}/&channel=${channel}`;
//Project RTSPtoWeb[WEBRTC]
const source=`http://${server}/stream/${uuid}/channel/${channel}/webrtc?uuid=${uuid}/&channel=${channel}`;
//Project RTSPtoWeb[HLS]
const source=`http://${server}/stream/${uuid}/channel/${channel}/hls/live/index.m3u8`;
//Project RTSPtoWeb[HLSLL]
const source=`http://${server}/stream/${uuid}/channel/${channel}/hlsll/live/index.m3u8`;
//Project RTSPtoWebRTC[WEBRTC]
const source=`http://${server}/stream/receiver/${uuid}`;
//Project RTSPtoWSMP4f[MSE]
const source=`ws://${server}/ws/live?suuid=${uuid}`;
//Project RTSPtoHLS[HLS]
const source=`http://${server}/play/hls/${uuid}/index.m3u8`;
//Project RTSPtoHLSLL[HLS]
const source=`http://${server}/play/hls/${uuid}/index.m3u8`;
player.load(source);
```
#### `destroy()`
breaks all active connections and destroys the player
#### `control media`
for player control you can use all methods for video tag [HTMLMediaElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement#methods) over player.video
for example
```js
const player=new RTSPtoWEBPlayer({
parentElement: document.getElementById('player')
});
player.load(source_url);
//pause
player.video.pause();
//play
player.video.play();
//get currentTime
console.log(player.video.currentTime);
//set currentTime
player.video.currentTime=10;
//etc
```

File diff suppressed because one or more lines are too long

View File

@@ -10,14 +10,15 @@
</head>
<body>
<div class="container">
<h2>RTSPtoWEBPlayer Example</h2>
<h2 class="text-center mt-3">RTSPtoWEBPlayer Example</h2>
<div class="row">
<div class="col-12">
<div class="input-group mb-3">
<input type="text" class="form-control" placeholder="url link" id="link"
value="">
value="https://sr232.ipeye.ru/webrtc/23875e82948a473b83bcd7d0d493fd8f/channel/0/webrtc?uuid=23875e82948a473b83bcd7d0d493fd8f&channel=0"/>
<button class="btn btn-outline-secondary" type="button" onclick="play()">PLAY</button>
</div>
<h6 class="text-center mb-3"><small><a href="#" id="outerLink"></a></small></h6>
</div>
<div class="col-12">
<div id="player"></div>
@@ -34,14 +35,14 @@
const play = ()=>{
const link=document.getElementById("link").value;
const url=new URL(window.location.href);
if(link!=''){
if(link!==''){
player.load(link);
window.history.pushState('RTSPtoWEBPlayer', 'RTSPtoWEBPlayer',`${url.origin+url.pathname+url.search}#${link}`);
outerLink.innerHTML=`${url.origin+url.pathname+url.search}#${link}`;
outerLink.setAttribute('href',`${url.origin+url.pathname+url.search}#${link}`)
}
}
if(window.location.hash){
console.log(window.location.hash.substr(1));
try{
const href=new URL(window.location.hash.substr(1));
document.getElementById("link").value=window.location.hash.substr(1);

View File

@@ -13,6 +13,7 @@ export default class RTSPtoWEBPlayer{
webrtc=null;
currentPlayerType=null;
hidden = "hidden";
paused=false;
options={
parentElement:null,
source:null,
@@ -24,13 +25,12 @@ export default class RTSPtoWEBPlayer{
},
webrtcconfig:{
iceServers: [{
urls: ["stun:stun.l.google.com:19302"]
}],
iceServers: [{urls: ["stun:stun.l.google.com:19302"]}],
sdpSemantics: "unified-plan",
bundlePolicy: 'max-compat'
}
bundlePolicy: "max-compat",
iceTransportPolicy: "all",//for option "relay" need use turn server
},
debug:false
}
constructor(options) {
@@ -49,6 +49,7 @@ export default class RTSPtoWEBPlayer{
this.options.muted ? this.video.setAttribute('muted',''):0;
this.options.autoplay ? this.video.setAttribute('autoplay',''):0;
this.options.loop ? this.video.setAttribute('loop',''):0;
this.addVideoListeners();
//wrapper
this.player=document.createElement("div");
this.player.classList.add('RTSPtoWEBPlayer');
@@ -87,7 +88,7 @@ export default class RTSPtoWEBPlayer{
}
addListeners = () => {
addMseListeners = () => {
this.MSE.addEventListener('sourceopen', this.sourceOpenHandler);
}
@@ -98,15 +99,10 @@ export default class RTSPtoWEBPlayer{
websocketEvents=()=>{
this.webSocket = new WebSocket(this.options.source);
this.webSocket.binaryType = "arraybuffer";
this.webSocket.onopen = () => {
}
this.webSocket.onclose = () => {
this.webSocket.onmessage=null;
}
this.webSocket.onmessage = ({data}) => {
if(this.codec===null){
if(typeof (data)==="object"){
this.codec=new TextDecoder("utf-8").decode((new Uint8Array(data)).slice(1));
@@ -117,7 +113,9 @@ export default class RTSPtoWEBPlayer{
this.MSESourceBuffer.mode = "segments";
this.MSESourceBuffer.addEventListener("updateend", this.pushPacket);
}else{
this.readPacket(data);
if(!this.paused){
this.readPacket(data);
}
}
if (document[this.hidden] && this.video.buffered.length) {
this.video.currentTime = this.video.buffered.end((this.video.buffered.length - 1)) - 1;
@@ -136,10 +134,7 @@ export default class RTSPtoWEBPlayer{
return;
}
this.turn.push(packet);
if (!this.MSESourceBuffer.updating) {
this.pushPacket();
}
this.pushPacket();
}
pushPacket = () => {
@@ -148,8 +143,8 @@ export default class RTSPtoWEBPlayer{
const packet = this.turn.shift();
try {
this.MSESourceBuffer.appendBuffer(packet)
} catch (e) {
console.log(e);
} catch (err) {
this.debugLogger(err);
}
} else {
this.MSEStreamingStarted = false;
@@ -164,7 +159,7 @@ export default class RTSPtoWEBPlayer{
msePlayer =()=>{
this.MSE = new MediaSource();
this.video.src = window.URL.createObjectURL(this.MSE);
this.addListeners();
this.addMseListeners();
}
hlsPlayer = ()=>{
@@ -179,49 +174,127 @@ export default class RTSPtoWEBPlayer{
}
}
webRtcPlayer() {
webRtcPlayer= async ()=>{
this.mediaStream = new MediaStream();
this.video.srcObject = this.mediaStream;
this.webrtc = new RTCPeerConnection(this.options.webrtcconfig);
this.webrtc.onnegotiationneeded = this.handleNegotiationNeeded;
this.webrtc.addTransceiver('video', {
'direction': 'sendrecv'
});
this.webrtc.onnegotiationneeded =this.handleNegotiationNeeded;
this.webrtc.onsignalingstatechange =this.signalingstatechange;
this.webrtc.onicegatheringstatechange =this.icegatheringstatechange;
this.webrtc.onicecandidate =this.icecandidate;
this.webrtc.onicecandidateerror =this.icecandidateerror;
this.webrtc.onconnectionstatechange =this.connectionstatechange;
this.webrtc.oniceconnectionstatechange =this.iceconnectionstatechange;
this.webrtc.ontrack = this.onTrack;
const offer = await this.webrtc.createOffer({
//iceRestart:true,
offerToReceiveAudio:true,
offerToReceiveVideo:true
});
await this.webrtc.setLocalDescription(offer);
}
handleNegotiationNeeded = async ()=>{
const offer = await this.webrtc.createOffer();
await this.webrtc.setLocalDescription(offer);
const suuid=((new URL(this.options.source)).pathname).split('/').slice(-1);
const formData = new FormData();
formData.append('data', btoa(this.webrtc.localDescription.sdp));
formData.append('suuid',suuid);
const response=await fetch(this.options.source, {
method: 'POST',
body: formData
});
if(response.ok){
const remoteDescription=await response.text();
this.webrtc.setRemoteDescription(new RTCSessionDescription({
type: 'answer',
sdp: atob(remoteDescription)
}))
}
handleNegotiationNeeded = async ()=>{
/*
* in this project this handler is not needed, but in another it can be useful
*/
this.debugLogger('handleNegotiationNeeded')
}
signalingstatechange = async ()=>{
switch (this.webrtc.signalingState){
case 'have-local-offer':
const suuid=((new URL(this.options.source)).pathname).split('/').slice(-1);
const formData = new FormData();
formData.append('data', btoa(this.webrtc.localDescription.sdp));
formData.append('suuid',suuid);
const response=await fetch(this.options.source, {
method: 'POST',
body: formData
});
if(response.ok){
const remoteDescription=await response.text();
this.webrtc.setRemoteDescription(new RTCSessionDescription({
type: 'answer',
sdp: atob(remoteDescription)
}))
}
break;
case 'stable':
/*
* There is no ongoing exchange of offer and answer underway.
* This may mean that the RTCPeerConnection object is new, in which case both the localDescription and remoteDescription are null;
* it may also mean that negotiation is complete and a connection has been established.
*/
break;
case 'closed':
/*
* The RTCPeerConnection has been closed.
*/
this.destroy();
break;
default:
console.log(`unhandled signalingState is ${this.webrtc.signalingState}`);
break;
}
}
icegatheringstatechange =()=>{
switch(this.webrtc.iceGatheringState) {
case "gathering":
/* collection of candidates has begun */
this.debugLogger('collection of candidates has begun');
break;
case "complete":
/* collection of candidates is finished */
this.debugLogger('collection of candidates is finished');
break;
}
}
icecandidate = (event)=>{
this.debugLogger('icecandidate\n',event.candidate)
}
icecandidateerror=(event)=>{
this.debugLogger('icecandidateerror\n',`hostCandidate: ${event.hostCandidate} CODE: ${event.errorCode} TEXT: ${event.errorText}`);
}
connectionstatechange=()=>{
switch(this.webrtc.connectionState) {
case "new":
case "connected":
this.debugLogger("Online");
break;
case "disconnected":
this.debugLogger("Disconnecting...");
break;
case "closed":
this.debugLogger("Offline");
break;
case "failed":
this.debugLogger("Error");
break;
default:
this.debugLogger(`Unhadled state: ${this.webrtc.connectionState}`);
break;
}
}
iceconnectionstatechange=()=>{
//this.debugLogger('iceconnectionstatechange\n',this.webrtc.iceConnectionState);
}
onTrack=(event)=>{
this.mediaStream.addTrack(event.track);
this.video.srcObject = this.mediaStream;
this.video.play();
}
destroy=()=>{
this.codec=null;
if (this.currentPlayerType != null) {
switch (this.currentPlayerType) {
case 'hls':
if (this.hls != null) {
this.hls.destroy();
@@ -253,6 +326,30 @@ export default class RTSPtoWEBPlayer{
}
}
addVideoListeners=()=>{
this.video.addEventListener('error', (e) => {
this.debugLogger('[ video listener ]',e)
this.destroy();
});
this.video.addEventListener('play', () => {
this.paused=false
});
this.video.addEventListener('pause', () => {
this.paused=true;
});//
this.video.addEventListener('progress', () => {
if(this.currentPlayerType === 'ws' && this.video.buffered.length>0){
if(this.video.currentTime<this.video.buffered.start(this.video.buffered.length-1)){
this.video.currentTime=this.video.buffered.end(this.video.buffered.length-1)-1;
}
}
});
}
defDocumentHidden() {
if (typeof document.hidden !== "undefined") {
this.hidden = "hidden";
@@ -262,6 +359,29 @@ export default class RTSPtoWEBPlayer{
this.hidden = "webkitHidden";
}
}
getImageBase64 = ()=>{
const canvas = document.createElement("canvas");
canvas.width = this.video.videoWidth;
canvas.height = this.video.videoHeight;
canvas.getContext('2d').drawImage(this.video, 0, 0, canvas.width, canvas.height);
const dataURL = canvas.toDataURL();
canvas.remove();
return dataURL;
}
debugLogger=(...arg)=>{
if(this.options.debug){
if(this.options.debug==='trace'){
console.trace(...arg)
}else{
console.log(...arg)
}
}
}
}