diff options
author | Maxim Devaev <[email protected]> | 2022-11-29 09:25:09 +0300 |
---|---|---|
committer | Maxim Devaev <[email protected]> | 2022-11-29 09:36:47 +0300 |
commit | 6a339238ffa5907ef99b537918b1861c27740265 (patch) | |
tree | b05a703e819c1b44239cfcef9cbcc550083e6600 /web | |
parent | 1d7ef25a0bcd42792c37dde4e985e85bb28d0a05 (diff) |
web: split stream.js
Diffstat (limited to 'web')
-rw-r--r-- | web/share/js/kvm/stream.js | 472 | ||||
-rw-r--r-- | web/share/js/kvm/stream_janus.js | 362 | ||||
-rw-r--r-- | web/share/js/kvm/stream_mjpeg.js | 158 |
3 files changed, 534 insertions, 458 deletions
diff --git a/web/share/js/kvm/stream.js b/web/share/js/kvm/stream.js index 34715ae3..fef59c49 100644 --- a/web/share/js/kvm/stream.js +++ b/web/share/js/kvm/stream.js @@ -26,437 +26,9 @@ import {tools, $} from "../tools.js"; import {wm} from "../wm.js"; +import {JanusStreamer} from "./stream_janus.js"; +import {MjpegStreamer} from "./stream_mjpeg.js"; -var _Janus = null; - - -function _JanusStreamer(__setActive, __setInactive, __setInfo) { - var self = this; - - var __stop = false; - var __ensuring = false; - - var __janus = null; - var __handle = null; - - var __retry_ensure_timeout = null; - var __retry_emsg_timeout = null; - var __info_interval = null; - - var __state = null; - var __frames = 0; - - self.getName = () => "H.264"; - self.getMode = () => "janus"; - - self.getResolution = function() { - let el = $("stream-video"); - return { - // Разрешение видео или элемента - "real_width": (el.videoWidth || el.offsetWidth), - "real_height": (el.videoHeight || el.offsetHeight), - "view_width": el.offsetWidth, - "view_height": el.offsetHeight, - }; - }; - - self.ensureStream = function(state) { - __state = state; - __stop = false; - __ensureJanus(false); - }; - - self.stopStream = function() { - __stop = true; - __destroyJanus(); - }; - - var __ensureJanus = function(internal) { - if (__janus === null && !__stop && (!__ensuring || internal)) { - __setInactive(); - __setInfo(false, false, ""); - __ensuring = true; - __logInfo("Starting Janus ..."); - __janus = new _Janus({ - "server": `${tools.is_https ? "wss" : "ws"}://${location.host}/janus/ws`, - "ipv6": true, - "destroyOnUnload": false, - "success": __attachJanus, - "error": function(error) { - __logError(error); - __setInfo(false, false, error); - __finishJanus(); - }, - }); - } - }; - - var __finishJanus = function() { - if (__stop) { - if (__retry_ensure_timeout !== null) { - clearTimeout(__retry_ensure_timeout); - __retry_ensure_timeout = null; - } - __ensuring = false; - } else { - if (__retry_ensure_timeout === null) { - __retry_ensure_timeout = setTimeout(function() { - __retry_ensure_timeout = null; - __ensureJanus(true); - }, 5000); - } - } - __stopRetryEmsgInterval(); - __stopInfoInterval(); - __handle = null; - __janus = null; - __setInactive(); - if (__stop) { - __setInfo(false, false, ""); - } - }; - - var __destroyJanus = function() { - if (__handle && __handle.webrtcStuff && __handle.webrtcStuff.remoteStream) { - for (let track of __handle.webrtcStuff.remoteStream.getTracks()) { - track.stop(); - __handle.webrtcStuff.remoteStream.removeTrack(track); - } - __handle.webrtcStuff.remoteStream = null; - } - $("stream-video").srcObject = null; - __setAudioEnabled(false); - if (__janus !== null) { - __janus.destroy(); - } - __finishJanus(); - }; - - var __attachJanus = function() { - if (__janus === null) { - return; - } - __janus.attach({ - "plugin": "janus.plugin.ustreamer", - "opaqueId": "oid-" + _Janus.randomString(12), - - "success": function(handle) { - __handle = handle; - __logInfo("uStreamer attached:", handle.getPlugin(), handle.getId()); - __sendWatch(); - }, - - "error": function(error) { - __logError("Can't attach uStreamer: ", error); - __setInfo(false, false, error); - __destroyJanus(); - }, - - "iceState": function(state) { - __logInfo("ICE state changed to", state); - // Если раскомментировать, то он начнет дрючить соединение, - // так как каллбек вызывает сильно после завершения работы - /*if (state === "disconnected") { - __destroyJanus(); - }*/ - }, - - "webrtcState": function(up) { - __logInfo("Janus says our WebRTC PeerConnection is", (up ? "up" : "down"), "now"); - if (up) { - __sendKeyRequired(); - } - }, - - "onmessage": function(msg, jsep) { - __stopRetryEmsgInterval(); - - if (msg.result) { - __logInfo("Got uStreamer result message:", msg.result.status); // starting, started, stopped - if (msg.result.status === "started") { - __setActive(); - __setInfo(false, false, ""); - } else if (msg.result.status === "stopped") { - __setInactive(); - __setInfo(false, false, ""); - } else if (msg.result.status === "features") { - __setAudioEnabled(msg.result.features.audio); - } - } else if (msg.error_code || msg.error) { - __logError("Got uStreamer error message:", msg.error_code, "-", msg.error); - __setInfo(false, false, msg.error); - if (__retry_emsg_timeout === null) { - __retry_emsg_timeout = setTimeout(function() { - if (!__stop) { - __sendStop(); - __sendWatch(); - } - __retry_emsg_timeout = null; - }, 2000); - } - return; - } else { - __logInfo("Got uStreamer other message:", msg); - } - - if (jsep) { - __logInfo("Handling SDP:", jsep); - __handle.createAnswer({ - "jsep": jsep, - "media": {"audioSend": false, "videoSend": false, "data": false}, - - "success": function(jsep) { - __logInfo("Got SDP:", jsep); - __sendStart(jsep); - }, - - "error": function(error) { - __logInfo("Error on SDP handling:", error); - __setInfo(false, false, error); - //__destroyJanus(); - }, - }); - } - }, - - "onremotestream": function(stream) { - __logInfo("Got a remote stream:", stream); - _Janus.attachMediaStream($("stream-video"), stream); - __sendKeyRequired(); - __startInfoInterval(); - // FIXME: Задержка уменьшается, но начинаются заикания на кейфреймах. - // - https://github.com/Glimesh/janus-ftl-plugin/issues/101 - /*if (__handle && __handle.webrtcStuff && __handle.webrtcStuff.pc) { - for (let receiver of __handle.webrtcStuff.pc.getReceivers()) { - if (receiver.track && receiver.track.kind === "video" && receiver.playoutDelayHint !== undefined) { - receiver.playoutDelayHint = 0; - } - } - }*/ - }, - - "oncleanup": function() { - __logInfo("Got a cleanup notification"); - __stopInfoInterval(); - }, - }); - }; - - var __setAudioEnabled = function(enabled) { - tools.feature.setEnabled($("stream-audio"), enabled); - }; - - var __startInfoInterval = function() { - __stopInfoInterval(); - __setActive(); - __updateInfo(); - __info_interval = setInterval(__updateInfo, 1000); - }; - - var __stopInfoInterval = function() { - if (__info_interval !== null) { - clearInterval(__info_interval); - } - __info_interval = null; - }; - - var __stopRetryEmsgInterval = function() { - if (__retry_emsg_timeout !== null) { - clearTimeout(__retry_emsg_timeout); - __retry_emsg_timeout = null; - } - }; - - var __updateInfo = function() { - if (__handle !== null) { - let online = !!(__state && __state.source && __state.source.online); - let info = ""; - if (__handle !== null) { - // https://wiki.whatwg.org/wiki/Video_Metrics - let frames = null; - let el = $("stream-video"); - if (el.webkitDecodedFrameCount !== undefined) { - frames = el.webkitDecodedFrameCount; - } else if (el.mozPaintedFrames !== undefined) { - frames = el.mozPaintedFrames; - } - if (frames !== null) { - info = `${Math.max(0, frames - __frames)} fps dynamic`; - __frames = frames; - } else { - info = `${__handle.getBitrate()}`.replace("kbits/sec", "kbps"); - } - } - __setInfo(true, online, info); - } - }; - - var __sendWatch = function() { - if (__handle) { - __logInfo("Sending WATCH + FEATURES ..."); - __handle.send({"message": {"request": "features"}}); - __handle.send({"message": {"request": "watch", "params": {"audio": true}}}); - } - }; - - var __sendStart = function(jsep) { - if (__handle) { - __logInfo("Sending START ..."); - __handle.send({"message": {"request": "start"}, "jsep": jsep}); - } - }; - - var __sendKeyRequired = function() { - /*if (__handle) { - // На этом шаге мы говорим что стрим пошел и надо запросить кейфрейм - __logInfo("Sending KEY_REQUIRED ..."); - __handle.send({message: {request: "key_required"}}); - }*/ - }; - - var __sendStop = function() { - __stopInfoInterval(); - if (__handle) { - __logInfo("Sending STOP ..."); - __handle.send({"message": {"request": "stop"}}); - __handle.hangup(); - } - }; - - var __logInfo = (...args) => tools.info("Stream [Janus]:", ...args); - var __logError = (...args) => tools.error("Stream [Janus]:", ...args); -} - -function _MjpegStreamer(__setActive, __setInactive, __setInfo) { - var self = this; - - /************************************************************************/ - - var __key = tools.makeId(); - var __id = ""; - var __fps = -1; - var __state = null; - - var __timer = null; - var __timer_retries = 0; - - /************************************************************************/ - - self.getName = () => "MJPEG"; - self.getMode = () => "mjpeg"; - - self.getResolution = function() { - let el = $("stream-image"); - return { - "real_width": el.naturalWidth, - "real_height": el.naturalHeight, - "view_width": el.offsetWidth, - "view_height": el.offsetHeight, - }; - }; - - self.ensureStream = function(state) { - if (state) { - __state = state; - __findId(); - if (__id.length > 0 && __id in __state.stream.clients_stat) { - __setStreamActive(); - __stopChecking(); - } else { - __ensureChecking(); - } - } else { - __stopChecking(); - __setStreamInactive(); - } - }; - - self.stopStream = function() { - self.ensureStream(null); - let blank = "/share/png/blank-stream.png"; - if (!String.prototype.endsWith.call($("stream-image").src, blank)) { - $("stream-image").src = blank; - } - }; - - var __setStreamActive = function() { - let old_fps = __fps; - __fps = __state.stream.clients_stat[__id].fps; - if (old_fps < 0) { - __logInfo("Active"); - __setActive(); - } - __setInfo(true, __state.source.online, `${__fps} fps dynamic`); - }; - - var __setStreamInactive = function() { - let old_fps = __fps; - __key = tools.makeId(); - __id = ""; - __fps = -1; - __state = null; - if (old_fps >= 0) { - __logInfo("Inactive"); - __setInactive(); - __setInfo(false, false, ""); - } - }; - - var __ensureChecking = function() { - if (!__timer) { - __timer_retries = 10; - __timer = setInterval(__checkStream, 100); - } - }; - - var __stopChecking = function() { - if (__timer) { - clearInterval(__timer); - } - __timer = null; - __timer_retries = 0; - }; - - var __findId = function() { - let stream_client = tools.cookies.get("stream_client"); - if (__id.length === 0 && stream_client && stream_client.startsWith(__key + "/")) { - __logInfo("Found acceptable stream_client cookie:", stream_client); - __id = stream_client.slice(stream_client.indexOf("/") + 1); - } - }; - - var __checkStream = function() { - __findId(); - - if (__id.legnth > 0 && __id in __state.stream.clients_stat) { - __setStreamActive(); - __stopChecking(); - - } else if (__id.length > 0 && __timer_retries >= 0) { - __timer_retries -= 1; - - } else { - __setStreamInactive(); - __stopChecking(); - - let path = `/streamer/stream?key=${__key}`; - if (tools.browser.is_safari || tools.browser.is_ios) { - // uStreamer fix for WebKit - __logInfo("Using dual_final_frames=1 to fix WebKit bugs"); - path += "&dual_final_frames=1"; - } else if (tools.browser.is_chrome || tools.browser.is_blink) { - // uStreamer fix for Blink https://bugs.chromium.org/p/chromium/issues/detail?id=527446 - __logInfo("Using advance_headers=1 to fix Blink bugs"); - path += "&advance_headers=1"; - } - - __logInfo("Refreshing ..."); - $("stream-image").src = path; - } - }; - - var __logInfo = (...args) => tools.info("Stream [MJPEG]:", ...args); -} export function Streamer() { var self = this; @@ -470,7 +42,7 @@ export function Streamer() { var __resolution = {"width": 640, "height": 480}; var __init__ = function() { - __streamer = new _MjpegStreamer(__setActive, __setInactive, __setInfo); + __streamer = new MjpegStreamer(__setActive, __setInactive, __setInfo); $("stream-led").title = "Stream inactive"; @@ -536,40 +108,24 @@ export function Streamer() { }; self.setJanusEnabled = function(enabled) { - let has_webrtc = !!window.RTCPeerConnection; - - let has_h264 = true; - if ($("stream-video").canPlayType) { - has_h264 = $("stream-video").canPlayType("video/mp4; codecs=\"avc1.42E01F\""); - } + let has_webrtc = JanusStreamer.is_webrtc_available(); + let has_h264 = JanusStreamer.is_h264_available(); let set_enabled = function() { tools.hidden.setVisible($("stream-message-no-webrtc"), !has_webrtc); tools.hidden.setVisible($("stream-message-no-h264"), !has_h264); - __janus_enabled = (enabled && has_webrtc && _Janus !== null); // Don't check has_h264 for sure + __janus_enabled = (enabled && has_webrtc && JanusStreamer.is_imported()); // Don't check has_h264 for sure tools.feature.setEnabled($("stream-mode"), __janus_enabled); - tools.info(`Stream: Janus WebRTC state: enabled=${enabled}, webrtc=${has_webrtc}, h264=${has_h264}, imported=${!!_Janus}`); + tools.info( + `Stream: Janus WebRTC state: enabled=${enabled},` + + ` webrtc=${has_webrtc}, h264=${has_h264}, imported=${JanusStreamer.is_imported()}` + ); tools.radio.clickValue("stream-mode-radio", tools.storage.get("stream.mode", "janus")); self.setState(__state); }; if (enabled && has_webrtc) { - if (_Janus === null) { - import("./janus.js").then((module) => { - module.Janus.init({ - "debug": "all", - "callback": function() { - _Janus = module.Janus; - set_enabled(); - }, - }); - }).catch((err) => { - tools.error("Stream: Can't import Janus module:", err); - set_enabled(); - }); - } else { - set_enabled(); - } + JanusStreamer.ensure_janus(set_enabled); } else { set_enabled(); } @@ -683,7 +239,7 @@ export function Streamer() { }; var __clickModeRadio = function() { - if (_Janus !== null) { + if (JanusStreamer.is_imported()) { let mode = tools.radio.getValue("stream-mode-radio"); tools.storage.set("stream.mode", mode); if (mode !== __streamer.getMode()) { @@ -691,10 +247,10 @@ export function Streamer() { tools.hidden.setVisible($("stream-video"), (mode === "janus")); if (mode === "janus") { __streamer.stopStream(); - __streamer = new _JanusStreamer(__setActive, __setInactive, __setInfo); + __streamer = new JanusStreamer(__setActive, __setInactive, __setInfo); } else { // mjpeg __streamer.stopStream(); - __streamer = new _MjpegStreamer(__setActive, __setInactive, __setInfo); + __streamer = new MjpegStreamer(__setActive, __setInactive, __setInfo); } if (wm.isWindowVisible($("stream-window"))) { __streamer.ensureStream(__state); diff --git a/web/share/js/kvm/stream_janus.js b/web/share/js/kvm/stream_janus.js new file mode 100644 index 00000000..eece0124 --- /dev/null +++ b/web/share/js/kvm/stream_janus.js @@ -0,0 +1,362 @@ +/***************************************************************************** +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2022 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +*****************************************************************************/ + + +"use strict"; + + +import {tools, $} from "../tools.js"; + + +var _Janus = null; + + +export function JanusStreamer(__setActive, __setInactive, __setInfo) { + var self = this; + + var __stop = false; + var __ensuring = false; + + var __janus = null; + var __handle = null; + + var __retry_ensure_timeout = null; + var __retry_emsg_timeout = null; + var __info_interval = null; + + var __state = null; + var __frames = 0; + + self.getName = () => "H.264"; + self.getMode = () => "janus"; + + self.getResolution = function() { + let el = $("stream-video"); + return { + // Разрешение видео или элемента + "real_width": (el.videoWidth || el.offsetWidth), + "real_height": (el.videoHeight || el.offsetHeight), + "view_width": el.offsetWidth, + "view_height": el.offsetHeight, + }; + }; + + self.ensureStream = function(state) { + __state = state; + __stop = false; + __ensureJanus(false); + }; + + self.stopStream = function() { + __stop = true; + __destroyJanus(); + }; + + var __ensureJanus = function(internal) { + if (__janus === null && !__stop && (!__ensuring || internal)) { + __setInactive(); + __setInfo(false, false, ""); + __ensuring = true; + __logInfo("Starting Janus ..."); + __janus = new _Janus({ + "server": `${tools.is_https ? "wss" : "ws"}://${location.host}/janus/ws`, + "ipv6": true, + "destroyOnUnload": false, + "success": __attachJanus, + "error": function(error) { + __logError(error); + __setInfo(false, false, error); + __finishJanus(); + }, + }); + } + }; + + var __finishJanus = function() { + if (__stop) { + if (__retry_ensure_timeout !== null) { + clearTimeout(__retry_ensure_timeout); + __retry_ensure_timeout = null; + } + __ensuring = false; + } else { + if (__retry_ensure_timeout === null) { + __retry_ensure_timeout = setTimeout(function() { + __retry_ensure_timeout = null; + __ensureJanus(true); + }, 5000); + } + } + __stopRetryEmsgInterval(); + __stopInfoInterval(); + __handle = null; + __janus = null; + __setInactive(); + if (__stop) { + __setInfo(false, false, ""); + } + }; + + var __destroyJanus = function() { + if (__handle && __handle.webrtcStuff && __handle.webrtcStuff.remoteStream) { + for (let track of __handle.webrtcStuff.remoteStream.getTracks()) { + track.stop(); + __handle.webrtcStuff.remoteStream.removeTrack(track); + } + __handle.webrtcStuff.remoteStream = null; + } + $("stream-video").srcObject = null; + __setAudioEnabled(false); + if (__janus !== null) { + __janus.destroy(); + } + __finishJanus(); + }; + + var __attachJanus = function() { + if (__janus === null) { + return; + } + __janus.attach({ + "plugin": "janus.plugin.ustreamer", + "opaqueId": "oid-" + _Janus.randomString(12), + + "success": function(handle) { + __handle = handle; + __logInfo("uStreamer attached:", handle.getPlugin(), handle.getId()); + __sendWatch(); + }, + + "error": function(error) { + __logError("Can't attach uStreamer: ", error); + __setInfo(false, false, error); + __destroyJanus(); + }, + + "iceState": function(state) { + __logInfo("ICE state changed to", state); + // Если раскомментировать, то он начнет дрючить соединение, + // так как каллбек вызывает сильно после завершения работы + /*if (state === "disconnected") { + __destroyJanus(); + }*/ + }, + + "webrtcState": function(up) { + __logInfo("Janus says our WebRTC PeerConnection is", (up ? "up" : "down"), "now"); + if (up) { + __sendKeyRequired(); + } + }, + + "onmessage": function(msg, jsep) { + __stopRetryEmsgInterval(); + + if (msg.result) { + __logInfo("Got uStreamer result message:", msg.result.status); // starting, started, stopped + if (msg.result.status === "started") { + __setActive(); + __setInfo(false, false, ""); + } else if (msg.result.status === "stopped") { + __setInactive(); + __setInfo(false, false, ""); + } else if (msg.result.status === "features") { + __setAudioEnabled(msg.result.features.audio); + } + } else if (msg.error_code || msg.error) { + __logError("Got uStreamer error message:", msg.error_code, "-", msg.error); + __setInfo(false, false, msg.error); + if (__retry_emsg_timeout === null) { + __retry_emsg_timeout = setTimeout(function() { + if (!__stop) { + __sendStop(); + __sendWatch(); + } + __retry_emsg_timeout = null; + }, 2000); + } + return; + } else { + __logInfo("Got uStreamer other message:", msg); + } + + if (jsep) { + __logInfo("Handling SDP:", jsep); + __handle.createAnswer({ + "jsep": jsep, + "media": {"audioSend": false, "videoSend": false, "data": false}, + + "success": function(jsep) { + __logInfo("Got SDP:", jsep); + __sendStart(jsep); + }, + + "error": function(error) { + __logInfo("Error on SDP handling:", error); + __setInfo(false, false, error); + //__destroyJanus(); + }, + }); + } + }, + + "onremotestream": function(stream) { + __logInfo("Got a remote stream:", stream); + _Janus.attachMediaStream($("stream-video"), stream); + __sendKeyRequired(); + __startInfoInterval(); + // FIXME: Задержка уменьшается, но начинаются заикания на кейфреймах. + // - https://github.com/Glimesh/janus-ftl-plugin/issues/101 + /*if (__handle && __handle.webrtcStuff && __handle.webrtcStuff.pc) { + for (let receiver of __handle.webrtcStuff.pc.getReceivers()) { + if (receiver.track && receiver.track.kind === "video" && receiver.playoutDelayHint !== undefined) { + receiver.playoutDelayHint = 0; + } + } + }*/ + }, + + "oncleanup": function() { + __logInfo("Got a cleanup notification"); + __stopInfoInterval(); + }, + }); + }; + + var __setAudioEnabled = function(enabled) { + tools.feature.setEnabled($("stream-audio"), enabled); + }; + + var __startInfoInterval = function() { + __stopInfoInterval(); + __setActive(); + __updateInfo(); + __info_interval = setInterval(__updateInfo, 1000); + }; + + var __stopInfoInterval = function() { + if (__info_interval !== null) { + clearInterval(__info_interval); + } + __info_interval = null; + }; + + var __stopRetryEmsgInterval = function() { + if (__retry_emsg_timeout !== null) { + clearTimeout(__retry_emsg_timeout); + __retry_emsg_timeout = null; + } + }; + + var __updateInfo = function() { + if (__handle !== null) { + let online = !!(__state && __state.source && __state.source.online); + let info = ""; + if (__handle !== null) { + // https://wiki.whatwg.org/wiki/Video_Metrics + let frames = null; + let el = $("stream-video"); + if (el.webkitDecodedFrameCount !== undefined) { + frames = el.webkitDecodedFrameCount; + } else if (el.mozPaintedFrames !== undefined) { + frames = el.mozPaintedFrames; + } + if (frames !== null) { + info = `${Math.max(0, frames - __frames)} fps dynamic`; + __frames = frames; + } else { + info = `${__handle.getBitrate()}`.replace("kbits/sec", "kbps"); + } + } + __setInfo(true, online, info); + } + }; + + var __sendWatch = function() { + if (__handle) { + __logInfo("Sending WATCH + FEATURES ..."); + __handle.send({"message": {"request": "features"}}); + __handle.send({"message": {"request": "watch", "params": {"audio": true}}}); + } + }; + + var __sendStart = function(jsep) { + if (__handle) { + __logInfo("Sending START ..."); + __handle.send({"message": {"request": "start"}, "jsep": jsep}); + } + }; + + var __sendKeyRequired = function() { + /*if (__handle) { + // На этом шаге мы говорим что стрим пошел и надо запросить кейфрейм + __logInfo("Sending KEY_REQUIRED ..."); + __handle.send({message: {request: "key_required"}}); + }*/ + }; + + var __sendStop = function() { + __stopInfoInterval(); + if (__handle) { + __logInfo("Sending STOP ..."); + __handle.send({"message": {"request": "stop"}}); + __handle.hangup(); + } + }; + + var __logInfo = (...args) => tools.info("Stream [Janus]:", ...args); + var __logError = (...args) => tools.error("Stream [Janus]:", ...args); +} + +JanusStreamer.ensure_janus = function(callback) { + if (_Janus === null) { + import("./janus.js").then((module) => { + module.Janus.init({ + "debug": "all", + "callback": function() { + _Janus = module.Janus; + callback(); + }, + }); + }).catch((err) => { + tools.error("Stream: Can't import Janus module:", err); + callback(); + }); + } else { + callback(); + } +}; + +JanusStreamer.is_imported = function() { + return (_Janus !== null); +}; + +JanusStreamer.is_webrtc_available = function() { + return !!window.RTCPeerConnection; +}; + +JanusStreamer.is_h264_available = function() { + let ok = true; + if ($("stream-video").canPlayType) { + ok = $("stream-video").canPlayType("video/mp4; codecs=\"avc1.42E01F\""); + } + return ok; +}; diff --git a/web/share/js/kvm/stream_mjpeg.js b/web/share/js/kvm/stream_mjpeg.js new file mode 100644 index 00000000..9ecbe114 --- /dev/null +++ b/web/share/js/kvm/stream_mjpeg.js @@ -0,0 +1,158 @@ +/***************************************************************************** +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2022 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +*****************************************************************************/ + + +"use strict"; + + +import {tools, $} from "../tools.js"; + + +export function MjpegStreamer(__setActive, __setInactive, __setInfo) { + var self = this; + + /************************************************************************/ + + var __key = tools.makeId(); + var __id = ""; + var __fps = -1; + var __state = null; + + var __timer = null; + var __timer_retries = 0; + + /************************************************************************/ + + self.getName = () => "MJPEG"; + self.getMode = () => "mjpeg"; + + self.getResolution = function() { + let el = $("stream-image"); + return { + "real_width": el.naturalWidth, + "real_height": el.naturalHeight, + "view_width": el.offsetWidth, + "view_height": el.offsetHeight, + }; + }; + + self.ensureStream = function(state) { + if (state) { + __state = state; + __findId(); + if (__id.length > 0 && __id in __state.stream.clients_stat) { + __setStreamActive(); + __stopChecking(); + } else { + __ensureChecking(); + } + } else { + __stopChecking(); + __setStreamInactive(); + } + }; + + self.stopStream = function() { + self.ensureStream(null); + let blank = "/share/png/blank-stream.png"; + if (!String.prototype.endsWith.call($("stream-image").src, blank)) { + $("stream-image").src = blank; + } + }; + + var __setStreamActive = function() { + let old_fps = __fps; + __fps = __state.stream.clients_stat[__id].fps; + if (old_fps < 0) { + __logInfo("Active"); + __setActive(); + } + __setInfo(true, __state.source.online, `${__fps} fps dynamic`); + }; + + var __setStreamInactive = function() { + let old_fps = __fps; + __key = tools.makeId(); + __id = ""; + __fps = -1; + __state = null; + if (old_fps >= 0) { + __logInfo("Inactive"); + __setInactive(); + __setInfo(false, false, ""); + } + }; + + var __ensureChecking = function() { + if (!__timer) { + __timer_retries = 10; + __timer = setInterval(__checkStream, 100); + } + }; + + var __stopChecking = function() { + if (__timer) { + clearInterval(__timer); + } + __timer = null; + __timer_retries = 0; + }; + + var __findId = function() { + let stream_client = tools.cookies.get("stream_client"); + if (__id.length === 0 && stream_client && stream_client.startsWith(__key + "/")) { + __logInfo("Found acceptable stream_client cookie:", stream_client); + __id = stream_client.slice(stream_client.indexOf("/") + 1); + } + }; + + var __checkStream = function() { + __findId(); + + if (__id.legnth > 0 && __id in __state.stream.clients_stat) { + __setStreamActive(); + __stopChecking(); + + } else if (__id.length > 0 && __timer_retries >= 0) { + __timer_retries -= 1; + + } else { + __setStreamInactive(); + __stopChecking(); + + let path = `/streamer/stream?key=${__key}`; + if (tools.browser.is_safari || tools.browser.is_ios) { + // uStreamer fix for WebKit + __logInfo("Using dual_final_frames=1 to fix WebKit bugs"); + path += "&dual_final_frames=1"; + } else if (tools.browser.is_chrome || tools.browser.is_blink) { + // uStreamer fix for Blink https://bugs.chromium.org/p/chromium/issues/detail?id=527446 + __logInfo("Using advance_headers=1 to fix Blink bugs"); + path += "&advance_headers=1"; + } + + __logInfo("Refreshing ..."); + $("stream-image").src = path; + } + }; + + var __logInfo = (...args) => tools.info("Stream [MJPEG]:", ...args); +} |