mirror of
https://github.com/vdalex25/rtsp-to-web-player.git
synced 2025-09-26 21:01:42 +08:00
rebuild webrtc connections
remake index add debuglog
This commit is contained in:
72
README.md
72
README.md
@@ -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
|
||||
```
|
2
dist/RTSPtoWEBPlayer.js
vendored
2
dist/RTSPtoWEBPlayer.js
vendored
File diff suppressed because one or more lines are too long
11
index.html
11
index.html
@@ -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);
|
||||
|
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user