reactor-runtime 2.8.0__tar.gz → 2.9.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/PKG-INFO +1 -1
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/pyproject.toml +2 -2
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/runtime_api.py +9 -1
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/serve/commands/run.py +59 -5
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/config.py +2 -1
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/events.py +1 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/client.py +42 -2
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/gst_helpers.py +8 -1
- reactor_runtime-2.9.0/src/reactor_runtime/transports/gstreamer/quality.py +71 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/sender/video.py +10 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime.egg-info/PKG-INFO +1 -1
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime.egg-info/SOURCES.txt +1 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/README.md +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/setup.cfg +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/api/__init__.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/__init__.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/config.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/experiment/__init__.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/experiment/session.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/__init__.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/client.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/defaults.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/driver/__init__.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/driver/pipeline_executor.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/driver/step_result.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/events/__init__.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/events/connected.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/events/event.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/events/messages.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/events/upload.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/internal/__init__.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/internal/input_buffer.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/internal/output_buffer.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/internal/reactor_core.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/model/__init__.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/model/decorators.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/model/handlers.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/model/reactor_model.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/pipeline/__init__.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/pipeline/idle.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/pipeline/input_state.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/pipeline/reactor_pipeline.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/tracks/__init__.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/tracks/descriptors.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/tracks/input.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/tracks/output.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/upload.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/model_state.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/profiling/__init__.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/profiling/backends/__init__.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/profiling/backends/base.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/profiling/backends/file.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/profiling/backends/otlp.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/profiling/helpers.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/profiling/nvml_sampler.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/profiling/plotting/__init__.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/profiling/plotting/plot_profiling.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/profiling/profiler.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/profiling/singleton.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/profiling/torch_chunk_profiler.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/recording/__init__.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/recording/chunk_encoder.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/recording/chunk_uploader.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/recording/config.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/recording/markers.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/recording/session_recorder.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/recording/sinks.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/recording/track_resolver.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/runtimes/http/config.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/runtimes/http/http_runtime.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/runtimes/http/types.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/schema.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/schema_validator.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/serve/__init__.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/serve/__main__.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/serve/commands/__init__.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/serve/commands/schema.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/serve/main.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/serve/utils/__init__.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/serve/utils/config.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/serve/utils/runtime.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/__init__.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/__init__.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/decoders/__init__.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/decoders/av1.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/decoders/base.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/decoders/factory.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/decoders/h264.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/decoders/h265.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/decoders/opus.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/decoders/vp8.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/decoders/vp9.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/encoders/__init__.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/encoders/av1.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/encoders/base.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/encoders/factory.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/encoders/h264.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/encoders/h265.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/encoders/opus.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/encoders/vp8.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/encoders/vp9.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/gst.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/probes/__init__.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/probes/fps_probe.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/receiver/__init__.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/receiver/audio.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/receiver/base.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/receiver/video.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/sdp/__init__.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/sdp/bundle.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/sdp/codec.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/sdp/extmap.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/sdp/ice.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/sender/__init__.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/sender/audio.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/sender/base.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/settings.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/signals.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/ice_uris.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/interface.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/media.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/types.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/utils/launch.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/utils/loader.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/utils/log.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/utils/messages.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/utils/paths.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/utils/ports.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/utils/typing.py +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime.egg-info/dependency_links.txt +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime.egg-info/requires.txt +0 -0
- {reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "reactor_runtime"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.9.0"
|
|
8
8
|
description = "Reactor runtime with public model API"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name = "Reactor", email = "team@reactor.inc" }
|
|
@@ -90,4 +90,4 @@ gst = [
|
|
|
90
90
|
# Legacy releases used `X.Y.Z-g<short-sha>` (e.g. `1.4.1-g5660eed`); the wheel
|
|
91
91
|
# stripped the `-g<sha>` suffix. fetch-proto.py still understands that shape
|
|
92
92
|
# for back-compat with old pins, but new bumps should track the calendar format.
|
|
93
|
-
version = "1.
|
|
93
|
+
version = "1.20260625.16626"
|
|
@@ -379,8 +379,16 @@ class Runtime(ModelStateMachine, ABC):
|
|
|
379
379
|
self._session_moderator: Optional["SessionModerator"] = None
|
|
380
380
|
moderation_enabled = self.config.reactor_config.moderation.enabled
|
|
381
381
|
if _MODERATION_AVAILABLE:
|
|
382
|
+
# REA-2601: pass the subclass-owned SupervisorClient (if any)
|
|
383
|
+
# so the default ``supervisor`` backend can proxy moderation
|
|
384
|
+
# over the existing gRPC channel. Subclasses without a
|
|
385
|
+
# Supervisor connection (e.g. HttpRuntime) simply leave the
|
|
386
|
+
# attribute unset; ``from_reactor_config`` raises a
|
|
387
|
+
# ``ModerationConfigError`` at startup so the misconfiguration
|
|
388
|
+
# never reaches a live session.
|
|
382
389
|
self._moderator = ContentModerator.from_reactor_config(
|
|
383
|
-
self.config.reactor_config
|
|
390
|
+
self.config.reactor_config,
|
|
391
|
+
supervisor_client=getattr(self, "_supervisor_client", None),
|
|
384
392
|
)
|
|
385
393
|
if self._moderator is not None:
|
|
386
394
|
logger.info("Content moderation enabled")
|
|
@@ -23,12 +23,37 @@ from reactor_runtime.utils.paths import _set_configured_weights_path
|
|
|
23
23
|
logger = get_logger(__name__)
|
|
24
24
|
|
|
25
25
|
_MODEL_NAME_ENV_VAR = "MODEL_NAME"
|
|
26
|
-
_MODEL_NAME_MAX_LENGTH =
|
|
27
|
-
_MODEL_NAME_PATTERN = re.compile(
|
|
26
|
+
_MODEL_NAME_MAX_LENGTH = 129
|
|
27
|
+
_MODEL_NAME_PATTERN = re.compile(
|
|
28
|
+
r"^[a-zA-Z0-9][a-zA-Z0-9._-]*(/[a-zA-Z0-9][a-zA-Z0-9._-]*)?$"
|
|
29
|
+
)
|
|
28
30
|
|
|
29
31
|
_REACTOR_YAML = "reactor.yaml"
|
|
30
32
|
|
|
31
33
|
|
|
34
|
+
def _advertised_name_matches(advertised: str, canonical: str) -> bool:
|
|
35
|
+
"""Check a reactor.yaml-advertised name against the assigned MODEL_NAME.
|
|
36
|
+
|
|
37
|
+
The Coordinator assigns a fully-qualified canonical name
|
|
38
|
+
(``namespace/slug``, e.g. ``reactor/echo``). A model author is allowed
|
|
39
|
+
to advertise either that canonical name or the bare slug — the
|
|
40
|
+
namespace is derived from the authenticated account, so omitting it is
|
|
41
|
+
legitimate. Any other value (a different slug, or a namespace the
|
|
42
|
+
author does not own) is a mismatch.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
advertised: ``model.name`` from the partner-authored reactor.yaml.
|
|
46
|
+
canonical: The platform-assigned ``MODEL_NAME``.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
True if ``advertised`` resolves to ``canonical``.
|
|
50
|
+
"""
|
|
51
|
+
if advertised == canonical:
|
|
52
|
+
return True
|
|
53
|
+
_, _, slug = canonical.partition("/")
|
|
54
|
+
return bool(slug) and advertised == slug
|
|
55
|
+
|
|
56
|
+
|
|
32
57
|
class RunCommand:
|
|
33
58
|
@staticmethod
|
|
34
59
|
def register_subcommand(subparsers):
|
|
@@ -126,10 +151,27 @@ class RunCommand:
|
|
|
126
151
|
return
|
|
127
152
|
|
|
128
153
|
# ---------------------------------------------------------
|
|
129
|
-
# Model
|
|
154
|
+
# Model identity
|
|
155
|
+
#
|
|
156
|
+
# In production the Coordinator assigns the canonical model name
|
|
157
|
+
# (e.g. "reactor/echo") and the Supervisor injects it as the
|
|
158
|
+
# MODEL_NAME env var on the generated deployment. That value is the
|
|
159
|
+
# trusted identity: the runtime keys its streams, logs and metering
|
|
160
|
+
# off it, never off the partner-authored reactor.yaml.
|
|
161
|
+
#
|
|
162
|
+
# A model author may still advertise a name in reactor.yaml
|
|
163
|
+
# (model.name). When MODEL_NAME is set we treat that as a claim that
|
|
164
|
+
# must be honoured — it has to resolve to the assigned name (the
|
|
165
|
+
# canonical name, or the bare slug since the namespace is derived
|
|
166
|
+
# from the authenticated account). A mismatch means untrusted model
|
|
167
|
+
# code is trying to run under a name it was not granted, so we
|
|
168
|
+
# refuse to start.
|
|
169
|
+
#
|
|
170
|
+
# Without MODEL_NAME (local `reactor run`) there is no assigning
|
|
171
|
+
# authority, so the advertised name — then the entrypoint spec — is
|
|
172
|
+
# used as a best-effort label.
|
|
130
173
|
# ---------------------------------------------------------
|
|
131
|
-
|
|
132
|
-
reactor_config.name = reactor_config.model
|
|
174
|
+
advertised_name = reactor_config.name
|
|
133
175
|
env_name = os.environ.get(_MODEL_NAME_ENV_VAR)
|
|
134
176
|
if env_name:
|
|
135
177
|
if len(env_name) > _MODEL_NAME_MAX_LENGTH:
|
|
@@ -138,7 +180,19 @@ class RunCommand:
|
|
|
138
180
|
if not _MODEL_NAME_PATTERN.match(env_name):
|
|
139
181
|
print(f"Error: {_MODEL_NAME_ENV_VAR} has invalid chars")
|
|
140
182
|
return
|
|
183
|
+
if advertised_name and not _advertised_name_matches(
|
|
184
|
+
advertised_name, env_name
|
|
185
|
+
):
|
|
186
|
+
print(
|
|
187
|
+
f"Error: reactor.yaml advertises model name "
|
|
188
|
+
f"'{advertised_name}', which does not match the "
|
|
189
|
+
f"assigned {_MODEL_NAME_ENV_VAR} '{env_name}'. A model "
|
|
190
|
+
f"may only run under the name the platform assigned it."
|
|
191
|
+
)
|
|
192
|
+
return
|
|
141
193
|
reactor_config.name = env_name
|
|
194
|
+
elif not reactor_config.name:
|
|
195
|
+
reactor_config.name = reactor_config.model
|
|
142
196
|
|
|
143
197
|
# ---------------------------------------------------------
|
|
144
198
|
# Resolve model config path + CLI overrides
|
|
@@ -258,7 +258,8 @@ def resolve_default_transport() -> TransportType:
|
|
|
258
258
|
bindings import and that every required element is present, raising
|
|
259
259
|
a clear ``ImportError`` (with an install hint) when they are not.
|
|
260
260
|
Failing loudly here surfaces a misconfigured environment at startup
|
|
261
|
-
rather than at the first ``WebRTCTransport.create`` call
|
|
261
|
+
rather than at the first ``WebRTCTransport.create`` call, and
|
|
262
|
+
pre-warms Gst.init() so the first connection does not pay that cost.
|
|
262
263
|
"""
|
|
263
264
|
try:
|
|
264
265
|
validate_transport_available(TransportType.GSTREAMER)
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/client.py
RENAMED
|
@@ -70,6 +70,7 @@ from reactor_runtime.transports.gstreamer.gst_helpers import (
|
|
|
70
70
|
from reactor_runtime.transports.gstreamer.signals import SignalManager
|
|
71
71
|
from reactor_runtime.transports.gstreamer.sender import AudioSender, VideoSender
|
|
72
72
|
from reactor_runtime.transports.gstreamer.receiver import VideoReceiver, AudioReceiver
|
|
73
|
+
from reactor_runtime.transports.gstreamer.quality import aggregate_qos_score
|
|
73
74
|
from reactor_runtime.transports.gstreamer.settings import (
|
|
74
75
|
SUPPORTED_RTP_HEADER_EXTENSION_URIS,
|
|
75
76
|
VIDEO_BWE_MIN_BITRATE_KBPS,
|
|
@@ -104,6 +105,9 @@ ICE_GATHERING_TIMEOUT_MS = 3000
|
|
|
104
105
|
# Frame queue drain timeout interval in milliseconds
|
|
105
106
|
FRAME_Q_DRAIN_TIMEOUT_MS = 10
|
|
106
107
|
|
|
108
|
+
# Quality score measurement interval
|
|
109
|
+
QUALITY_METER_INTERVAL_MS = 1000
|
|
110
|
+
|
|
107
111
|
# =============================================================================
|
|
108
112
|
# Helpers for converting transport-agnostic types to GStreamer expected ones
|
|
109
113
|
# =============================================================================
|
|
@@ -489,6 +493,38 @@ class GStreamerTransport(WebRTCTransport):
|
|
|
489
493
|
src.set_callback(_drain)
|
|
490
494
|
src.attach(self._main_ctx)
|
|
491
495
|
|
|
496
|
+
def _gst_arm_quality_meter(self) -> None:
|
|
497
|
+
"""GLib thread: log a per-second video quality score for every active video sender."""
|
|
498
|
+
if self._main_ctx is None:
|
|
499
|
+
return
|
|
500
|
+
|
|
501
|
+
src = GLib.timeout_source_new(QUALITY_METER_INTERVAL_MS)
|
|
502
|
+
self._track_source(src)
|
|
503
|
+
|
|
504
|
+
def _tick(*_args) -> bool:
|
|
505
|
+
if self._stopping:
|
|
506
|
+
return GLib.SOURCE_REMOVE
|
|
507
|
+
|
|
508
|
+
scores = [
|
|
509
|
+
s
|
|
510
|
+
for sender in self._senders.values()
|
|
511
|
+
if isinstance(sender, VideoSender)
|
|
512
|
+
for s in [sender.qos()]
|
|
513
|
+
if s is not None
|
|
514
|
+
]
|
|
515
|
+
score = aggregate_qos_score(scores)
|
|
516
|
+
if score is not None:
|
|
517
|
+
logger.info(
|
|
518
|
+
"video quality score",
|
|
519
|
+
score=score,
|
|
520
|
+
senders=len(scores),
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
return GLib.SOURCE_CONTINUE
|
|
524
|
+
|
|
525
|
+
src.set_callback(_tick)
|
|
526
|
+
src.attach(self._main_ctx)
|
|
527
|
+
|
|
492
528
|
def _require_webrtc(self) -> Gst.Element:
|
|
493
529
|
if self._webrtc is None:
|
|
494
530
|
raise RuntimeError("webrtcbin element is not initialized")
|
|
@@ -874,6 +910,7 @@ class GStreamerTransport(WebRTCTransport):
|
|
|
874
910
|
if not self._connected.is_set():
|
|
875
911
|
self._connected.set()
|
|
876
912
|
self._gst_emit_event(ConnectedEvent())
|
|
913
|
+
self._gst_arm_quality_meter()
|
|
877
914
|
elif state in (
|
|
878
915
|
GstWebRTC.WebRTCPeerConnectionState.FAILED,
|
|
879
916
|
GstWebRTC.WebRTCPeerConnectionState.CLOSED,
|
|
@@ -906,10 +943,10 @@ class GStreamerTransport(WebRTCTransport):
|
|
|
906
943
|
|
|
907
944
|
if label == "control":
|
|
908
945
|
self._control_channel = data_channel
|
|
909
|
-
self.
|
|
946
|
+
self._setup_control_channel_handlers()
|
|
910
947
|
else:
|
|
911
948
|
self._data_channel = data_channel
|
|
912
|
-
self.
|
|
949
|
+
self._setup_data_channel_handlers()
|
|
913
950
|
|
|
914
951
|
def _gst_on_pad_added(self, element, pad) -> None:
|
|
915
952
|
if self._stopping or self._stop_event.is_set():
|
|
@@ -1642,6 +1679,7 @@ class GStreamerTransport(WebRTCTransport):
|
|
|
1642
1679
|
width: Optional[int] = None
|
|
1643
1680
|
height: Optional[int] = None
|
|
1644
1681
|
codec_name: Optional[str] = None
|
|
1682
|
+
qos: Optional[float] = None
|
|
1645
1683
|
sender = self._senders.get(mid)
|
|
1646
1684
|
if isinstance(sender, VideoSender):
|
|
1647
1685
|
bitrate_bps = sender.current_bitrate_kbps * 1000
|
|
@@ -1649,6 +1687,7 @@ class GStreamerTransport(WebRTCTransport):
|
|
|
1649
1687
|
width = sender.width
|
|
1650
1688
|
height = sender.height
|
|
1651
1689
|
codec_name = sender.codec_name
|
|
1690
|
+
qos = sender.qos()
|
|
1652
1691
|
elif isinstance(sender, AudioSender):
|
|
1653
1692
|
bitrate_bps = sender.current_bitrate_kbps * 1000
|
|
1654
1693
|
codec_name = sender.codec_name
|
|
@@ -1678,6 +1717,7 @@ class GStreamerTransport(WebRTCTransport):
|
|
|
1678
1717
|
packet_loss=packet_loss,
|
|
1679
1718
|
packets_sent=packets_sent,
|
|
1680
1719
|
jitter=jitter,
|
|
1720
|
+
qos=qos,
|
|
1681
1721
|
)
|
|
1682
1722
|
)
|
|
1683
1723
|
|
|
@@ -75,7 +75,14 @@ def add_many(
|
|
|
75
75
|
the element will remain in NULL state until explicitly set.
|
|
76
76
|
"""
|
|
77
77
|
for e in elements:
|
|
78
|
-
|
|
78
|
+
# Gst.Bin.add() returns the C gboolean on most PyGObject builds, but some
|
|
79
|
+
# return None even on success — so verify the element actually landed in
|
|
80
|
+
# this bin rather than trusting the binding's return value. Checking
|
|
81
|
+
# `is not bin` (not just `is None`) also catches a failed add of an element
|
|
82
|
+
# that already belongs to another bin, where get_parent() returns that
|
|
83
|
+
# other bin.
|
|
84
|
+
bin.add(e)
|
|
85
|
+
if e.get_parent() is not bin:
|
|
79
86
|
raise RuntimeError(f"Failed to add {e.get_name()} to {bin.get_name()}")
|
|
80
87
|
if sync_with_parent:
|
|
81
88
|
e.sync_state_with_parent()
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Copyright (c) 2026 Reactor Technologies, Inc. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Video quality scoring utilities.
|
|
5
|
+
|
|
6
|
+
Provides a formula-based estimate of the *expected* bitrate for a given
|
|
7
|
+
resolution, frame-rate, and codec, and a 0–10 quality score comparing
|
|
8
|
+
a measured bitrate against that expectation.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
# =========================================================================
|
|
14
|
+
# Reference tables
|
|
15
|
+
# =========================================================================
|
|
16
|
+
|
|
17
|
+
# Bits-per-pixel for H.264 at "medium" quality (general content, no motion
|
|
18
|
+
# extremes). Other codecs scale from this baseline via _CODEC_EFFICIENCY.
|
|
19
|
+
_BPP_H264_MEDIUM = 0.08
|
|
20
|
+
|
|
21
|
+
# Compression efficiency relative to H.264 (lower → fewer bits needed for
|
|
22
|
+
# equal perceived quality). Keys are RTP encoding-name strings (uppercase),
|
|
23
|
+
# as returned by GStreamer and SDP negotiation.
|
|
24
|
+
_CODEC_EFFICIENCY: dict[str, float] = {
|
|
25
|
+
"H264": 1.00,
|
|
26
|
+
"H265": 0.55,
|
|
27
|
+
"VP8": 1.00,
|
|
28
|
+
"VP9": 0.65,
|
|
29
|
+
"AV1": 0.45,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# =========================================================================
|
|
33
|
+
# Per-track helpers
|
|
34
|
+
# =========================================================================
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def expected_video_bitrate_bps(
|
|
38
|
+
width: int, height: int, fps: float, codec_name: str
|
|
39
|
+
) -> int:
|
|
40
|
+
"""Return the target bitrate in bps for a good-quality encode."""
|
|
41
|
+
efficiency = _CODEC_EFFICIENCY.get(codec_name.upper(), 1.0)
|
|
42
|
+
return int(width * height * fps * _BPP_H264_MEDIUM * efficiency)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def video_qos_score(
|
|
46
|
+
current_bps: int, width: int, height: int, fps: float, codec_name: str
|
|
47
|
+
) -> float:
|
|
48
|
+
"""Return a 0–10 quality score for a single video track.
|
|
49
|
+
|
|
50
|
+
10.0 means at or above the expected bitrate for good quality.
|
|
51
|
+
Score scales linearly down to 0 as bitrate approaches zero.
|
|
52
|
+
"""
|
|
53
|
+
expected = expected_video_bitrate_bps(width, height, fps, codec_name)
|
|
54
|
+
if expected <= 0:
|
|
55
|
+
return 10.0
|
|
56
|
+
return round(min(10.0, current_bps / expected * 10.0), 1)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# =========================================================================
|
|
60
|
+
# Aggregate score across multiple senders
|
|
61
|
+
# =========================================================================
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def aggregate_qos_score(scores: list[float]) -> Optional[float]:
|
|
65
|
+
"""Return the average 0–10 quality score across all video senders.
|
|
66
|
+
|
|
67
|
+
Returns None when the list is empty (no ready senders).
|
|
68
|
+
"""
|
|
69
|
+
if not scores:
|
|
70
|
+
return None
|
|
71
|
+
return round(sum(scores) / len(scores), 1)
|
|
@@ -9,6 +9,7 @@ from typing import List, NamedTuple, Optional
|
|
|
9
9
|
import numpy as np
|
|
10
10
|
|
|
11
11
|
from reactor_runtime.transports.gstreamer.encoders import EncoderFactory
|
|
12
|
+
from reactor_runtime.transports.gstreamer.quality import video_qos_score
|
|
12
13
|
from reactor_runtime.transports.gstreamer.settings import VIDEO_BWE_TARGET_BITRATE_KBPS
|
|
13
14
|
from reactor_runtime.transports.gstreamer.sdp.codec import CodecEntry
|
|
14
15
|
from reactor_runtime.transports.gstreamer.sdp.extmap import SdpExtmap
|
|
@@ -208,6 +209,15 @@ class VideoSender(_SenderStreamBase):
|
|
|
208
209
|
"""Forward target bitrate to the encoder bin."""
|
|
209
210
|
self._encoder.set_target_bitrate_kbps(int(new_kbps))
|
|
210
211
|
|
|
212
|
+
def qos(self) -> Optional[float]:
|
|
213
|
+
"""Return the 0–10 quality score for this sender, or None if not ready."""
|
|
214
|
+
w, h, fps = self.width, self.height, self.fps
|
|
215
|
+
if not w or not h or not fps:
|
|
216
|
+
return None
|
|
217
|
+
return video_qos_score(
|
|
218
|
+
self.current_bitrate_kbps * 1000, w, h, fps, self._codec_name
|
|
219
|
+
)
|
|
220
|
+
|
|
211
221
|
def push_buffer(self, frame: np.ndarray) -> bool:
|
|
212
222
|
"""
|
|
213
223
|
Extract dimensions and bytes from the frame (e.g. numpy array with .shape and .tobytes()),
|
|
@@ -85,6 +85,7 @@ src/reactor_runtime/transports/gstreamer/__init__.py
|
|
|
85
85
|
src/reactor_runtime/transports/gstreamer/client.py
|
|
86
86
|
src/reactor_runtime/transports/gstreamer/gst.py
|
|
87
87
|
src/reactor_runtime/transports/gstreamer/gst_helpers.py
|
|
88
|
+
src/reactor_runtime/transports/gstreamer/quality.py
|
|
88
89
|
src/reactor_runtime/transports/gstreamer/settings.py
|
|
89
90
|
src/reactor_runtime/transports/gstreamer/signals.py
|
|
90
91
|
src/reactor_runtime/transports/gstreamer/decoders/__init__.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/driver/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/driver/step_result.py
RENAMED
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/events/__init__.py
RENAMED
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/events/connected.py
RENAMED
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/events/event.py
RENAMED
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/events/messages.py
RENAMED
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/events/upload.py
RENAMED
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/internal/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/model/__init__.py
RENAMED
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/model/decorators.py
RENAMED
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/model/handlers.py
RENAMED
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/model/reactor_model.py
RENAMED
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/pipeline/__init__.py
RENAMED
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/pipeline/idle.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/tracks/__init__.py
RENAMED
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/tracks/descriptors.py
RENAMED
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/tracks/input.py
RENAMED
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/interface/tracks/output.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/profiling/backends/__init__.py
RENAMED
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/profiling/backends/base.py
RENAMED
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/profiling/backends/file.py
RENAMED
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/profiling/backends/otlp.py
RENAMED
|
File without changes
|
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/profiling/nvml_sampler.py
RENAMED
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/profiling/plotting/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/recording/chunk_encoder.py
RENAMED
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/recording/chunk_uploader.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/recording/session_recorder.py
RENAMED
|
File without changes
|
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/recording/track_resolver.py
RENAMED
|
File without changes
|
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/runtimes/http/http_runtime.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/serve/commands/__init__.py
RENAMED
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/serve/commands/schema.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/gst.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/sdp/ice.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/settings.py
RENAMED
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime/transports/gstreamer/signals.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{reactor_runtime-2.8.0 → reactor_runtime-2.9.0}/src/reactor_runtime.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|