|
|
|
|
@ -37,6 +37,9 @@ function MonitorStream(monitorData) {
|
|
|
|
|
this.mseSourceBuffer = null;
|
|
|
|
|
this.janusEnabled = monitorData.janusEnabled;
|
|
|
|
|
this.janusPin = monitorData.janus_pin;
|
|
|
|
|
this.mediaStream = null;
|
|
|
|
|
this.audioTrack = null;
|
|
|
|
|
this.videoTrack = null;
|
|
|
|
|
this.server_id = monitorData.server_id;
|
|
|
|
|
this.scale = monitorData.scale ? parseInt(monitorData.scale) : 100;
|
|
|
|
|
this.status = {capturefps: 0, analysisfps: 0}; // json object with alarmstatus, fps etc
|
|
|
|
|
@ -332,17 +335,14 @@ function MonitorStream(monitorData) {
|
|
|
|
|
}
|
|
|
|
|
}; // setStreamScale
|
|
|
|
|
|
|
|
|
|
this.updateStreamInfo = function(info) {
|
|
|
|
|
/*
|
|
|
|
|
* If you specify info='' when calling, only the "status" will be updated, while "info" will not be changed.
|
|
|
|
|
*/
|
|
|
|
|
this.updateStreamInfo = function(info='', status='') {
|
|
|
|
|
const modeEl = document.querySelector('#monitor' + this.id + ' .stream-info-mode');
|
|
|
|
|
const statusEl = document.querySelector('#monitor' + this.id + ' .stream-info-status');
|
|
|
|
|
if (modeEl) modeEl.innerText = info;
|
|
|
|
|
if (statusEl) statusEl.innerText = '';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.copyAllAttributes = function(fromEl, toEl) {
|
|
|
|
|
for (const attr of fromEl.attributes) {
|
|
|
|
|
toEl.setAttribute(attr.name, attr.value);
|
|
|
|
|
}
|
|
|
|
|
if (modeEl && info) modeEl.innerText = info;
|
|
|
|
|
if (statusEl) statusEl.innerText = status;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
@ -410,9 +410,7 @@ function MonitorStream(monitorData) {
|
|
|
|
|
if (ZM_GO2RTC_PATH) {
|
|
|
|
|
const url = new URL(ZM_GO2RTC_PATH);
|
|
|
|
|
|
|
|
|
|
const old_stream = this.getElement();
|
|
|
|
|
const stream = this.element = document.createElement('video-stream');
|
|
|
|
|
this.copyAllAttributes(old_stream, stream);
|
|
|
|
|
const stream = this.element = replaceDOMElement(this.getElement(), 'video-stream');
|
|
|
|
|
stream.background = true; // We do not use the document hiding/showing analysis from "video-rtc.js", because we have our own analysis
|
|
|
|
|
//stream.muted = this.muted;
|
|
|
|
|
const Go2RTCModUrl = url;
|
|
|
|
|
@ -424,10 +422,7 @@ function MonitorStream(monitorData) {
|
|
|
|
|
webrtcUrl.pathname += "/ws";
|
|
|
|
|
webrtcUrl.search = 'src=' + this.id + streamSuffix;
|
|
|
|
|
stream.src = webrtcUrl.href;
|
|
|
|
|
const stream_container = old_stream.parentNode;
|
|
|
|
|
|
|
|
|
|
old_stream.remove();
|
|
|
|
|
stream_container.appendChild(stream);
|
|
|
|
|
this.webrtc = stream; // track separately do to api differences between video tag and video-stream
|
|
|
|
|
if (-1 != this.player.indexOf('_')) {
|
|
|
|
|
stream.mode = this.player.substring(this.player.indexOf('_')+1);
|
|
|
|
|
@ -455,9 +450,15 @@ function MonitorStream(monitorData) {
|
|
|
|
|
|
|
|
|
|
if (this.janusEnabled && ((!this.player) || (-1 !== this.player.indexOf('janus')))) {
|
|
|
|
|
let server;
|
|
|
|
|
document.querySelector('video').addEventListener('play', (e) => {
|
|
|
|
|
this.createVolumeSlider();
|
|
|
|
|
}, this);
|
|
|
|
|
const stream = this.element = replaceDOMElement(this.getElement(), 'video');
|
|
|
|
|
stream.setAttribute("autoplay", "");
|
|
|
|
|
stream.setAttribute("muted", this.muted);
|
|
|
|
|
const video_el = document.querySelector('#liveStream'+this.id);
|
|
|
|
|
if (video_el) {
|
|
|
|
|
video_el.addEventListener('play', (e) => {
|
|
|
|
|
this.createVolumeSlider();
|
|
|
|
|
}, this);
|
|
|
|
|
}
|
|
|
|
|
if (ZM_JANUS_PATH) {
|
|
|
|
|
server = ZM_JANUS_PATH;
|
|
|
|
|
} else if (this.server_id && Servers[this.server_id]) {
|
|
|
|
|
@ -474,39 +475,33 @@ function MonitorStream(monitorData) {
|
|
|
|
|
janus = new Janus({server: server}); //new Janus
|
|
|
|
|
}});
|
|
|
|
|
}
|
|
|
|
|
attachVideo(parseInt(this.id), this.janusPin);
|
|
|
|
|
attachVideo(this);
|
|
|
|
|
this.statusCmdTimer = setInterval(this.statusCmdQuery.bind(this), statusRefreshTimeout);
|
|
|
|
|
this.started = true;
|
|
|
|
|
this.streamListenerBind();
|
|
|
|
|
this.activePlayer = 'janus';
|
|
|
|
|
this.updateStreamInfo('Janus');
|
|
|
|
|
this.updateStreamInfo('Janus', 'loading');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// FIXME auto mode doesn't work properly here. Ideally it would try each until one succeeds
|
|
|
|
|
if (this.RTSP2WebEnabled && ((!this.player) || (-1 !== this.player.indexOf('rtsp2web')))) {
|
|
|
|
|
if (ZM_RTSP2WEB_PATH) {
|
|
|
|
|
let stream = this.getElement();
|
|
|
|
|
if (stream.nodeName != 'VIDEO') {
|
|
|
|
|
// replace with new video tag.
|
|
|
|
|
const stream_container = stream.parentNode;
|
|
|
|
|
const new_stream = this.element = document.createElement('video');
|
|
|
|
|
this.copyAllAttributes(stream, new_stream);
|
|
|
|
|
new_stream.setAttribute("autoplay", "");
|
|
|
|
|
new_stream.setAttribute("muted", this.muted);
|
|
|
|
|
new_stream.setAttribute("playsinline", "");
|
|
|
|
|
new_stream.style = stream.style; // Copy any applied styles
|
|
|
|
|
stream.remove();
|
|
|
|
|
stream_container.appendChild(new_stream);
|
|
|
|
|
stream = new_stream;
|
|
|
|
|
}
|
|
|
|
|
const stream = this.element = replaceDOMElement(this.getElement(), 'video');
|
|
|
|
|
stream.setAttribute("autoplay", "");
|
|
|
|
|
stream.setAttribute("muted", this.muted);
|
|
|
|
|
stream.setAttribute("playsinline", "");
|
|
|
|
|
const url = new URL(ZM_RTSP2WEB_PATH);
|
|
|
|
|
const useSSL = (url.protocol == 'https');
|
|
|
|
|
|
|
|
|
|
const rtsp2webModUrl = url;
|
|
|
|
|
document.querySelector('video').addEventListener('play', (e) => {
|
|
|
|
|
this.createVolumeSlider();
|
|
|
|
|
}, this);
|
|
|
|
|
const video_el = document.querySelector('video#liveStream'+this.id);
|
|
|
|
|
if (video_el) {
|
|
|
|
|
video_el.muted = this.muted;
|
|
|
|
|
video_el.addEventListener('play', (e) => {
|
|
|
|
|
this.createVolumeSlider();
|
|
|
|
|
}, this);
|
|
|
|
|
}
|
|
|
|
|
rtsp2webModUrl.username = '';
|
|
|
|
|
rtsp2webModUrl.password = '';
|
|
|
|
|
//.urlParts.length > 1 ? urlParts[1] : urlParts[0]; // drop the username and password for viewing
|
|
|
|
|
@ -523,7 +518,15 @@ function MonitorStream(monitorData) {
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
if (Hls.isSupported()) {
|
|
|
|
|
this.hls = new Hls();
|
|
|
|
|
this.hls = new Hls({
|
|
|
|
|
maxBufferLength: 10,
|
|
|
|
|
maxMaxBufferLength: 30,
|
|
|
|
|
});
|
|
|
|
|
this.hls.on(Hls.Events.MEDIA_ATTACHED, function(event, data) {
|
|
|
|
|
console.log(`Video and hls.js are now bound together for monitor ID=${this.id}`);
|
|
|
|
|
this.updateStreamInfo('', ''); //HLS
|
|
|
|
|
this.getTracksFromStream(); //HLS
|
|
|
|
|
}, this);
|
|
|
|
|
this.hls.loadSource(hlsUrl.href);
|
|
|
|
|
this.hls.attachMedia(stream);
|
|
|
|
|
} else if (stream.canPlayType('application/vnd.apple.mpegurl')) {
|
|
|
|
|
@ -547,7 +550,7 @@ function MonitorStream(monitorData) {
|
|
|
|
|
this.statusCmdTimer = setInterval(this.statusCmdQuery.bind(this), statusRefreshTimeout);
|
|
|
|
|
this.started = true;
|
|
|
|
|
this.streamListenerBind();
|
|
|
|
|
this.updateStreamInfo(players ? players[this.activePlayer] : 'RTSP2Web ' + this.RTSP2WebType);
|
|
|
|
|
this.updateStreamInfo(players ? players[this.activePlayer] : 'RTSP2Web ' + this.RTSP2WebType, 'loading');
|
|
|
|
|
return;
|
|
|
|
|
} else {
|
|
|
|
|
console.log("ZM_RTSP2WEB_PATH is empty. Go to Options->System and set ZM_RTSP2WEB_PATH accordingly.");
|
|
|
|
|
@ -555,18 +558,11 @@ function MonitorStream(monitorData) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// zms stream
|
|
|
|
|
let stream = this.getElement();
|
|
|
|
|
const stream = this.element = replaceDOMElement(this.getElement(), 'img');
|
|
|
|
|
if (!stream) return;
|
|
|
|
|
|
|
|
|
|
if (stream.nodeName != 'IMG') {
|
|
|
|
|
// replace with new img tag.
|
|
|
|
|
const stream_container = stream.parentNode;
|
|
|
|
|
const new_stream = this.element = document.createElement('img');
|
|
|
|
|
this.copyAllAttributes(stream, new_stream);
|
|
|
|
|
stream.remove();
|
|
|
|
|
stream_container.appendChild(new_stream);
|
|
|
|
|
stream = new_stream;
|
|
|
|
|
}
|
|
|
|
|
this.destroyVolumeSlider();
|
|
|
|
|
|
|
|
|
|
this.streamCmdTimer = clearTimeout(this.streamCmdTimer);
|
|
|
|
|
// Step 1 make sure we are streaming instead of a static image
|
|
|
|
|
if (stream.getAttribute('loading') == 'lazy') {
|
|
|
|
|
@ -590,6 +586,7 @@ function MonitorStream(monitorData) {
|
|
|
|
|
src = src.replace(/auth=\w+/i, 'auth='+auth_hash);
|
|
|
|
|
}
|
|
|
|
|
if (-1 == src.search('connkey')) {
|
|
|
|
|
this.streamCmdParms.connkey = this.statusCmdParms.connkey = this.connKey = this.genConnKey(); // The "connkey" needs to be replaced, because on the Watch page, when switching the player to ZMS, then to any other player, and then returning to ZMS, playback will not occur, because the socket="previous connkey" will be closed.
|
|
|
|
|
src += '&connkey='+this.connKey;
|
|
|
|
|
}
|
|
|
|
|
if (-1 == src.search('scale=')) {
|
|
|
|
|
@ -623,10 +620,14 @@ function MonitorStream(monitorData) {
|
|
|
|
|
if (!stream) {
|
|
|
|
|
console.warn(`! ${dateTimeToISOLocal(new Date())} Stream for ID=${this.id} it is impossible to stop because it is not found.`);
|
|
|
|
|
return;
|
|
|
|
|
} else if (!this.started) {
|
|
|
|
|
console.warn(`! ${dateTimeToISOLocal(new Date())} Stream for ID=${this.id} has already stopped.`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
console.debug(`! ${dateTimeToISOLocal(new Date())} Stream for ID=${this.id} STOPPING`);
|
|
|
|
|
this.statusCmdTimer = clearInterval(this.statusCmdTimer);
|
|
|
|
|
this.streamCmdTimer = clearInterval(this.streamCmdTimer);
|
|
|
|
|
this.mediaStream = this.audioTrack = this.videoTrack = null;
|
|
|
|
|
|
|
|
|
|
if (-1 !== this.activePlayer.indexOf('zms')) {
|
|
|
|
|
// Icon: My current thought is to just tell zms to stop. Don't go to single.
|
|
|
|
|
@ -645,6 +646,7 @@ function MonitorStream(monitorData) {
|
|
|
|
|
console.log('close not in ', this.webrtc);
|
|
|
|
|
}
|
|
|
|
|
this.webrtc = null;
|
|
|
|
|
stream.srcObject = null;
|
|
|
|
|
} else if (-1 !== this.activePlayer.indexOf('rtsp2web')) {
|
|
|
|
|
if (this.webrtc) {
|
|
|
|
|
if (this.webrtc.close) this.webrtc.close();
|
|
|
|
|
@ -660,12 +662,17 @@ function MonitorStream(monitorData) {
|
|
|
|
|
this.stopMse();
|
|
|
|
|
}
|
|
|
|
|
} else if (-1 !== this.activePlayer.indexOf('janus')) {
|
|
|
|
|
stream.src = '';
|
|
|
|
|
stream.srcObject = null;
|
|
|
|
|
if (janus && streaming[this.id]) {
|
|
|
|
|
//streaming[this.id].detach(); // This will result in an error! This requires a more detailed study of Janus, or perhaps it has been fixed in a version higher than 1.1.2.
|
|
|
|
|
}
|
|
|
|
|
//stream.src = '';
|
|
|
|
|
//stream.srcObject = null;
|
|
|
|
|
janus.destroy();
|
|
|
|
|
janus = null;
|
|
|
|
|
} else {
|
|
|
|
|
console.log("Unknown activePlayer", this.activePlayer);
|
|
|
|
|
}
|
|
|
|
|
this.activePlayer = '';
|
|
|
|
|
this.started = false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
@ -700,7 +707,7 @@ function MonitorStream(monitorData) {
|
|
|
|
|
resolve();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onBufferRemoved(this_) {
|
|
|
|
|
function onBufferRemoved(event) {
|
|
|
|
|
this.removeEventListener('updateend', onBufferRemoved);
|
|
|
|
|
resolve();
|
|
|
|
|
}
|
|
|
|
|
@ -717,6 +724,7 @@ function MonitorStream(monitorData) {
|
|
|
|
|
this.MSEBufferCleared = true;
|
|
|
|
|
})
|
|
|
|
|
.catch((error) => {
|
|
|
|
|
//IMPORTANT!!! If this error occurs, captureStream will not always work for the next RTSP2Web RTC stream. This requires investigation!!!
|
|
|
|
|
console.warn(`${dateTimeToISOLocal(new Date())} An error occurred while stopMse() for ID=${this.id}`, error);
|
|
|
|
|
this.closeWebSocket();
|
|
|
|
|
this.mse = null;
|
|
|
|
|
@ -729,9 +737,9 @@ function MonitorStream(monitorData) {
|
|
|
|
|
this.kill = function() {
|
|
|
|
|
console.log("kill");
|
|
|
|
|
/* kill should actually remove the zms process. Resulting in a broken image on screen. */
|
|
|
|
|
if (janus && streaming[this.id]) {
|
|
|
|
|
streaming[this.id].detach();
|
|
|
|
|
}
|
|
|
|
|
//if (janus && streaming[this.id]) { // This will result in an error!
|
|
|
|
|
// streaming[this.id].detach();
|
|
|
|
|
//}
|
|
|
|
|
const stream = this.getElement();
|
|
|
|
|
if (!stream) {
|
|
|
|
|
console.log("No element found for monitor "+this.id);
|
|
|
|
|
@ -744,7 +752,7 @@ function MonitorStream(monitorData) {
|
|
|
|
|
if (this.started && (-1 !== this.activePlayer.indexOf('zms')) && this.connKey) {
|
|
|
|
|
// Make zms exit, sometimes zms doesn't receive SIGPIPE, so try to send QUIT
|
|
|
|
|
this.streamCommand(CMD_QUIT);
|
|
|
|
|
this.connKey = null;
|
|
|
|
|
this.streamCmdParms.connkey = this.statusCmdParms.connkey = this.connKey = null;
|
|
|
|
|
this.started = false;
|
|
|
|
|
}
|
|
|
|
|
// Kill and stop share a lot of the same code... so just call stop
|
|
|
|
|
@ -766,8 +774,12 @@ function MonitorStream(monitorData) {
|
|
|
|
|
this.statusCmdTimer = clearInterval(this.statusCmdTimer);
|
|
|
|
|
} else if ((-1 !== this.activePlayer.indexOf('zms')) && this.connKey) {
|
|
|
|
|
this.streamCommand(CMD_PAUSE);
|
|
|
|
|
} else { // janus?
|
|
|
|
|
this.element.pause();
|
|
|
|
|
} else { // janus
|
|
|
|
|
if ('pause' in this.element) {
|
|
|
|
|
this.element.pause();
|
|
|
|
|
} else {
|
|
|
|
|
console.log('The "Pause" method cannot be called on the element', this.element);
|
|
|
|
|
}
|
|
|
|
|
this.statusCmdTimer = clearInterval(this.statusCmdTimer);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
@ -793,8 +805,12 @@ function MonitorStream(monitorData) {
|
|
|
|
|
this.statusCmdTimer = setInterval(this.statusCmdQuery.bind(this), statusRefreshTimeout);
|
|
|
|
|
} else if ((-1 !== this.activePlayer.indexOf('zms')) && this.connKey) {
|
|
|
|
|
this.streamCommand(CMD_PLAY);
|
|
|
|
|
} else {
|
|
|
|
|
this.element.play();
|
|
|
|
|
} else { // janus
|
|
|
|
|
if ('play' in this.element) {
|
|
|
|
|
this.element.play();
|
|
|
|
|
} else {
|
|
|
|
|
console.log('The "Play" method cannot be called on the element', this.element);
|
|
|
|
|
}
|
|
|
|
|
this.statusCmdTimer = setInterval(this.statusCmdQuery.bind(this), statusRefreshTimeout);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
@ -850,58 +866,62 @@ function MonitorStream(monitorData) {
|
|
|
|
|
this.onplay = func;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.getVolumeSlider = function(mid) {
|
|
|
|
|
|
|
|
|
|
this.getVolumeControls = function() {
|
|
|
|
|
// On Watch page slider has no ID, on Montage page it has ID
|
|
|
|
|
return (document.getElementById('volumeSlider')) ? document.getElementById('volumeSlider') : document.getElementById('volumeSlider'+mid);
|
|
|
|
|
return (document.getElementById('volumeControls')) ? document.getElementById('volumeControls') : document.getElementById('volumeControls'+this.id);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.getIconMute = function(mid) {
|
|
|
|
|
this.getVolumeSlider = function() {
|
|
|
|
|
// On Watch page slider has no ID, on Montage page it has ID
|
|
|
|
|
return (document.getElementById('volumeSlider')) ? document.getElementById('volumeSlider') : document.getElementById('volumeSlider'+this.id);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.getIconMute = function() {
|
|
|
|
|
// On Watch page icon has no ID, on Montage page it has ID
|
|
|
|
|
return (document.getElementById('controlMute')) ? document.getElementById('controlMute') : document.getElementById('controlMute'+mid);
|
|
|
|
|
return (document.getElementById('controlMute')) ? document.getElementById('controlMute') : document.getElementById('controlMute'+this.id);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.getAudioStream = function(mid) {
|
|
|
|
|
this.getAudioStream = function() {
|
|
|
|
|
/*
|
|
|
|
|
Go2RTC uses <video-stream id='liveStreamXX'><video></video></video-stream>,
|
|
|
|
|
RTSP2Web uses <video id='liveStreamXX'></video>
|
|
|
|
|
This.getElement() may need to be changed, but the implications of such a change need to be analyzed
|
|
|
|
|
*/
|
|
|
|
|
return (document.querySelector('#liveStream'+mid + ' video') || document.getElementById('liveStream'+mid));
|
|
|
|
|
return (document.querySelector('#liveStream'+this.id + ' video') || document.getElementById('liveStream'+this.id));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.listenerVolumechange = function(el) {
|
|
|
|
|
// System audio level change
|
|
|
|
|
const mid = this.id;
|
|
|
|
|
const audioStream = el.target;
|
|
|
|
|
const volumeSlider = this.getVolumeSlider(mid);
|
|
|
|
|
if (volumeSlider.allowSetValue) {
|
|
|
|
|
if (audioStream.muted === true) {
|
|
|
|
|
this.changeStateIconMute(mid, 'off');
|
|
|
|
|
volumeSlider.classList.add('noUi-mute');
|
|
|
|
|
} else {
|
|
|
|
|
this.changeStateIconMute(mid, 'on');
|
|
|
|
|
volumeSlider.classList.remove('noUi-mute');
|
|
|
|
|
}
|
|
|
|
|
if (volumeSlider) {
|
|
|
|
|
volumeSlider.noUiSlider.set(audioStream.volume * 100);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const volumeSlider = this.getVolumeSlider();
|
|
|
|
|
|
|
|
|
|
if (volumeSlider) {
|
|
|
|
|
volumeSlider.setAttribute('data-muted', audioStream.muted);
|
|
|
|
|
volumeSlider.setAttribute('data-volume', parseInt(audioStream.volume * 100));
|
|
|
|
|
if (volumeSlider.allowSetValue) {
|
|
|
|
|
volumeSlider.noUiSlider.set(audioStream.volume * 100);
|
|
|
|
|
if (audioStream.muted === true) {
|
|
|
|
|
this.changeStateIconMute('off');
|
|
|
|
|
volumeSlider.classList.add('noUi-mute');
|
|
|
|
|
} else {
|
|
|
|
|
this.changeStateIconMute('on');
|
|
|
|
|
volumeSlider.classList.remove('noUi-mute');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
console.warn(`volumeSlider for monitor with ID=${this.id} not found`);
|
|
|
|
|
}
|
|
|
|
|
if (currentView != 'montage') {
|
|
|
|
|
setCookie('zmWatchMuted', audioStream.muted);
|
|
|
|
|
setCookie('zmWatchVolume', parseInt(audioStream.volume * 100));
|
|
|
|
|
}
|
|
|
|
|
if (volumeSlider) {
|
|
|
|
|
volumeSlider.setAttribute('data-muted', audioStream.muted);
|
|
|
|
|
volumeSlider.setAttribute('data-volume', parseInt(audioStream.volume * 100));
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.createVolumeSlider = function() {
|
|
|
|
|
const mid = this.id;
|
|
|
|
|
const volumeSlider = this.getVolumeSlider(mid);
|
|
|
|
|
const iconMute = this.getIconMute(mid);
|
|
|
|
|
const audioStream = this.getAudioStream(mid);
|
|
|
|
|
const volumeSlider = this.getVolumeSlider();
|
|
|
|
|
const iconMute = this.getIconMute();
|
|
|
|
|
const audioStream = this.getAudioStream();
|
|
|
|
|
if (!volumeSlider || !audioStream) return;
|
|
|
|
|
const defaultVolume = (volumeSlider.getAttribute("data-volume") || 50);
|
|
|
|
|
if (volumeSlider.noUiSlider) volumeSlider.noUiSlider.destroy();
|
|
|
|
|
@ -968,12 +988,21 @@ function MonitorStream(monitorData) {
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.destroyVolumeSlider = function() {
|
|
|
|
|
const volumeSlider = this.getVolumeSlider();
|
|
|
|
|
const iconMute = this.getIconMute();
|
|
|
|
|
if (iconMute) iconMute.innerText = "";
|
|
|
|
|
if (volumeSlider && 'noUiSlider' in volumeSlider) volumeSlider.noUiSlider.destroy();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* volume: on || off
|
|
|
|
|
*/
|
|
|
|
|
this.changeStateIconMute = function(mid, volume) {
|
|
|
|
|
const iconMute = this.getIconMute(mid);
|
|
|
|
|
if (iconMute) {
|
|
|
|
|
this.changeStateIconMute = function(volume) {
|
|
|
|
|
const volumeControls = this.getVolumeControls();
|
|
|
|
|
const disabled = (volumeControls) ? volumeControls.classList.contains('disabled') : false;
|
|
|
|
|
const iconMute = this.getIconMute();
|
|
|
|
|
if (!disabled && iconMute) {
|
|
|
|
|
iconMute.innerHTML = (volume == 'on')? 'volume_up' : 'volume_off';
|
|
|
|
|
}
|
|
|
|
|
return iconMute;
|
|
|
|
|
@ -982,14 +1011,24 @@ function MonitorStream(monitorData) {
|
|
|
|
|
/*
|
|
|
|
|
* volume: on || off
|
|
|
|
|
*/
|
|
|
|
|
this.changeVolumeSlider = function(mid, volume) {
|
|
|
|
|
const volumeSlider = this.getVolumeSlider(mid);
|
|
|
|
|
this.changeVolumeSlider = function(volume) {
|
|
|
|
|
const volumeControls = this.getVolumeControls();
|
|
|
|
|
//const controlMute = document.querySelector('[id ^= "controlMute'+this.id+'"]');
|
|
|
|
|
const volumeSlider = this.getVolumeSlider();
|
|
|
|
|
|
|
|
|
|
if (volumeSlider) {
|
|
|
|
|
let disabled = false;
|
|
|
|
|
if (volumeControls) {
|
|
|
|
|
disabled = volumeControls.classList.contains('disabled');
|
|
|
|
|
}
|
|
|
|
|
if (volume == 'on') {
|
|
|
|
|
volumeSlider.classList.remove('noUi-mute');
|
|
|
|
|
} else if (volume == 'off') {
|
|
|
|
|
volumeSlider.classList.add('noUi-mute');
|
|
|
|
|
}
|
|
|
|
|
if (volumeSlider.noUiSlider) {
|
|
|
|
|
(disabled) ? volumeSlider.noUiSlider.disable() : volumeSlider.noUiSlider.enable();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return volumeSlider;
|
|
|
|
|
};
|
|
|
|
|
@ -998,40 +1037,113 @@ function MonitorStream(monitorData) {
|
|
|
|
|
* mode: switch, on, off
|
|
|
|
|
*/
|
|
|
|
|
this.controlMute = function(mode = 'switch') {
|
|
|
|
|
const mid = this.id;
|
|
|
|
|
let volumeSlider;
|
|
|
|
|
const audioStream = this.getAudioStream(mid);
|
|
|
|
|
if (!audioStream) {
|
|
|
|
|
console.log(`No audiostream! in controlMute for monitor ID=${mid}`);
|
|
|
|
|
let volumeSlider = this.getVolumeSlider();
|
|
|
|
|
const audioStream = this.getAudioStream();
|
|
|
|
|
const volumeControls = this.getVolumeControls();
|
|
|
|
|
const disabled = (volumeControls) ? volumeControls.classList.contains('disabled') : false;
|
|
|
|
|
|
|
|
|
|
if (volumeSlider && volumeSlider.noUiSlider) {
|
|
|
|
|
(disabled) ? volumeSlider.noUiSlider.disable() : volumeSlider.noUiSlider.enable();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (disabled) {
|
|
|
|
|
console.log(`Volume control is disabled in controlMute for monitor ID=${this.id}`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!audioStream) {
|
|
|
|
|
console.log(`No audiostream! in controlMute for monitor ID=${this.id}`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mode=='switch') {
|
|
|
|
|
if (audioStream.muted) {
|
|
|
|
|
audioStream.muted = this.muted = false;
|
|
|
|
|
this.changeStateIconMute(mid, 'on');
|
|
|
|
|
volumeSlider = this.changeVolumeSlider(mid, 'on');
|
|
|
|
|
if (volumeSlider) {
|
|
|
|
|
this.changeStateIconMute('on');
|
|
|
|
|
volumeSlider = this.changeVolumeSlider('on');
|
|
|
|
|
if (volumeSlider && volumeSlider.noUiSlider) {
|
|
|
|
|
audioStream.volume = volumeSlider.noUiSlider.get() / 100;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
audioStream.muted = this.muted = true;
|
|
|
|
|
this.changeStateIconMute(mid, 'off');
|
|
|
|
|
this.changeVolumeSlider(mid, 'off');
|
|
|
|
|
this.changeStateIconMute('off');
|
|
|
|
|
this.changeVolumeSlider('off');
|
|
|
|
|
}
|
|
|
|
|
} else if (mode=='on') {
|
|
|
|
|
audioStream.muted = this.muted = true;
|
|
|
|
|
this.changeStateIconMute(mid, 'off');
|
|
|
|
|
this.changeVolumeSlider(mid, 'off');
|
|
|
|
|
this.changeStateIconMute('off');
|
|
|
|
|
this.changeVolumeSlider('off');
|
|
|
|
|
} else if (mode=='off') {
|
|
|
|
|
audioStream.muted = this.muted = false;
|
|
|
|
|
this.changeStateIconMute(mid, 'on');
|
|
|
|
|
volumeSlider = this.changeVolumeSlider(mid, 'on');
|
|
|
|
|
if (volumeSlider) {
|
|
|
|
|
this.changeStateIconMute('on');
|
|
|
|
|
volumeSlider = this.changeVolumeSlider('on');
|
|
|
|
|
if (volumeSlider && volumeSlider.noUiSlider) {
|
|
|
|
|
audioStream.volume = volumeSlider.noUiSlider.get() / 100;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* mode = 'disable' || 'enable'
|
|
|
|
|
*/
|
|
|
|
|
this.volumeControlsHandler = function(mode) {
|
|
|
|
|
const volumeControls = this.getVolumeControls();
|
|
|
|
|
const volumeSlider = this.getVolumeSlider();
|
|
|
|
|
if (mode == 'disable') {
|
|
|
|
|
if (volumeControls) volumeControls.classList.add('disabled');
|
|
|
|
|
if (volumeSlider && volumeSlider.noUiSlider) {
|
|
|
|
|
volumeSlider.noUiSlider.disable();
|
|
|
|
|
}
|
|
|
|
|
} else if (mode == 'enable') {
|
|
|
|
|
if (volumeControls) volumeControls.classList.remove('disabled');
|
|
|
|
|
if (volumeSlider && volumeSlider.noUiSlider) {
|
|
|
|
|
volumeSlider.noUiSlider.enable();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/*IMPORTANT DO NOT CALL WITHOUT CONSCIOUS NEED!!!*/
|
|
|
|
|
// https://habr.com/ru/companies/timeweb/articles/667148/
|
|
|
|
|
this.getTracksFromStream = async function() {
|
|
|
|
|
this.mediaStream = this.audioTrack = this.videoTrack = null;
|
|
|
|
|
|
|
|
|
|
let streamCaptureNotSupported = false;
|
|
|
|
|
const el = (-1 !== this.activePlayer.indexOf('go2rtc')) ? document.querySelector('[id ^= "liveStream'+this.id+'"] video') : this.getElement();
|
|
|
|
|
let stream = null;
|
|
|
|
|
|
|
|
|
|
// We should NOT call captureStream again, as there may be problems with capturing the stream!
|
|
|
|
|
let moz = false; // Detecting Firefox
|
|
|
|
|
if ("captureStream" in el) {
|
|
|
|
|
stream = await el.captureStream();
|
|
|
|
|
} else if ("mozCaptureStreamUntilEnded" in el) {
|
|
|
|
|
stream = await el.mozCaptureStreamUntilEnded();
|
|
|
|
|
moz = true;
|
|
|
|
|
} else {
|
|
|
|
|
console.warn(`"captureStream" NOT found in STREAM for monitor ID=${this.id} or not supported by the browser.`);
|
|
|
|
|
streamCaptureNotSupported = true; // This will enable the volume control if the browser does not support captureStream (for example, Safari)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (stream) {
|
|
|
|
|
this.audioTrack = stream.getAudioTracks()[0];
|
|
|
|
|
this.videoTrack = stream.getVideoTracks()[0];
|
|
|
|
|
this.mediaStream = stream;
|
|
|
|
|
if (moz && this.audioTrack) {
|
|
|
|
|
// Fix Firefox https://stackoverflow.com/questions/72401396/usage-of-mozcapturestream-stop-audio-output-of-video-element
|
|
|
|
|
const ctx = new AudioContext();
|
|
|
|
|
const dest = ctx.createMediaStreamSource(stream);
|
|
|
|
|
dest.connect(ctx.destination);
|
|
|
|
|
}
|
|
|
|
|
} else if (!streamCaptureNotSupported) {
|
|
|
|
|
console.warn(`Failed to capture stream for monitor ID=${this.id} while receiving tracks.`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.debug(`mediaStream for ID=${this.id}:`, this.mediaStream);
|
|
|
|
|
console.debug(`audioTrack for ID=${this.id}:`, this.audioTrack);
|
|
|
|
|
console.debug(`videoTrack for ID=${this.id}:`, this.videoTrack);
|
|
|
|
|
(this.audioTrack || streamCaptureNotSupported) ? this.volumeControlsHandler('enable') : this.volumeControlsHandler('disable');
|
|
|
|
|
|
|
|
|
|
//this.connectAudioMotion();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.setStateClass = function(jobj, stateClass) {
|
|
|
|
|
if (!jobj) {
|
|
|
|
|
console.log("No obj in setStateClass");
|
|
|
|
|
@ -1385,7 +1497,7 @@ function MonitorStream(monitorData) {
|
|
|
|
|
// We correct the lag from real time. Relevant for long viewing and network problems.
|
|
|
|
|
if (-1 !== this.activePlayer.indexOf('mse')) {
|
|
|
|
|
const videoEl = document.getElementById("liveStream" + this.id);
|
|
|
|
|
if (this.wsMSE && videoEl.buffered != undefined && videoEl.buffered.length > 0) {
|
|
|
|
|
if (this.wsMSE && videoEl && videoEl.buffered != undefined && videoEl.buffered.length > 0) {
|
|
|
|
|
const videoElCurrentTime = videoEl.currentTime; // Current time of playback
|
|
|
|
|
const currentTime = (Date.now() / 1000);
|
|
|
|
|
const deltaRealTime = (currentTime - this.streamStartTime).toFixed(2); // How much real time has passed since playback started
|
|
|
|
|
@ -1416,13 +1528,21 @@ function MonitorStream(monitorData) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if (!this.wsMSE && this.started) {
|
|
|
|
|
console.warn(`UNSCHEDULED CLOSE SOCKET for camera ID=${this.id}`);
|
|
|
|
|
this.restart(this.currentChannelStream);
|
|
|
|
|
if (this.mse.readyState == 'open') {
|
|
|
|
|
console.warn(`UNSCHEDULED CLOSE SOCKET for camera ID=${this.id} RESTART is started.`);
|
|
|
|
|
this.restart(this.currentChannelStream);
|
|
|
|
|
} else {
|
|
|
|
|
console.log(`MediaSource for camera ID=${this.id} is in state "${this.mse.readyState.toUpperCase()}"`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if (-1 !== this.player.indexOf('webrtc')) {
|
|
|
|
|
if ((!this.webrtc || (this.webrtc && this.webrtc.connectionState != "connected")) && this.started) {
|
|
|
|
|
console.warn(`UNSCHEDULED CLOSE WebRTC for camera ID=${this.id}`);
|
|
|
|
|
this.restart(this.currentChannelStream);
|
|
|
|
|
if (this.webrtc && (this.webrtc.connectionState == "new" || this.webrtc.connectionState == "connecting")) {
|
|
|
|
|
console.log(`Waiting WebRTC connection for camera ID=${this.id} State="${this.webrtc.connectionState}"`);
|
|
|
|
|
} else {
|
|
|
|
|
console.warn(`UNSCHEDULED CLOSE WebRTC for camera ID=${this.id}`, this.webrtc, this.started);
|
|
|
|
|
this.restart(this.currentChannelStream);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} // end if Go2RTC or RTSP2Web
|
|
|
|
|
@ -1523,9 +1643,12 @@ function MonitorStream(monitorData) {
|
|
|
|
|
}; // end setMaxFPS
|
|
|
|
|
|
|
|
|
|
this.closeWebSocket = function() {
|
|
|
|
|
console.log(`${dateTimeToISOLocal(new Date())} WebSocket for a video object ID=${this.id} is being closed.`);
|
|
|
|
|
if (this.wsMSE && this.wsMSE.readyState !== WebSocket.CLOSING && this.wsMSE.readyState !== WebSocket.CLOSED) {
|
|
|
|
|
if (!this.wsMSE ||
|
|
|
|
|
(this.wsMSE && (this.wsMSE.readyState === WebSocket.CLOSING || this.wsMSE.readyState === WebSocket.CLOSED))) {
|
|
|
|
|
console.log(`${dateTimeToISOLocal(new Date())} WebSocket for a video object ID=${this.id} is already in the process of closing or has already been closed.`);
|
|
|
|
|
} else {
|
|
|
|
|
//Socket may still be in the "CONNECTING" state. It would be better to wait for the connection and only then close it, but we will not complicate the code, since this happens rarely and does not globally affect the overall work.
|
|
|
|
|
console.log(`${dateTimeToISOLocal(new Date())} WebSocket for a video object ID=${this.id} is being closed.`);
|
|
|
|
|
this.wsMSE.close(1000, "We close the connection");
|
|
|
|
|
}
|
|
|
|
|
this.mseQueue = []; // ABSOLUTELY NEEDED
|
|
|
|
|
@ -1582,7 +1705,14 @@ function MonitorStream(monitorData) {
|
|
|
|
|
};
|
|
|
|
|
} // end class MonitorStream
|
|
|
|
|
|
|
|
|
|
async function attachVideo(id, pin) {
|
|
|
|
|
/* +++ Janus */
|
|
|
|
|
async function attachVideo(monitorStream) {
|
|
|
|
|
const id = parseInt(monitorStream.id);
|
|
|
|
|
const pin = monitorStream.janusPin;
|
|
|
|
|
if (!janus || !('isConnected' in janus)) {
|
|
|
|
|
console.log(`The Janus object for the camera with ID=${id} does not exist.`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
await waitUntil(() => janus.isConnected() );
|
|
|
|
|
janus.attach({
|
|
|
|
|
plugin: "janus.plugin.streaming",
|
|
|
|
|
@ -1635,9 +1765,18 @@ async function attachVideo(id, pin) {
|
|
|
|
|
}
|
|
|
|
|
}, //onmessage function
|
|
|
|
|
onremotestream: function(ourstream) {
|
|
|
|
|
Janus.debug(" ::: Got a remote stream :::");
|
|
|
|
|
Janus.debug(ourstream);
|
|
|
|
|
Janus.attachMediaStream(document.getElementById("liveStream" + id), ourstream);
|
|
|
|
|
if (monitorStream.started) {
|
|
|
|
|
Janus.debug(" ::: Got a remote stream :::");
|
|
|
|
|
Janus.debug(ourstream);
|
|
|
|
|
if (ourstream.active) {
|
|
|
|
|
Janus.attachMediaStream(document.getElementById("liveStream" + id), ourstream);
|
|
|
|
|
} else {
|
|
|
|
|
Janus.debug("Janus stream is not active. Restart.");
|
|
|
|
|
monitorStream.restart();
|
|
|
|
|
}
|
|
|
|
|
monitorStream.updateStreamInfo('', ''); //JANUS
|
|
|
|
|
monitorStream.getTracksFromStream(); //JANUS
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
onremotetrack: function(track, mid, on) {
|
|
|
|
|
Janus.debug(" ::: Got a remote track :::");
|
|
|
|
|
@ -1672,7 +1811,10 @@ const waitUntil = (condition) => {
|
|
|
|
|
}, 100);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
/* --- Janus */
|
|
|
|
|
|
|
|
|
|
/* +++ What is this ? */
|
|
|
|
|
/* https://github.com/ZoneMinder/zoneminder/commit/a26a2e8020ca910043bc4a8c7e61fb623ba8bc4a */
|
|
|
|
|
async function get_PeerConnection(media, videoEl) {
|
|
|
|
|
const pc = new RTCPeerConnection({
|
|
|
|
|
bundlePolicy: 'max-bundle',
|
|
|
|
|
@ -1729,6 +1871,7 @@ async function getMediaTracks(media, constraints) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/* --- What is this ? */
|
|
|
|
|
|
|
|
|
|
function startRTSP2WebPlay(videoEl, url, stream) {
|
|
|
|
|
if (typeof RTCPeerConnection !== 'function') {
|
|
|
|
|
@ -1738,6 +1881,7 @@ function startRTSP2WebPlay(videoEl, url, stream) {
|
|
|
|
|
stream.RTSP2WebType = null; // Avoid repeated restarts.
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
stream.updateStreamInfo('', 'loading');
|
|
|
|
|
|
|
|
|
|
if (stream.webrtc) {
|
|
|
|
|
stream.webrtc.close();
|
|
|
|
|
@ -1773,19 +1917,29 @@ function startRTSP2WebPlay(videoEl, url, stream) {
|
|
|
|
|
await stream.webrtc.setLocalDescription(offer);
|
|
|
|
|
//console.log(stream.webrtc.localDescription.sdp);
|
|
|
|
|
|
|
|
|
|
$j.post(url, {
|
|
|
|
|
data: btoa(stream.webrtc.localDescription.sdp)
|
|
|
|
|
}, function(data) {
|
|
|
|
|
if ((stream.webrtc && 'sctp' in stream.webrtc && stream.webrtc.sctp) && stream.webrtc.sctp.state != 'stable') {
|
|
|
|
|
//console.log(data);
|
|
|
|
|
try {
|
|
|
|
|
stream.webrtc.setRemoteDescription(new RTCSessionDescription({
|
|
|
|
|
type: 'answer',
|
|
|
|
|
sdp: atob(data)
|
|
|
|
|
}));
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.warn(e);
|
|
|
|
|
$j.ajax({
|
|
|
|
|
url: url,
|
|
|
|
|
method: 'POST',
|
|
|
|
|
data: {data: btoa(stream.webrtc.localDescription.sdp)},
|
|
|
|
|
success: function(response) {
|
|
|
|
|
if ((stream.webrtc && 'sctp' in stream.webrtc && stream.webrtc.sctp) && stream.webrtc.sctp.state != 'stable') {
|
|
|
|
|
try {
|
|
|
|
|
stream.webrtc.setRemoteDescription(new RTCSessionDescription({
|
|
|
|
|
type: 'answer',
|
|
|
|
|
sdp: atob(response)
|
|
|
|
|
}));
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.warn(e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
error: function(xhr, status, error) {
|
|
|
|
|
console.warn('Error request localDescription:', error, xhr.responseText);
|
|
|
|
|
stream.updateStreamInfo('', 'Error'); //WEBRTC
|
|
|
|
|
stream.kill();
|
|
|
|
|
},
|
|
|
|
|
complete: function() {
|
|
|
|
|
//console.log('Request localDescription completed.');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
@ -1793,6 +1947,7 @@ function startRTSP2WebPlay(videoEl, url, stream) {
|
|
|
|
|
stream.webrtc.onsignalingstatechange = async function signalingstatechange() {
|
|
|
|
|
switch (stream.webrtc.signalingState) {
|
|
|
|
|
case 'have-local-offer':
|
|
|
|
|
//console.log("webrtc.onsignalingstatechange (connectionState): ", stream.webrtc.connectionState);
|
|
|
|
|
break;
|
|
|
|
|
case 'stable':
|
|
|
|
|
/*
|
|
|
|
|
@ -1819,13 +1974,17 @@ function startRTSP2WebPlay(videoEl, url, stream) {
|
|
|
|
|
|
|
|
|
|
const webrtcSendChannel = stream.webrtc.createDataChannel('rtsptowebSendChannel');
|
|
|
|
|
webrtcSendChannel.onopen = (event) => {
|
|
|
|
|
console.log(`${webrtcSendChannel.label} has opened`);
|
|
|
|
|
stream.updateStreamInfo('', ''); //WEBRTC
|
|
|
|
|
stream.getTracksFromStream(); //WEBRTC
|
|
|
|
|
console.log(`${webrtcSendChannel.label} for camera ID=${stream.id} has opened`);
|
|
|
|
|
webrtcSendChannel.send('ping');
|
|
|
|
|
};
|
|
|
|
|
webrtcSendChannel.onclose = (_event) => {
|
|
|
|
|
console.log(`${webrtcSendChannel.label} has closed`);
|
|
|
|
|
if (stream.started) {
|
|
|
|
|
startRTSP2WebPlay(videoEl, url, stream);
|
|
|
|
|
console.warn(`UNSCHEDULED CLOSE ${webrtcSendChannel.label} for camera ID=${stream.id}. We execute "stream.restart"`);
|
|
|
|
|
stream.restart(stream.currentChannelStream);
|
|
|
|
|
} else {
|
|
|
|
|
console.log(`${webrtcSendChannel.label} for camera ID=${stream.id} has closed`);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
webrtcSendChannel.onmessage = (event) => console.log(event.data);
|
|
|
|
|
@ -1843,14 +2002,14 @@ function mseListenerSourceopen(context, videoEl, url) {
|
|
|
|
|
context.wsMSE.binaryType = 'arraybuffer';
|
|
|
|
|
|
|
|
|
|
context.wsMSE.onopen = function(event) {
|
|
|
|
|
console.log(`Connect to ws for a video object ID=${context.id}`);
|
|
|
|
|
console.log(`Connect to WebSocket MSE for a video object ID=${context.id}`);
|
|
|
|
|
};
|
|
|
|
|
context.wsMSE.onclose = (event) => {
|
|
|
|
|
context.clearWebSocket();
|
|
|
|
|
console.log(`${dateTimeToISOLocal(new Date())} WebSocket CLOSED for a video object ID=${context.id}.`);
|
|
|
|
|
console.log(`${dateTimeToISOLocal(new Date())} WebSocket MSE CLOSED for a video object ID=${context.id}.`);
|
|
|
|
|
};
|
|
|
|
|
context.wsMSE.onerror = function(event) {
|
|
|
|
|
console.warn(`${dateTimeToISOLocal(new Date())} WebSocket ERROR for a video object ID=${context.id}:`, event);
|
|
|
|
|
console.warn(`${dateTimeToISOLocal(new Date())} WebSocket MSE ERROR for a video object ID=${context.id}:`, event);
|
|
|
|
|
if (this.started) this.restart();
|
|
|
|
|
};
|
|
|
|
|
context.wsMSE.onmessage = function(event) {
|
|
|
|
|
@ -1866,9 +2025,9 @@ function mseListenerSourceopen(context, videoEl, url) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (MediaSource.isTypeSupported('video/mp4; codecs="' + mimeCodec + '"')) {
|
|
|
|
|
console.log(`For a video object ID=${context.id} codec used: ${mimeCodec}`);
|
|
|
|
|
console.log(`WebSocket MSE for a video object ID=${context.id} codec used: ${mimeCodec}`);
|
|
|
|
|
} else {
|
|
|
|
|
const msg = `For a video object ID=${context.id} codec '${mimeCodec}' not supported. Monitor '${context.name}' ID=${context.id} not starting.`;
|
|
|
|
|
const msg = `WebSocket MSE for a video object ID=${context.id} codec '${mimeCodec}' not supported. Monitor '${context.name}' ID=${context.id} not starting.`;
|
|
|
|
|
console.log(msg);
|
|
|
|
|
context.getElement().before(document.createTextNode(msg));
|
|
|
|
|
context.stop();
|
|
|
|
|
@ -1910,10 +2069,12 @@ function startMsePlay(context, videoEl, url) {
|
|
|
|
|
|
|
|
|
|
context.mse = new MediaSource();
|
|
|
|
|
videoEl.onplay = (event) => {
|
|
|
|
|
context.updateStreamInfo('', ''); //MSE
|
|
|
|
|
context.getTracksFromStream(); //MSE
|
|
|
|
|
context.streamStartTime = (Date.now() / 1000).toFixed(2);
|
|
|
|
|
if (videoEl.buffered.length > 0 && videoEl.currentTime < videoEl.buffered.end(videoEl.buffered.length - 1) - 0.1) {
|
|
|
|
|
//For example, after a pause you press Play, you need to adjust the time.
|
|
|
|
|
console.debug(`${dateTimeToISOLocal(new Date())} Adjusting currentTime for a video object ID=${context.id} Lag='${(videoEl.buffered.end(videoEl.buffered.length - 1) - videoEl.currentTime).toFixed(2)}sec.`);
|
|
|
|
|
console.debug(`${dateTimeToISOLocal(new Date())} WebSocket MSE adjusting currentTime for a video object ID=${context.id} Lag='${(videoEl.buffered.end(videoEl.buffered.length - 1) - videoEl.currentTime).toFixed(2)}sec.`);
|
|
|
|
|
videoEl.currentTime = videoEl.buffered.end(videoEl.buffered.length - 1) - 0.1;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|