janus-api-streaming 0.2.0__py3-none-any.whl
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.
- janus_api_streaming-0.2.0.dist-info/METADATA +57 -0
- janus_api_streaming-0.2.0.dist-info/RECORD +14 -0
- janus_api_streaming-0.2.0.dist-info/WHEEL +4 -0
- janus_api_streaming-0.2.0.dist-info/entry_points.txt +2 -0
- janus_streaming/__init__.py +67 -0
- janus_streaming/_compat.py +638 -0
- janus_streaming/admin.py +211 -0
- janus_streaming/errors.py +39 -0
- janus_streaming/models.py +214 -0
- janus_streaming/py.typed +0 -0
- janus_streaming/requests.py +489 -0
- janus_streaming/responses.py +189 -0
- janus_streaming/runtime.py +143 -0
- janus_streaming/viewer.py +170 -0
janus_streaming/admin.py
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from collections.abc import Awaitable, Callable
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from . import _compat
|
|
8
|
+
from .errors import StreamingNotStarted, StreamingProtocolError
|
|
9
|
+
from .models import (
|
|
10
|
+
CreatedMountpoint,
|
|
11
|
+
FileMountpointSpec,
|
|
12
|
+
MountpointInfo,
|
|
13
|
+
MountpointSummary,
|
|
14
|
+
RtpMountpointSpec,
|
|
15
|
+
RtspMountpointSpec,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
HandleFactory = Callable[..., Awaitable[_compat.JanusStreamingHandle]]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class StreamingAdminClient:
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
session: Any,
|
|
25
|
+
*,
|
|
26
|
+
admin_key: str | None = None,
|
|
27
|
+
request_timeout: float = 10.0,
|
|
28
|
+
attach_timeout: float = 5.0,
|
|
29
|
+
max_concurrency: int = 32,
|
|
30
|
+
handle_factory: HandleFactory | None = None,
|
|
31
|
+
) -> None:
|
|
32
|
+
if request_timeout <= 0:
|
|
33
|
+
raise ValueError("request_timeout must be greater than zero")
|
|
34
|
+
if attach_timeout <= 0:
|
|
35
|
+
raise ValueError("attach_timeout must be greater than zero")
|
|
36
|
+
if max_concurrency < 1:
|
|
37
|
+
raise ValueError("max_concurrency must be at least one")
|
|
38
|
+
self._session = session
|
|
39
|
+
self._admin_key = admin_key
|
|
40
|
+
self._request_timeout = request_timeout
|
|
41
|
+
self._attach_timeout = attach_timeout
|
|
42
|
+
self._semaphore = asyncio.Semaphore(max_concurrency)
|
|
43
|
+
self._start_lock = asyncio.Lock()
|
|
44
|
+
self._handle: _compat.JanusStreamingHandle | None = None
|
|
45
|
+
self._handle_factory = handle_factory or _compat.JanusStreamingHandle.attach
|
|
46
|
+
|
|
47
|
+
async def start(self) -> None:
|
|
48
|
+
async with self._start_lock:
|
|
49
|
+
if self._handle is not None:
|
|
50
|
+
return
|
|
51
|
+
self._handle = await self._handle_factory(
|
|
52
|
+
self._session,
|
|
53
|
+
admin_key=self._admin_key,
|
|
54
|
+
attach_timeout=self._attach_timeout,
|
|
55
|
+
default_timeout=self._request_timeout,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
async def close(self) -> None:
|
|
59
|
+
handle, self._handle = self._handle, None
|
|
60
|
+
if handle is not None:
|
|
61
|
+
await handle.close()
|
|
62
|
+
|
|
63
|
+
async def list_mountpoints(self) -> tuple[MountpointSummary, ...]:
|
|
64
|
+
payload = await self._command(_compat.list_request(), "list")
|
|
65
|
+
values = payload.get("list", payload.get("mountpoints", []))
|
|
66
|
+
if not isinstance(values, list):
|
|
67
|
+
raise StreamingProtocolError("list response did not contain a mountpoint list")
|
|
68
|
+
return tuple(self._parse_summary(item) for item in values)
|
|
69
|
+
|
|
70
|
+
async def info(self, mountpoint_id: int, *, secret: str | None = None) -> MountpointInfo:
|
|
71
|
+
payload = await self._command(
|
|
72
|
+
_compat.info_request(mountpoint_id, secret),
|
|
73
|
+
"info",
|
|
74
|
+
)
|
|
75
|
+
value = payload.get("info", payload.get("mountpoint"))
|
|
76
|
+
if not isinstance(value, dict):
|
|
77
|
+
raise StreamingProtocolError("info response did not contain mountpoint information")
|
|
78
|
+
return self._parse_info(value)
|
|
79
|
+
|
|
80
|
+
async def create_rtp(self, spec: RtpMountpointSpec) -> CreatedMountpoint:
|
|
81
|
+
return await self._create(_compat.rtp_create_request(spec, self._admin_key))
|
|
82
|
+
|
|
83
|
+
async def create_rtsp(self, spec: RtspMountpointSpec) -> CreatedMountpoint:
|
|
84
|
+
return await self._create(_compat.rtsp_create_request(spec, self._admin_key))
|
|
85
|
+
|
|
86
|
+
async def create_file(self, spec: FileMountpointSpec) -> CreatedMountpoint:
|
|
87
|
+
return await self._create(_compat.file_create_request(spec, self._admin_key))
|
|
88
|
+
|
|
89
|
+
async def edit(
|
|
90
|
+
self,
|
|
91
|
+
mountpoint_id: int,
|
|
92
|
+
*,
|
|
93
|
+
secret: str | None = None,
|
|
94
|
+
new_description: str | None = None,
|
|
95
|
+
new_metadata: str | None = None,
|
|
96
|
+
new_secret: str | None = None,
|
|
97
|
+
new_pin: str | None = None,
|
|
98
|
+
new_private: bool | None = None,
|
|
99
|
+
permanent: bool = False,
|
|
100
|
+
edited_event: bool = False,
|
|
101
|
+
) -> dict[str, Any]:
|
|
102
|
+
return await self._command(
|
|
103
|
+
_compat.edit_request(
|
|
104
|
+
mountpoint_id,
|
|
105
|
+
secret=secret,
|
|
106
|
+
new_description=new_description,
|
|
107
|
+
new_metadata=new_metadata,
|
|
108
|
+
new_secret=new_secret,
|
|
109
|
+
new_pin=new_pin,
|
|
110
|
+
new_is_private=new_private,
|
|
111
|
+
permanent=permanent,
|
|
112
|
+
edited_event=edited_event,
|
|
113
|
+
),
|
|
114
|
+
"edit",
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
async def enable(self, mountpoint_id: int, *, secret: str | None = None) -> None:
|
|
118
|
+
await self._command(_compat.enable_request(mountpoint_id, secret), "enable")
|
|
119
|
+
|
|
120
|
+
async def disable(
|
|
121
|
+
self,
|
|
122
|
+
mountpoint_id: int,
|
|
123
|
+
*,
|
|
124
|
+
secret: str | None = None,
|
|
125
|
+
stop_recording: bool = True,
|
|
126
|
+
) -> None:
|
|
127
|
+
await self._command(
|
|
128
|
+
_compat.disable_request(mountpoint_id, secret, stop_recording),
|
|
129
|
+
"disable",
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
async def kick_all(self, mountpoint_id: int, *, secret: str | None = None) -> None:
|
|
133
|
+
await self._command(_compat.kick_all_request(mountpoint_id, secret), "kick_all")
|
|
134
|
+
|
|
135
|
+
async def destroy(
|
|
136
|
+
self,
|
|
137
|
+
mountpoint_id: int,
|
|
138
|
+
*,
|
|
139
|
+
secret: str | None = None,
|
|
140
|
+
permanent: bool = False,
|
|
141
|
+
) -> None:
|
|
142
|
+
await self._command(
|
|
143
|
+
_compat.destroy_request(mountpoint_id, secret, permanent),
|
|
144
|
+
"destroy",
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
async def _create(self, request: Any) -> CreatedMountpoint:
|
|
148
|
+
payload = await self._command(request, "create")
|
|
149
|
+
value = payload.get("stream")
|
|
150
|
+
if not isinstance(value, dict):
|
|
151
|
+
raise StreamingProtocolError("create response did not contain a stream")
|
|
152
|
+
normalized = dict(value)
|
|
153
|
+
normalized["private"] = normalized.pop("is_private", normalized.get("private"))
|
|
154
|
+
normalized["permanent"] = bool(payload.get("permanent", False))
|
|
155
|
+
return CreatedMountpoint.model_validate(normalized)
|
|
156
|
+
|
|
157
|
+
async def _command(self, request: Any, operation: str) -> dict[str, Any]:
|
|
158
|
+
handle = self._require_handle()
|
|
159
|
+
async with self._semaphore:
|
|
160
|
+
response = await handle.request(request, operation=operation)
|
|
161
|
+
return _compat.plugin_payload(response)
|
|
162
|
+
|
|
163
|
+
def _require_handle(self) -> _compat.JanusStreamingHandle:
|
|
164
|
+
if self._handle is None:
|
|
165
|
+
raise StreamingNotStarted("StreamingAdminClient.start() has not been called")
|
|
166
|
+
return self._handle
|
|
167
|
+
|
|
168
|
+
@staticmethod
|
|
169
|
+
def _parse_summary(value: Any) -> MountpointSummary:
|
|
170
|
+
if not isinstance(value, dict):
|
|
171
|
+
value = _compat.to_jsonable(value)
|
|
172
|
+
normalized = dict(value)
|
|
173
|
+
normalized["media"] = [
|
|
174
|
+
StreamingAdminClient._normalize_track(item) for item in value.get("media", [])
|
|
175
|
+
]
|
|
176
|
+
return MountpointSummary.model_validate(normalized)
|
|
177
|
+
|
|
178
|
+
@staticmethod
|
|
179
|
+
def _parse_info(value: Any) -> MountpointInfo:
|
|
180
|
+
if not isinstance(value, dict):
|
|
181
|
+
value = _compat.to_jsonable(value)
|
|
182
|
+
normalized = dict(value)
|
|
183
|
+
normalized["private"] = normalized.pop("is_private", normalized.get("private"))
|
|
184
|
+
normalized.pop("secret", None)
|
|
185
|
+
normalized.pop("pin", None)
|
|
186
|
+
normalized["media"] = [
|
|
187
|
+
StreamingAdminClient._normalize_track(item) for item in value.get("media", [])
|
|
188
|
+
]
|
|
189
|
+
return MountpointInfo.model_validate(normalized)
|
|
190
|
+
|
|
191
|
+
@staticmethod
|
|
192
|
+
def _normalize_track(value: Any) -> dict[str, Any]:
|
|
193
|
+
if not isinstance(value, dict):
|
|
194
|
+
value = _compat.to_jsonable(value)
|
|
195
|
+
normalized = dict(value)
|
|
196
|
+
normalized["payload_type"] = normalized.pop("pt", normalized.get("payload_type"))
|
|
197
|
+
normalized["port"] = normalized.get("port", normalized.get("rtp_port"))
|
|
198
|
+
allowed = {
|
|
199
|
+
"mid",
|
|
200
|
+
"type",
|
|
201
|
+
"label",
|
|
202
|
+
"msid",
|
|
203
|
+
"mindex",
|
|
204
|
+
"age_ms",
|
|
205
|
+
"payload_type",
|
|
206
|
+
"codec",
|
|
207
|
+
"rtpmap",
|
|
208
|
+
"fmtp",
|
|
209
|
+
"port",
|
|
210
|
+
}
|
|
211
|
+
return {key: item for key, item in normalized.items() if key in allowed}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class StreamingError(Exception):
|
|
5
|
+
"""Base exception exposed by janus-api-streaming."""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class StreamingNotStarted(StreamingError):
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class StreamingTimeout(StreamingError):
|
|
13
|
+
def __init__(self, operation: str, timeout: float) -> None:
|
|
14
|
+
super().__init__(f"{operation!r} timed out after {timeout:.1f}s")
|
|
15
|
+
self.operation = operation
|
|
16
|
+
self.timeout = timeout
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class StreamingBackendError(StreamingError):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class StreamingProtocolError(StreamingError):
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class JanusCommandError(StreamingError):
|
|
28
|
+
def __init__(self, code: int | None, reason: str) -> None:
|
|
29
|
+
super().__init__(reason)
|
|
30
|
+
self.code = code
|
|
31
|
+
self.reason = reason
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class InvalidViewerState(StreamingError):
|
|
35
|
+
def __init__(self, current: object, expected: tuple[object, ...]) -> None:
|
|
36
|
+
expected_text = ", ".join(str(value) for value in expected)
|
|
37
|
+
super().__init__(f"viewer is in state {current!s}; expected one of: {expected_text}")
|
|
38
|
+
self.current = current
|
|
39
|
+
self.expected = expected
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from enum import StrEnum
|
|
4
|
+
from typing import Annotated, Literal
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class StrictModel(BaseModel):
|
|
10
|
+
model_config = ConfigDict(
|
|
11
|
+
extra="forbid",
|
|
12
|
+
frozen=True,
|
|
13
|
+
populate_by_name=True,
|
|
14
|
+
str_strip_whitespace=True,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SessionDescription(StrictModel):
|
|
19
|
+
type: Literal["offer", "answer"]
|
|
20
|
+
sdp: str = Field(min_length=1, repr=False)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class IceCandidate(StrictModel):
|
|
24
|
+
candidate: str = Field(min_length=1, repr=False)
|
|
25
|
+
sdp_mid: str | None = Field(default=None, alias="sdpMid")
|
|
26
|
+
sdp_mline_index: int | None = Field(default=None, alias="sdpMLineIndex", ge=0)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ViewerState(StrEnum):
|
|
30
|
+
NEW = "new"
|
|
31
|
+
OFFERED = "offered"
|
|
32
|
+
ACTIVE = "active"
|
|
33
|
+
PAUSED = "paused"
|
|
34
|
+
FAILED = "failed"
|
|
35
|
+
CLOSED = "closed"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class AudioTrackSpec(StrictModel):
|
|
39
|
+
type: Literal["audio"] = "audio"
|
|
40
|
+
mid: str = Field(min_length=1, max_length=64)
|
|
41
|
+
port: int | None = Field(default=None, ge=1, le=65535)
|
|
42
|
+
rtcp_port: int | None = Field(default=None, ge=1, le=65535)
|
|
43
|
+
payload_type: int = Field(default=111, ge=0, le=127)
|
|
44
|
+
codec: str = Field(default="opus", min_length=1)
|
|
45
|
+
fmtp: str | None = None
|
|
46
|
+
multicast: str | None = None
|
|
47
|
+
bind_interface: str | None = None
|
|
48
|
+
skew: bool = False
|
|
49
|
+
label: str | None = None
|
|
50
|
+
msid: str | None = None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class VideoTrackSpec(StrictModel):
|
|
54
|
+
type: Literal["video"] = "video"
|
|
55
|
+
mid: str = Field(min_length=1, max_length=64)
|
|
56
|
+
port: int | None = Field(default=None, ge=1, le=65535)
|
|
57
|
+
rtcp_port: int | None = Field(default=None, ge=1, le=65535)
|
|
58
|
+
payload_type: int = Field(default=96, ge=0, le=127)
|
|
59
|
+
codec: str = Field(default="h264", min_length=1)
|
|
60
|
+
fmtp: str | None = None
|
|
61
|
+
multicast: str | None = None
|
|
62
|
+
bind_interface: str | None = None
|
|
63
|
+
skew: bool = False
|
|
64
|
+
simulcast: bool = False
|
|
65
|
+
port2: int | None = Field(default=None, ge=1, le=65535)
|
|
66
|
+
port3: int | None = Field(default=None, ge=1, le=65535)
|
|
67
|
+
svc: bool = False
|
|
68
|
+
h264_sps: str | None = None
|
|
69
|
+
collision_ms: int | None = Field(default=None, ge=0)
|
|
70
|
+
label: str | None = None
|
|
71
|
+
msid: str | None = None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class DataTrackSpec(StrictModel):
|
|
75
|
+
type: Literal["data"] = "data"
|
|
76
|
+
mid: str = Field(min_length=1, max_length=64)
|
|
77
|
+
port: int | None = Field(default=None, ge=1, le=65535)
|
|
78
|
+
data_type: Literal["text", "binary"] = "text"
|
|
79
|
+
buffer_latest_message: bool = False
|
|
80
|
+
multicast: str | None = None
|
|
81
|
+
bind_interface: str | None = None
|
|
82
|
+
label: str | None = None
|
|
83
|
+
msid: str | None = None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
TrackSpec = Annotated[AudioTrackSpec | VideoTrackSpec | DataTrackSpec, Field(discriminator="type")]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class RtpMountpointSpec(StrictModel):
|
|
90
|
+
id: int | None = Field(default=None, ge=0)
|
|
91
|
+
name: str | None = None
|
|
92
|
+
description: str | None = None
|
|
93
|
+
metadata: str | None = None
|
|
94
|
+
private: bool = True
|
|
95
|
+
secret: str | None = Field(default=None, repr=False)
|
|
96
|
+
pin: str | None = Field(default=None, repr=False)
|
|
97
|
+
enabled: bool = False
|
|
98
|
+
permanent: bool = False
|
|
99
|
+
media: tuple[TrackSpec, ...] = Field(min_length=1)
|
|
100
|
+
threads: int | None = Field(default=None, ge=0)
|
|
101
|
+
buffer_keyframes_ms: int | None = Field(default=None, ge=0)
|
|
102
|
+
buffer_keyframes_bytes: int | None = Field(default=None, ge=0)
|
|
103
|
+
srtp_suite: Literal[32, 80] | None = None
|
|
104
|
+
srtp_crypto: str | None = Field(default=None, repr=False)
|
|
105
|
+
e2ee: bool | None = None
|
|
106
|
+
|
|
107
|
+
@model_validator(mode="after")
|
|
108
|
+
def unique_mids(self) -> RtpMountpointSpec:
|
|
109
|
+
mids = [item.mid for item in self.media]
|
|
110
|
+
if len(mids) != len(set(mids)):
|
|
111
|
+
raise ValueError("media mids must be unique")
|
|
112
|
+
return self
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class RtspMountpointSpec(StrictModel):
|
|
116
|
+
id: int | None = Field(default=None, ge=0)
|
|
117
|
+
url: str = Field(min_length=1)
|
|
118
|
+
name: str | None = None
|
|
119
|
+
description: str | None = None
|
|
120
|
+
metadata: str | None = None
|
|
121
|
+
private: bool = True
|
|
122
|
+
secret: str | None = Field(default=None, repr=False)
|
|
123
|
+
pin: str | None = Field(default=None, repr=False)
|
|
124
|
+
enabled: bool = False
|
|
125
|
+
permanent: bool = False
|
|
126
|
+
username: str | None = None
|
|
127
|
+
password: str | None = Field(default=None, repr=False)
|
|
128
|
+
reconnect_delay_seconds: int | None = Field(default=5, ge=0)
|
|
129
|
+
session_timeout_seconds: int | None = Field(default=None, ge=0)
|
|
130
|
+
media_timeout_seconds: int | None = Field(default=None, ge=0)
|
|
131
|
+
connection_timeout_seconds: int | None = Field(default=None, ge=0)
|
|
132
|
+
bind_interface: str | None = None
|
|
133
|
+
notify_changes: bool = True
|
|
134
|
+
fail_check: bool | None = None
|
|
135
|
+
quirk: bool | None = None
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class FileMountpointSpec(StrictModel):
|
|
139
|
+
type: Literal["live", "ondemand"]
|
|
140
|
+
id: int | None = Field(default=None, ge=0)
|
|
141
|
+
filename: str = Field(min_length=1)
|
|
142
|
+
name: str | None = None
|
|
143
|
+
description: str | None = None
|
|
144
|
+
metadata: str | None = None
|
|
145
|
+
private: bool = True
|
|
146
|
+
secret: str | None = Field(default=None, repr=False)
|
|
147
|
+
pin: str | None = Field(default=None, repr=False)
|
|
148
|
+
enabled: bool = False
|
|
149
|
+
permanent: bool = False
|
|
150
|
+
audio: bool = True
|
|
151
|
+
video: bool = False
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class MountpointTrack(StrictModel):
|
|
155
|
+
mid: str
|
|
156
|
+
type: Literal["audio", "video", "data"]
|
|
157
|
+
label: str | None = None
|
|
158
|
+
msid: str | None = None
|
|
159
|
+
mindex: int | None = None
|
|
160
|
+
age_ms: int | None = Field(default=None, ge=0)
|
|
161
|
+
payload_type: int | None = Field(default=None, ge=0, le=127)
|
|
162
|
+
codec: str | None = None
|
|
163
|
+
rtpmap: str | None = None
|
|
164
|
+
fmtp: str | None = None
|
|
165
|
+
port: int | None = Field(default=None, ge=1, le=65535)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class MountpointSummary(StrictModel):
|
|
169
|
+
id: int
|
|
170
|
+
type: Literal["rtp", "live", "ondemand", "rtsp"]
|
|
171
|
+
description: str | None = None
|
|
172
|
+
metadata: str | None = None
|
|
173
|
+
enabled: bool | None = None
|
|
174
|
+
media: tuple[MountpointTrack, ...] = ()
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class MountpointInfo(StrictModel):
|
|
178
|
+
id: int
|
|
179
|
+
type: Literal["rtp", "live", "ondemand", "rtsp"]
|
|
180
|
+
name: str | None = None
|
|
181
|
+
description: str | None = None
|
|
182
|
+
metadata: str | None = None
|
|
183
|
+
private: bool | None = None
|
|
184
|
+
viewers: int = Field(default=0, ge=0)
|
|
185
|
+
enabled: bool | None = None
|
|
186
|
+
media: tuple[MountpointTrack, ...] = ()
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class CreatedPort(StrictModel):
|
|
190
|
+
type: Literal["audio", "video", "data"]
|
|
191
|
+
mid: str
|
|
192
|
+
port: int = Field(ge=1, le=65535)
|
|
193
|
+
msid: str | None = None
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class CreatedMountpoint(StrictModel):
|
|
197
|
+
id: int
|
|
198
|
+
type: Literal["rtp", "live", "ondemand", "rtsp"]
|
|
199
|
+
description: str | None = None
|
|
200
|
+
private: bool | None = None
|
|
201
|
+
permanent: bool = False
|
|
202
|
+
ports: tuple[CreatedPort, ...] = ()
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class ConfigureStream(StrictModel):
|
|
206
|
+
mid: str
|
|
207
|
+
send: bool | None = None
|
|
208
|
+
substream: int | None = Field(default=None, ge=0, le=2)
|
|
209
|
+
temporal: int | None = Field(default=None, ge=0, le=2)
|
|
210
|
+
fallback_microseconds: int | None = Field(default=250_000, ge=0)
|
|
211
|
+
spatial_layer: int | None = Field(default=None, ge=0, le=1)
|
|
212
|
+
temporal_layer: int | None = Field(default=None, ge=0, le=2)
|
|
213
|
+
min_delay: int | None = Field(default=None, ge=0)
|
|
214
|
+
max_delay: int | None = Field(default=None, ge=0)
|
janus_streaming/py.typed
ADDED
|
File without changes
|