From 6a339238ffa5907ef99b537918b1861c27740265 Mon Sep 17 00:00:00 2001 From: Maxim Devaev Date: Tue, 29 Nov 2022 09:25:09 +0300 Subject: web: split stream.js --- web/share/js/kvm/stream.js | 472 ++------------------------------------------- 1 file changed, 14 insertions(+), 458 deletions(-) (limited to 'web/share/js/kvm/stream.js') 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); -- cgit v1.2.3