mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2025-12-12 01:00:29 +08:00
partial streamer events
This commit is contained in:
parent
0e4a70e7b9
commit
a26aee3543
@ -151,6 +151,7 @@ class _Subsystem:
|
|||||||
class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-instance-attributes
|
class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-instance-attributes
|
||||||
__EV_GPIO_STATE = "gpio_state"
|
__EV_GPIO_STATE = "gpio_state"
|
||||||
__EV_INFO_STATE = "info_state"
|
__EV_INFO_STATE = "info_state"
|
||||||
|
__EV_STREAMER_STATE = "streamer_state"
|
||||||
|
|
||||||
def __init__( # pylint: disable=too-many-arguments,too-many-locals
|
def __init__( # pylint: disable=too-many-arguments,too-many-locals
|
||||||
self,
|
self,
|
||||||
@ -362,13 +363,16 @@ class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-ins
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def __poll_state(self, event_type: str, poller: AsyncGenerator[dict, None]) -> None:
|
async def __poll_state(self, event_type: str, poller: AsyncGenerator[dict, None]) -> None:
|
||||||
if event_type == self.__EV_GPIO_STATE:
|
match event_type:
|
||||||
await self.__poll_gpio_state(poller)
|
case self.__EV_GPIO_STATE:
|
||||||
elif event_type == self.__EV_INFO_STATE:
|
await self.__poll_gpio_state(poller)
|
||||||
await self.__poll_info_state(poller)
|
case self.__EV_INFO_STATE:
|
||||||
else:
|
await self.__poll_info_state(poller)
|
||||||
async for state in poller:
|
case self.__EV_STREAMER_STATE:
|
||||||
await self._broadcast_ws_event(event_type, state)
|
await self.__poll_streamer_state(poller)
|
||||||
|
case _:
|
||||||
|
async for state in poller:
|
||||||
|
await self._broadcast_ws_event(event_type, state)
|
||||||
|
|
||||||
async def __poll_gpio_state(self, poller: AsyncGenerator[dict, None]) -> None:
|
async def __poll_gpio_state(self, poller: AsyncGenerator[dict, None]) -> None:
|
||||||
prev: dict = {"state": {"inputs": {}, "outputs": {}}}
|
prev: dict = {"state": {"inputs": {}, "outputs": {}}}
|
||||||
@ -387,3 +391,11 @@ class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-ins
|
|||||||
await self._broadcast_ws_event(self.__EV_INFO_STATE, state, legacy=False)
|
await self._broadcast_ws_event(self.__EV_INFO_STATE, state, legacy=False)
|
||||||
for (key, value) in state.items():
|
for (key, value) in state.items():
|
||||||
await self._broadcast_ws_event(f"info_{key}_state", value, legacy=True)
|
await self._broadcast_ws_event(f"info_{key}_state", value, legacy=True)
|
||||||
|
|
||||||
|
async def __poll_streamer_state(self, poller: AsyncGenerator[dict, None]) -> None:
|
||||||
|
prev: dict = {}
|
||||||
|
async for state in poller:
|
||||||
|
await self._broadcast_ws_event(self.__EV_STREAMER_STATE, state, legacy=False)
|
||||||
|
prev.update(state)
|
||||||
|
if "features" in prev: # Complete/Full
|
||||||
|
await self._broadcast_ws_event(self.__EV_STREAMER_STATE, prev, legacy=True)
|
||||||
|
|||||||
@ -137,6 +137,11 @@ class _StreamerParams:
|
|||||||
|
|
||||||
|
|
||||||
class Streamer: # pylint: disable=too-many-instance-attributes
|
class Streamer: # pylint: disable=too-many-instance-attributes
|
||||||
|
__ST_FULL = 0xFF
|
||||||
|
__ST_PARAMS = 0x01
|
||||||
|
__ST_STREAMER = 0x02
|
||||||
|
__ST_SNAPSHOT = 0x04
|
||||||
|
|
||||||
def __init__( # pylint: disable=too-many-arguments,too-many-locals
|
def __init__( # pylint: disable=too-many-arguments,too-many-locals
|
||||||
self,
|
self,
|
||||||
|
|
||||||
@ -261,6 +266,7 @@ class Streamer: # pylint: disable=too-many-instance-attributes
|
|||||||
|
|
||||||
def set_params(self, params: dict) -> None:
|
def set_params(self, params: dict) -> None:
|
||||||
assert not self.__streamer_task
|
assert not self.__streamer_task
|
||||||
|
self.__notifier.notify(self.__ST_PARAMS)
|
||||||
return self.__params.set_params(params)
|
return self.__params.set_params(params)
|
||||||
|
|
||||||
def get_params(self) -> dict:
|
def get_params(self) -> dict:
|
||||||
@ -269,49 +275,72 @@ class Streamer: # pylint: disable=too-many-instance-attributes
|
|||||||
# =====
|
# =====
|
||||||
|
|
||||||
async def get_state(self) -> dict:
|
async def get_state(self) -> dict:
|
||||||
streamer_state = None
|
|
||||||
if self.__streamer_task:
|
|
||||||
session = self.__ensure_client_session()
|
|
||||||
try:
|
|
||||||
streamer_state = await session.get_state()
|
|
||||||
except (aiohttp.ClientConnectionError, aiohttp.ServerConnectionError):
|
|
||||||
pass
|
|
||||||
except Exception:
|
|
||||||
get_logger().exception("Invalid streamer response from /state")
|
|
||||||
|
|
||||||
snapshot: (dict | None) = None
|
|
||||||
if self.__snapshot:
|
|
||||||
snapshot = dataclasses.asdict(self.__snapshot)
|
|
||||||
del snapshot["headers"]
|
|
||||||
del snapshot["data"]
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
"features": self.__params.get_features(),
|
||||||
"limits": self.__params.get_limits(),
|
"limits": self.__params.get_limits(),
|
||||||
"params": self.__params.get_params(),
|
"params": self.__params.get_params(),
|
||||||
"snapshot": {"saved": snapshot},
|
"streamer": (await self.__get_streamer_state()),
|
||||||
"streamer": streamer_state,
|
"snapshot": self.__get_snapshot_state(),
|
||||||
"features": self.__params.get_features(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async def trigger_state(self) -> None:
|
async def trigger_state(self) -> None:
|
||||||
self.__notifier.notify(1)
|
self.__notifier.notify(self.__ST_FULL)
|
||||||
|
|
||||||
async def poll_state(self) -> AsyncGenerator[dict, None]:
|
async def poll_state(self) -> AsyncGenerator[dict, None]:
|
||||||
def signal_handler(*_: Any) -> None:
|
def signal_handler(*_: Any) -> None:
|
||||||
get_logger(0).info("Got SIGUSR2, checking the stream state ...")
|
get_logger(0).info("Got SIGUSR2, checking the stream state ...")
|
||||||
self.__notifier.notify()
|
self.__notifier.notify(self.__ST_STREAMER)
|
||||||
|
|
||||||
get_logger(0).info("Installing SIGUSR2 streamer handler ...")
|
get_logger(0).info("Installing SIGUSR2 streamer handler ...")
|
||||||
asyncio.get_event_loop().add_signal_handler(signal.SIGUSR2, signal_handler)
|
asyncio.get_event_loop().add_signal_handler(signal.SIGUSR2, signal_handler)
|
||||||
|
|
||||||
prev: dict = {}
|
prev: dict = {}
|
||||||
while True:
|
while True:
|
||||||
if (await self.__notifier.wait(timeout=self.__state_poll)) > 0:
|
new: dict = {}
|
||||||
prev = {}
|
|
||||||
new = await self.get_state()
|
mask = await self.__notifier.wait(timeout=self.__state_poll)
|
||||||
if new != prev:
|
if mask == self.__ST_FULL:
|
||||||
|
new = await self.get_state()
|
||||||
prev = copy.deepcopy(new)
|
prev = copy.deepcopy(new)
|
||||||
yield new
|
yield new
|
||||||
|
continue
|
||||||
|
|
||||||
|
if mask < 0:
|
||||||
|
mask = self.__ST_STREAMER
|
||||||
|
|
||||||
|
def check_update(key: str, value: (dict | None)) -> None:
|
||||||
|
if prev.get(key) != value:
|
||||||
|
new[key] = value
|
||||||
|
|
||||||
|
if mask & self.__ST_PARAMS:
|
||||||
|
check_update("params", self.__params.get_params())
|
||||||
|
if mask & self.__ST_STREAMER:
|
||||||
|
check_update("streamer", await self.__get_streamer_state())
|
||||||
|
if mask & self.__ST_SNAPSHOT:
|
||||||
|
check_update("snapshot", self.__get_snapshot_state())
|
||||||
|
|
||||||
|
if new and prev != new:
|
||||||
|
prev.update(copy.deepcopy(new))
|
||||||
|
yield new
|
||||||
|
|
||||||
|
async def __get_streamer_state(self) -> (dict | None):
|
||||||
|
if self.__streamer_task:
|
||||||
|
session = self.__ensure_client_session()
|
||||||
|
try:
|
||||||
|
return (await session.get_state())
|
||||||
|
except (aiohttp.ClientConnectionError, aiohttp.ServerConnectionError):
|
||||||
|
pass
|
||||||
|
except Exception:
|
||||||
|
get_logger().exception("Invalid streamer response from /state")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def __get_snapshot_state(self) -> dict:
|
||||||
|
if self.__snapshot:
|
||||||
|
snapshot = dataclasses.asdict(self.__snapshot)
|
||||||
|
del snapshot["headers"]
|
||||||
|
del snapshot["data"]
|
||||||
|
return {"saved": snapshot}
|
||||||
|
return {"saved": None}
|
||||||
|
|
||||||
# =====
|
# =====
|
||||||
|
|
||||||
@ -325,7 +354,7 @@ class Streamer: # pylint: disable=too-many-instance-attributes
|
|||||||
if snapshot.online or allow_offline:
|
if snapshot.online or allow_offline:
|
||||||
if save:
|
if save:
|
||||||
self.__snapshot = snapshot
|
self.__snapshot = snapshot
|
||||||
self.__notifier.notify()
|
self.__notifier.notify(self.__ST_SNAPSHOT)
|
||||||
return snapshot
|
return snapshot
|
||||||
logger.error("Stream is offline, no signal or so")
|
logger.error("Stream is offline, no signal or so")
|
||||||
except (aiohttp.ClientConnectionError, aiohttp.ServerConnectionError) as ex:
|
except (aiohttp.ClientConnectionError, aiohttp.ServerConnectionError) as ex:
|
||||||
|
|||||||
@ -57,7 +57,7 @@ export function Session() {
|
|||||||
var __info_fan_state = null;
|
var __info_fan_state = null;
|
||||||
|
|
||||||
var __init__ = function() {
|
var __init__ = function() {
|
||||||
__startSession();
|
__streamer.ensureDeps(() => __startSession());
|
||||||
};
|
};
|
||||||
|
|
||||||
/************************************************************************/
|
/************************************************************************/
|
||||||
@ -281,11 +281,6 @@ export function Session() {
|
|||||||
tools.feature.setEnabled($("system-tool-webterm"), has_webterm);
|
tools.feature.setEnabled($("system-tool-webterm"), has_webterm);
|
||||||
$("webterm-window").show_hook = show_hook;
|
$("webterm-window").show_hook = show_hook;
|
||||||
$("webterm-window").close_hook = close_hook;
|
$("webterm-window").close_hook = close_hook;
|
||||||
|
|
||||||
__streamer.setJanusEnabled(
|
|
||||||
(state.janus && (state.janus.enabled || state.janus.started))
|
|
||||||
|| (state.janus_static && (state.janus_static.enabled || state.janus_static.started))
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var __startSession = function() {
|
var __startSession = function() {
|
||||||
|
|||||||
@ -35,11 +35,11 @@ export function Streamer() {
|
|||||||
|
|
||||||
/************************************************************************/
|
/************************************************************************/
|
||||||
|
|
||||||
var __janus_enabled = null;
|
var __janus_imported = null;
|
||||||
var __streamer = null;
|
var __streamer = null;
|
||||||
|
|
||||||
var __state = null;
|
var __state = null;
|
||||||
var __resolution = {"width": 640, "height": 480};
|
var __res = {"width": 640, "height": 480};
|
||||||
|
|
||||||
var __init__ = function() {
|
var __init__ = function() {
|
||||||
__streamer = new MjpegStreamer(__setActive, __setInactive, __setInfo);
|
__streamer = new MjpegStreamer(__setActive, __setInactive, __setInfo);
|
||||||
@ -47,22 +47,22 @@ export function Streamer() {
|
|||||||
$("stream-led").title = "Stream inactive";
|
$("stream-led").title = "Stream inactive";
|
||||||
|
|
||||||
tools.slider.setParams($("stream-quality-slider"), 5, 100, 5, 80, function(value) {
|
tools.slider.setParams($("stream-quality-slider"), 5, 100, 5, 80, function(value) {
|
||||||
$("stream-quality-value").innerHTML = `${value}%`;
|
$("stream-quality-value").innerText = `${value}%`;
|
||||||
});
|
});
|
||||||
tools.slider.setOnUpDelayed($("stream-quality-slider"), 1000, (value) => __sendParam("quality", value));
|
tools.slider.setOnUpDelayed($("stream-quality-slider"), 1000, (value) => __sendParam("quality", value));
|
||||||
|
|
||||||
tools.slider.setParams($("stream-h264-bitrate-slider"), 25, 20000, 25, 5000, function(value) {
|
tools.slider.setParams($("stream-h264-bitrate-slider"), 25, 20000, 25, 5000, function(value) {
|
||||||
$("stream-h264-bitrate-value").innerHTML = value;
|
$("stream-h264-bitrate-value").innerText = value;
|
||||||
});
|
});
|
||||||
tools.slider.setOnUpDelayed($("stream-h264-bitrate-slider"), 1000, (value) => __sendParam("h264_bitrate", value));
|
tools.slider.setOnUpDelayed($("stream-h264-bitrate-slider"), 1000, (value) => __sendParam("h264_bitrate", value));
|
||||||
|
|
||||||
tools.slider.setParams($("stream-h264-gop-slider"), 0, 60, 1, 30, function(value) {
|
tools.slider.setParams($("stream-h264-gop-slider"), 0, 60, 1, 30, function(value) {
|
||||||
$("stream-h264-gop-value").innerHTML = value;
|
$("stream-h264-gop-value").innerText = value;
|
||||||
});
|
});
|
||||||
tools.slider.setOnUpDelayed($("stream-h264-gop-slider"), 1000, (value) => __sendParam("h264_gop", value));
|
tools.slider.setOnUpDelayed($("stream-h264-gop-slider"), 1000, (value) => __sendParam("h264_gop", value));
|
||||||
|
|
||||||
tools.slider.setParams($("stream-desired-fps-slider"), 0, 120, 1, 0, function(value) {
|
tools.slider.setParams($("stream-desired-fps-slider"), 0, 120, 1, 0, function(value) {
|
||||||
$("stream-desired-fps-value").innerHTML = (value === 0 ? "Unlimited" : value);
|
$("stream-desired-fps-value").innerText = (value === 0 ? "Unlimited" : value);
|
||||||
});
|
});
|
||||||
tools.slider.setOnUpDelayed($("stream-desired-fps-slider"), 1000, (value) => __sendParam("desired_fps", value));
|
tools.slider.setOnUpDelayed($("stream-desired-fps-slider"), 1000, (value) => __sendParam("desired_fps", value));
|
||||||
|
|
||||||
@ -86,7 +86,7 @@ export function Streamer() {
|
|||||||
tools.slider.setParams($("stream-audio-volume-slider"), 0, 100, 1, 0, function(value) {
|
tools.slider.setParams($("stream-audio-volume-slider"), 0, 100, 1, 0, function(value) {
|
||||||
$("stream-video").muted = !value;
|
$("stream-video").muted = !value;
|
||||||
$("stream-video").volume = value / 100;
|
$("stream-video").volume = value / 100;
|
||||||
$("stream-audio-volume-value").innerHTML = value + "%";
|
$("stream-audio-volume-value").innerText = value + "%";
|
||||||
if (__streamer.getMode() === "janus") {
|
if (__streamer.getMode() === "janus") {
|
||||||
let allow_audio = !$("stream-video").muted;
|
let allow_audio = !$("stream-video").muted;
|
||||||
if (__streamer.isAudioAllowed() !== allow_audio) {
|
if (__streamer.isAudioAllowed() !== allow_audio) {
|
||||||
@ -104,6 +104,13 @@ export function Streamer() {
|
|||||||
|
|
||||||
/************************************************************************/
|
/************************************************************************/
|
||||||
|
|
||||||
|
self.ensureDeps = function(callback) {
|
||||||
|
JanusStreamer.ensure_janus(function(avail) {
|
||||||
|
__janus_imported = avail;
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
self.getGeometry = function() {
|
self.getGeometry = function() {
|
||||||
// Первоначально обновление геометрии считалось через ResizeObserver.
|
// Первоначально обновление геометрии считалось через ResizeObserver.
|
||||||
// Но оно не ловило некоторые события, например в последовательности:
|
// Но оно не ловило некоторые события, например в последовательности:
|
||||||
@ -126,90 +133,106 @@ export function Streamer() {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
self.setJanusEnabled = function(enabled) {
|
|
||||||
let has_webrtc = JanusStreamer.is_webrtc_available();
|
|
||||||
let has_h264 = JanusStreamer.is_h264_available();
|
|
||||||
|
|
||||||
let set_enabled = function(imported) {
|
|
||||||
tools.hidden.setVisible($("stream-message-no-webrtc"), enabled && !has_webrtc);
|
|
||||||
tools.hidden.setVisible($("stream-message-no-h264"), enabled && !has_h264);
|
|
||||||
__janus_enabled = (enabled && has_webrtc && 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=${imported}`
|
|
||||||
);
|
|
||||||
let mode = (__janus_enabled ? tools.storage.get("stream.mode", "janus") : "mjpeg");
|
|
||||||
tools.radio.clickValue("stream-mode-radio", mode);
|
|
||||||
if (!__janus_enabled) {
|
|
||||||
tools.feature.setEnabled($("stream-audio"), false); // Enabling in stream_janus.js
|
|
||||||
}
|
|
||||||
self.setState(__state);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (enabled && has_webrtc) {
|
|
||||||
JanusStreamer.ensure_janus(set_enabled);
|
|
||||||
} else {
|
|
||||||
set_enabled(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.setState = function(state) {
|
self.setState = function(state) {
|
||||||
__state = state;
|
if (state) {
|
||||||
if (__janus_enabled !== null) {
|
if (!__state) {
|
||||||
__applyState(wm.isWindowVisible($("stream-window")) ? __state : null);
|
__state = {};
|
||||||
|
}
|
||||||
|
if (state.features) {
|
||||||
|
__state.features = state.features;
|
||||||
|
__state.limits = state.limits; // Following together with features
|
||||||
|
}
|
||||||
|
if (__state.features && state.streamer !== undefined) {
|
||||||
|
__state.streamer = state.streamer;
|
||||||
|
}
|
||||||
|
__setControlsEnabled(!!state.streamer);
|
||||||
|
} else {
|
||||||
|
__state = null;
|
||||||
}
|
}
|
||||||
|
let visible = wm.isWindowVisible($("stream-window"));
|
||||||
|
__applyState((visible && __state && __state.features) ? state : null);
|
||||||
};
|
};
|
||||||
|
|
||||||
var __applyState = function(state) {
|
var __applyState = function(state) {
|
||||||
if (state) {
|
if (__janus_imported === null) {
|
||||||
tools.feature.setEnabled($("stream-quality"), state.features.quality && (state.streamer === null || state.streamer.encoder.quality > 0));
|
alert("__janus_imported is null, please report");
|
||||||
tools.feature.setEnabled($("stream-h264-bitrate"), state.features.h264 && __janus_enabled);
|
return;
|
||||||
tools.feature.setEnabled($("stream-h264-gop"), state.features.h264 && __janus_enabled);
|
}
|
||||||
tools.feature.setEnabled($("stream-resolution"), state.features.resolution);
|
|
||||||
|
|
||||||
if (state.streamer) {
|
if (!state) {
|
||||||
tools.el.setEnabled($("stream-quality-slider"), true);
|
__streamer.stopStream();
|
||||||
tools.slider.setValue($("stream-quality-slider"), state.streamer.encoder.quality);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (state.features.h264 && __janus_enabled) {
|
if (state.features) {
|
||||||
__setLimitsAndValue($("stream-h264-bitrate-slider"), state.limits.h264_bitrate, state.streamer.h264.bitrate);
|
let f = state.features;
|
||||||
tools.el.setEnabled($("stream-h264-bitrate-slider"), true);
|
let l = state.limits;
|
||||||
|
let has_webrtc = JanusStreamer.is_webrtc_available();
|
||||||
|
let has_h264 = JanusStreamer.is_h264_available();
|
||||||
|
let has_janus = (__janus_imported && f.h264 && has_webrtc); // Don't check has_h264 for sure
|
||||||
|
|
||||||
__setLimitsAndValue($("stream-h264-gop-slider"), state.limits.h264_gop, state.streamer.h264.gop);
|
tools.info(
|
||||||
tools.el.setEnabled($("stream-h264-gop-slider"), true);
|
`Stream: Janus WebRTC state: features.h264=${f.h264},`
|
||||||
|
+ ` webrtc=${has_webrtc}, h264=${has_h264}, janus_imported=${__janus_imported}`
|
||||||
|
);
|
||||||
|
|
||||||
|
tools.hidden.setVisible($("stream-message-no-webrtc"), __janus_imported && f.h264 && !has_webrtc);
|
||||||
|
tools.hidden.setVisible($("stream-message-no-h264"), __janus_imported && f.h264 && !has_h264);
|
||||||
|
|
||||||
|
tools.slider.setRange($("stream-desired-fps-slider"), l.desired_fps.min, l.desired_fps.max);
|
||||||
|
if (f.resolution) {
|
||||||
|
let el = $("stream-resolution-selector");
|
||||||
|
el.options.length = 0;
|
||||||
|
for (let res of l.available_resolutions) {
|
||||||
|
tools.selector.addOption(el, res, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
__setLimitsAndValue($("stream-desired-fps-slider"), state.limits.desired_fps, state.streamer.source.desired_fps);
|
|
||||||
tools.el.setEnabled($("stream-desired-fps-slider"), true);
|
|
||||||
|
|
||||||
let resolution_str = __makeStringResolution(state.streamer.source.resolution);
|
|
||||||
if (__makeStringResolution(__resolution) !== resolution_str) {
|
|
||||||
__resolution = state.streamer.source.resolution;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.features.resolution) {
|
|
||||||
let el = $("stream-resolution-selector");
|
|
||||||
if (!state.limits.available_resolutions.includes(resolution_str)) {
|
|
||||||
state.limits.available_resolutions.push(resolution_str);
|
|
||||||
}
|
|
||||||
tools.selector.setValues(el, state.limits.available_resolutions);
|
|
||||||
tools.selector.setSelectedValue(el, resolution_str);
|
|
||||||
tools.el.setEnabled(el, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
tools.el.setEnabled($("stream-quality-slider"), false);
|
$("stream-resolution-selector").options.length = 0;
|
||||||
tools.el.setEnabled($("stream-h264-bitrate-slider"), false);
|
}
|
||||||
tools.el.setEnabled($("stream-h264-gop-slider"), false);
|
if (has_janus) {
|
||||||
tools.el.setEnabled($("stream-desired-fps-slider"), false);
|
tools.slider.setRange($("stream-h264-bitrate-slider"), l.h264_bitrate.min, l.h264_bitrate.max);
|
||||||
tools.el.setEnabled($("stream-resolution-selector"), false);
|
tools.slider.setRange($("stream-h264-gop-slider"), l.h264_gop.min, l.h264_gop.max);
|
||||||
}
|
}
|
||||||
|
|
||||||
__streamer.ensureStream(state.streamer);
|
// tools.feature.setEnabled($("stream-quality"), f.quality); // Only on s.encoder.quality
|
||||||
|
tools.feature.setEnabled($("stream-resolution"), f.resolution);
|
||||||
|
tools.feature.setEnabled($("stream-h264-bitrate"), has_janus);
|
||||||
|
tools.feature.setEnabled($("stream-h264-gop"), has_janus);
|
||||||
|
tools.feature.setEnabled($("stream-mode"), has_janus);
|
||||||
|
if (!has_janus) {
|
||||||
|
tools.feature.setEnabled($("stream-audio"), false);
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
let mode = (has_janus ? tools.storage.get("stream.mode", "janus") : "mjpeg");
|
||||||
__streamer.stopStream();
|
tools.radio.clickValue("stream-mode-radio", mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.streamer !== undefined) {
|
||||||
|
let ok = (state.streamer !== null);
|
||||||
|
if (ok) {
|
||||||
|
let s = state.streamer;
|
||||||
|
__res = s.source.resolution;
|
||||||
|
|
||||||
|
{
|
||||||
|
let res = `${__res.width}x${__res.height}`;
|
||||||
|
let el = $("stream-resolution-selector");
|
||||||
|
if (!tools.selector.hasValue(el, res)) {
|
||||||
|
tools.selector.addOption(el, res, res);
|
||||||
|
}
|
||||||
|
el.value = res;
|
||||||
|
}
|
||||||
|
tools.slider.setValue($("stream-quality-slider"), Math.max(s.encoder.quality, 1));
|
||||||
|
tools.slider.setValue($("stream-desired-fps-slider"), s.source.desired_fps);
|
||||||
|
if (s.h264 && s.h264.bitrate) {
|
||||||
|
tools.slider.setValue($("stream-h264-bitrate-slider"), s.h264.bitrate);
|
||||||
|
tools.slider.setValue($("stream-h264-gop-slider"), s.h264.gop); // Following together with gop
|
||||||
|
}
|
||||||
|
|
||||||
|
tools.feature.setEnabled($("stream-quality"), (s.encoder.quality > 0));
|
||||||
|
|
||||||
|
__streamer.ensureStream(s);
|
||||||
|
}
|
||||||
|
__setControlsEnabled(ok);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -223,16 +246,24 @@ export function Streamer() {
|
|||||||
$("stream-led").title = "Stream inactive";
|
$("stream-led").title = "Stream inactive";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var __setControlsEnabled = function(enabled) {
|
||||||
|
tools.el.setEnabled($("stream-quality-slider"), enabled);
|
||||||
|
tools.el.setEnabled($("stream-desired-fps-slider"), enabled);
|
||||||
|
tools.el.setEnabled($("stream-resolution-selector"), enabled);
|
||||||
|
tools.el.setEnabled($("stream-h264-bitrate-slider"), enabled);
|
||||||
|
tools.el.setEnabled($("stream-h264-gop-slider"), enabled);
|
||||||
|
};
|
||||||
|
|
||||||
var __setInfo = function(is_active, online, text) {
|
var __setInfo = function(is_active, online, text) {
|
||||||
$("stream-box").classList.toggle("stream-box-offline", !online);
|
$("stream-box").classList.toggle("stream-box-offline", !online);
|
||||||
let el_grab = document.querySelector("#stream-window-header .window-grab");
|
let el_grab = document.querySelector("#stream-window-header .window-grab");
|
||||||
let el_info = $("stream-info");
|
let el_info = $("stream-info");
|
||||||
let title = `${__streamer.getName()} – `;
|
let title = `${__streamer.getName()} - `;
|
||||||
if (is_active) {
|
if (is_active) {
|
||||||
if (!online) {
|
if (!online) {
|
||||||
title += "No signal / ";
|
title += "No signal / ";
|
||||||
}
|
}
|
||||||
title += __makeStringResolution(__resolution);
|
title += `${__res.width}x${__res.height}`;
|
||||||
if (text.length > 0) {
|
if (text.length > 0) {
|
||||||
title += " / " + text;
|
title += " / " + text;
|
||||||
}
|
}
|
||||||
@ -243,12 +274,7 @@ export function Streamer() {
|
|||||||
title += "Inactive";
|
title += "Inactive";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
el_grab.innerHTML = el_info.innerHTML = title;
|
el_grab.innerText = el_info.innerText = title;
|
||||||
};
|
|
||||||
|
|
||||||
var __setLimitsAndValue = function(el, limits, value) {
|
|
||||||
tools.slider.setRange(el, limits.min, limits.max);
|
|
||||||
tools.slider.setValue(el, value);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var __resetStream = function(mode=null) {
|
var __resetStream = function(mode=null) {
|
||||||
@ -268,7 +294,7 @@ export function Streamer() {
|
|||||||
tools.feature.setEnabled($("stream-audio"), false); // Enabling in stream_janus.js
|
tools.feature.setEnabled($("stream-audio"), false); // Enabling in stream_janus.js
|
||||||
}
|
}
|
||||||
if (wm.isWindowVisible($("stream-window"))) {
|
if (wm.isWindowVisible($("stream-window"))) {
|
||||||
__streamer.ensureStream(__state ? __state.streamer : null);
|
__streamer.ensureStream((__state && __state.streamer !== undefined) ? __state.streamer : null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -305,6 +331,12 @@ export function Streamer() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var __sendParam = function(name, value) {
|
var __sendParam = function(name, value) {
|
||||||
|
tools.el.setEnabled($("stream-quality-slider"), false);
|
||||||
|
tools.el.setEnabled($("stream-desired-fps-slider"), false);
|
||||||
|
tools.el.setEnabled($("stream-resolution-selector"), false);
|
||||||
|
tools.el.setEnabled($("stream-h264-bitrate-slider"), false);
|
||||||
|
tools.el.setEnabled($("stream-h264-gop-slider"), false);
|
||||||
|
|
||||||
tools.httpPost("/api/streamer/set_params", {[name]: value}, function(http) {
|
tools.httpPost("/api/streamer/set_params", {[name]: value}, function(http) {
|
||||||
if (http.status !== 200) {
|
if (http.status !== 200) {
|
||||||
wm.error("Can't configure stream", http.responseText);
|
wm.error("Can't configure stream", http.responseText);
|
||||||
@ -312,9 +344,5 @@ export function Streamer() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
var __makeStringResolution = function(resolution) {
|
|
||||||
return `${resolution.width}x${resolution.height}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
__init__();
|
__init__();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -383,7 +383,7 @@ export function JanusStreamer(__setActive, __setInactive, __setInfo, __orient, _
|
|||||||
};
|
};
|
||||||
|
|
||||||
var __isOnline = function() {
|
var __isOnline = function() {
|
||||||
return !!(__state && __state.source && __state.source.online);
|
return !!(__state && __state.source.online);
|
||||||
};
|
};
|
||||||
|
|
||||||
var __sendWatch = function() {
|
var __sendWatch = function() {
|
||||||
|
|||||||
@ -117,10 +117,10 @@ export function MjpegStreamer(__setActive, __setInactive, __setInfo) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var __findId = function() {
|
var __findId = function() {
|
||||||
let stream_client = tools.cookies.get("stream_client");
|
let sc = tools.cookies.get("stream_client");
|
||||||
if (__id.length === 0 && stream_client && stream_client.startsWith(__key + "/")) {
|
if (__id.length === 0 && sc && sc.startsWith(__key + "/")) {
|
||||||
__logInfo("Found acceptable stream_client cookie:", stream_client);
|
__logInfo("Found acceptable stream_client cookie:", sc);
|
||||||
__id = stream_client.slice(stream_client.indexOf("/") + 1);
|
__id = sc.slice(sc.indexOf("/") + 1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -309,6 +309,14 @@ export var tools = new function() {
|
|||||||
self.selector.addComment(el, "\u2500".repeat(repeat));
|
self.selector.addComment(el, "\u2500".repeat(repeat));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"hasValue": function(el, value) {
|
||||||
|
for (let el_op of el.options) {
|
||||||
|
if (el_op.value === value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
"setValues": function(el, values, empty_title=null) {
|
"setValues": function(el, values, empty_title=null) {
|
||||||
if (values.constructor == Object) {
|
if (values.constructor == Object) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user