streamlit-webrtc 0.49.3__py3-none-any.whl → 0.50.1__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.
- streamlit_webrtc/__init__.py +2 -1
- streamlit_webrtc/factory.py +2 -3
- streamlit_webrtc/frontend/dist/index.html +1 -1
- streamlit_webrtc/mix.py +14 -14
- streamlit_webrtc/process.py +6 -4
- streamlit_webrtc/webrtc.py +10 -8
- {streamlit_webrtc-0.49.3.dist-info → streamlit_webrtc-0.50.1.dist-info}/METADATA +9 -17
- {streamlit_webrtc-0.49.3.dist-info → streamlit_webrtc-0.50.1.dist-info}/RECORD +11 -11
- {streamlit_webrtc-0.49.3.dist-info → streamlit_webrtc-0.50.1.dist-info}/WHEEL +1 -1
- {streamlit_webrtc-0.49.3.dist-info → streamlit_webrtc-0.50.1.dist-info/licenses}/LICENSE +0 -0
streamlit_webrtc/__init__.py
CHANGED
@@ -23,7 +23,7 @@ from .config import (
|
|
23
23
|
VideoHTMLAttributes,
|
24
24
|
)
|
25
25
|
from .factory import create_mix_track, create_process_track, create_video_source_track
|
26
|
-
from .mix import MixerCallback
|
26
|
+
from .mix import MediaStreamMixTrack, MixerCallback
|
27
27
|
from .source import VideoSourceCallback, VideoSourceTrack
|
28
28
|
from .webrtc import (
|
29
29
|
AudioProcessorBase,
|
@@ -77,6 +77,7 @@ __all__ = [
|
|
77
77
|
"create_process_track",
|
78
78
|
"create_mix_track",
|
79
79
|
"MixerCallback",
|
80
|
+
"MediaStreamMixTrack",
|
80
81
|
"WebRtcStreamerContext",
|
81
82
|
"WebRtcStreamerState",
|
82
83
|
"DEFAULT_AUDIO_HTML_ATTRS",
|
streamlit_webrtc/factory.py
CHANGED
@@ -1,7 +1,6 @@
|
|
1
|
-
from typing import Literal, Optional, Union, overload
|
1
|
+
from typing import Literal, Optional, Type, Union, overload
|
2
2
|
|
3
3
|
import streamlit as st
|
4
|
-
from aiortc import MediaStreamTrack
|
5
4
|
|
6
5
|
from .eventloop import get_global_event_loop, loop_context
|
7
6
|
from .mix import MediaStreamMixTrack, MixerCallback
|
@@ -33,7 +32,7 @@ _PROCESSOR_TRACK_CACHE_KEY_PREFIX = "__PROCESSOR_TRACK_CACHE__"
|
|
33
32
|
|
34
33
|
def _get_track_class(
|
35
34
|
kind: Literal["video", "audio"], async_processing: bool
|
36
|
-
) ->
|
35
|
+
) -> Union[Type[MediaProcessTrack], Type[AsyncMediaProcessTrack]]:
|
37
36
|
if kind == "video":
|
38
37
|
if async_processing:
|
39
38
|
return AsyncVideoProcessTrack
|
streamlit_webrtc/mix.py
CHANGED
@@ -8,12 +8,14 @@ import time
|
|
8
8
|
import traceback
|
9
9
|
import weakref
|
10
10
|
from collections import OrderedDict
|
11
|
-
from typing import Callable, Generic, List, NamedTuple, Optional, Union
|
11
|
+
from typing import Callable, Generic, List, NamedTuple, Optional, Union, cast
|
12
12
|
|
13
13
|
import av
|
14
14
|
from aiortc import MediaStreamTrack
|
15
15
|
from aiortc.contrib.media import RelayStreamTrack
|
16
16
|
from aiortc.mediastreams import MediaStreamError
|
17
|
+
from av.frame import Frame
|
18
|
+
from av.packet import Packet
|
17
19
|
|
18
20
|
from .eventloop import get_global_event_loop, loop_context
|
19
21
|
from .models import FrameT
|
@@ -37,12 +39,11 @@ VIDEO_TIME_BASE = fractions.Fraction(1, VIDEO_CLOCK_RATE)
|
|
37
39
|
|
38
40
|
|
39
41
|
MixerCallback = Callable[[List[FrameT]], FrameT]
|
40
|
-
Frame = Union[av.VideoFrame, av.AudioFrame]
|
41
42
|
|
42
43
|
|
43
44
|
class InputQueueItem(NamedTuple):
|
44
|
-
source_track_id:
|
45
|
-
frame: Optional[Frame]
|
45
|
+
source_track_id: str
|
46
|
+
frame: Optional[Union[Frame, Packet]]
|
46
47
|
|
47
48
|
|
48
49
|
async def input_track_coro(
|
@@ -99,10 +100,10 @@ async def mix_coro(mix_track: "MediaStreamMixTrack"):
|
|
99
100
|
if output_frame.pts is None and output_frame.time_base is None:
|
100
101
|
timestamp = time.monotonic() - started_at
|
101
102
|
if isinstance(output_frame, av.VideoFrame):
|
102
|
-
output_frame.pts = timestamp * VIDEO_CLOCK_RATE
|
103
|
+
output_frame.pts = int(timestamp * VIDEO_CLOCK_RATE)
|
103
104
|
output_frame.time_base = VIDEO_TIME_BASE
|
104
105
|
elif isinstance(output_frame, av.AudioFrame):
|
105
|
-
output_frame.pts = timestamp * AUDIO_SAMPLE_RATE
|
106
|
+
output_frame.pts = int(timestamp * AUDIO_SAMPLE_RATE)
|
106
107
|
output_frame.time_base = AUDIO_TIME_BASE
|
107
108
|
|
108
109
|
except Exception:
|
@@ -128,7 +129,7 @@ class MediaStreamMixTrack(MediaStreamTrack, Generic[FrameT]):
|
|
128
129
|
_input_queue: asyncio.Queue
|
129
130
|
_queue: "asyncio.Queue[Optional[Frame]]"
|
130
131
|
_latest_frames_map: (
|
131
|
-
"weakref.WeakKeyDictionary[RelayStreamTrack, Union[Frame, None]]"
|
132
|
+
"weakref.WeakKeyDictionary[RelayStreamTrack, Union[Frame, Packet, None]]"
|
132
133
|
)
|
133
134
|
_latest_frames_updated_event: asyncio.Event
|
134
135
|
|
@@ -146,7 +147,7 @@ class MediaStreamMixTrack(MediaStreamTrack, Generic[FrameT]):
|
|
146
147
|
mixer_output_interval: float = 1 / 30,
|
147
148
|
) -> None:
|
148
149
|
self.kind = kind
|
149
|
-
self._mixer_callback = mixer_callback
|
150
|
+
self._mixer_callback: MixerCallback[FrameT] = mixer_callback
|
150
151
|
self._mixer_callback_lock = threading.Lock()
|
151
152
|
|
152
153
|
self.mixer_output_interval = mixer_output_interval
|
@@ -210,7 +211,7 @@ class MediaStreamMixTrack(MediaStreamTrack, Generic[FrameT]):
|
|
210
211
|
|
211
212
|
relay = get_global_relay()
|
212
213
|
with loop_context(self._loop):
|
213
|
-
input_proxy = relay.subscribe(input_track)
|
214
|
+
input_proxy = cast(RelayStreamTrack, relay.subscribe(input_track))
|
214
215
|
|
215
216
|
self._input_proxies[input_track] = input_proxy
|
216
217
|
|
@@ -223,7 +224,7 @@ class MediaStreamMixTrack(MediaStreamTrack, Generic[FrameT]):
|
|
223
224
|
)
|
224
225
|
self._input_tasks[input_proxy] = task
|
225
226
|
|
226
|
-
input_proxy.on("ended"
|
227
|
+
input_proxy.on("ended", functools.partial(self.remove_input_proxy, input_proxy))
|
227
228
|
|
228
229
|
def remove_input_proxy(self, input_proxy: RelayStreamTrack) -> None:
|
229
230
|
LOGGER.debug("Remove a relay track %s from %s", input_proxy, self)
|
@@ -241,13 +242,13 @@ class MediaStreamMixTrack(MediaStreamTrack, Generic[FrameT]):
|
|
241
242
|
task.cancel()
|
242
243
|
|
243
244
|
def _set_latest_frame(
|
244
|
-
self, input_proxy: RelayStreamTrack, frame: Union[Frame, None]
|
245
|
+
self, input_proxy: RelayStreamTrack, frame: Union[Frame, Packet, None]
|
245
246
|
):
|
246
247
|
# TODO: Lock here to make these 2 lines atomic
|
247
248
|
self._latest_frames_map[input_proxy] = frame
|
248
249
|
self._latest_frames_updated_event.set()
|
249
250
|
|
250
|
-
async def _get_latest_frames(self) -> List[Frame]:
|
251
|
+
async def _get_latest_frames(self) -> List[Union[Frame, Packet]]:
|
251
252
|
# TODO: Lock here to make these 2 lines atomic
|
252
253
|
await self._latest_frames_updated_event.wait()
|
253
254
|
self._latest_frames_updated_event.clear()
|
@@ -257,8 +258,7 @@ class MediaStreamMixTrack(MediaStreamTrack, Generic[FrameT]):
|
|
257
258
|
self._latest_frames_map.get(proxy)
|
258
259
|
for proxy in self._input_proxies.values()
|
259
260
|
]
|
260
|
-
|
261
|
-
return latest_frames
|
261
|
+
return [f for f in latest_frames if f is not None]
|
262
262
|
|
263
263
|
async def recv(self):
|
264
264
|
if self.readyState != "live":
|
streamlit_webrtc/process.py
CHANGED
@@ -25,11 +25,12 @@ class MediaProcessTrack(MediaStreamTrack, Generic[ProcessorT, FrameT]):
|
|
25
25
|
self.track = track
|
26
26
|
self.processor: ProcessorT = processor
|
27
27
|
|
28
|
-
@self.track.on("ended")
|
29
28
|
def on_input_track_ended():
|
30
29
|
logger.debug("Input track %s ended. Stop self %s", self.track, self)
|
31
30
|
self.stop()
|
32
31
|
|
32
|
+
self.track.on("ended", on_input_track_ended)
|
33
|
+
|
33
34
|
async def recv(self):
|
34
35
|
if self.readyState != "live":
|
35
36
|
raise MediaStreamError
|
@@ -98,11 +99,12 @@ class AsyncMediaProcessTrack(MediaStreamTrack, Generic[ProcessorT, FrameT]):
|
|
98
99
|
)
|
99
100
|
self._thread.start()
|
100
101
|
|
101
|
-
@self.track.on("ended")
|
102
102
|
def on_input_track_ended():
|
103
103
|
logger.debug("Input track %s ended. Stop self %s", self.track, self)
|
104
104
|
self.stop()
|
105
105
|
|
106
|
+
self.track.on("ended", on_input_track_ended)
|
107
|
+
|
106
108
|
def _run_worker_thread(self):
|
107
109
|
try:
|
108
110
|
self._worker_thread()
|
@@ -114,7 +116,7 @@ class AsyncMediaProcessTrack(MediaStreamTrack, Generic[ProcessorT, FrameT]):
|
|
114
116
|
for tbline in tb.rstrip().splitlines():
|
115
117
|
logger.error(tbline.rstrip())
|
116
118
|
|
117
|
-
async def _fallback_recv_queued(self, frames: List[FrameT]) -> FrameT:
|
119
|
+
async def _fallback_recv_queued(self, frames: List[FrameT]) -> List[FrameT]:
|
118
120
|
"""
|
119
121
|
Used as a fallback when the processor does not have its own `recv_queued`.
|
120
122
|
"""
|
@@ -126,7 +128,7 @@ class AsyncMediaProcessTrack(MediaStreamTrack, Generic[ProcessorT, FrameT]):
|
|
126
128
|
if self.processor.recv:
|
127
129
|
return [self.processor.recv(frames[-1])]
|
128
130
|
|
129
|
-
return frames[-1]
|
131
|
+
return [frames[-1]]
|
130
132
|
|
131
133
|
def _worker_thread(self) -> None:
|
132
134
|
loop = asyncio.new_event_loop()
|
streamlit_webrtc/webrtc.py
CHANGED
@@ -93,7 +93,7 @@ async def _process_offer_coro(
|
|
93
93
|
|
94
94
|
if mode == WebRtcMode.SENDRECV:
|
95
95
|
|
96
|
-
@pc.on("track")
|
96
|
+
@pc.on("track") # type: ignore
|
97
97
|
def on_track(input_track: MediaStreamTrack):
|
98
98
|
logger.info("Track %s received", input_track.kind)
|
99
99
|
|
@@ -164,7 +164,7 @@ async def _process_offer_coro(
|
|
164
164
|
elif output_track.kind == "audio":
|
165
165
|
on_track_created("output:audio", output_track)
|
166
166
|
|
167
|
-
@input_track.on("ended")
|
167
|
+
@input_track.on("ended") # type: ignore
|
168
168
|
async def on_ended():
|
169
169
|
logger.info("Track %s ended", input_track.kind)
|
170
170
|
if in_recorder:
|
@@ -174,7 +174,7 @@ async def _process_offer_coro(
|
|
174
174
|
|
175
175
|
elif mode == WebRtcMode.SENDONLY:
|
176
176
|
|
177
|
-
@pc.on("track")
|
177
|
+
@pc.on("track") # type: ignore
|
178
178
|
def on_track(input_track: MediaStreamTrack):
|
179
179
|
logger.info("Track %s received", input_track.kind)
|
180
180
|
|
@@ -183,6 +183,8 @@ async def _process_offer_coro(
|
|
183
183
|
elif input_track.kind == "audio":
|
184
184
|
on_track_created("input:audio", input_track)
|
185
185
|
|
186
|
+
output_track: MediaStreamTrack
|
187
|
+
|
186
188
|
if input_track.kind == "audio":
|
187
189
|
if audio_receiver:
|
188
190
|
if audio_processor:
|
@@ -224,7 +226,7 @@ async def _process_offer_coro(
|
|
224
226
|
logger.info("Track %s is added to in_recorder", input_track.kind)
|
225
227
|
in_recorder.addTrack(relay.subscribe(input_track))
|
226
228
|
|
227
|
-
@input_track.on("ended")
|
229
|
+
@input_track.on("ended") # type: ignore
|
228
230
|
async def on_ended():
|
229
231
|
logger.info("Track %s ended", input_track.kind)
|
230
232
|
if video_receiver:
|
@@ -237,7 +239,7 @@ async def _process_offer_coro(
|
|
237
239
|
await pc.setRemoteDescription(offer)
|
238
240
|
if mode == WebRtcMode.RECVONLY:
|
239
241
|
for t in pc.getTransceivers():
|
240
|
-
output_track = None
|
242
|
+
output_track: Optional[MediaStreamTrack] = None
|
241
243
|
if t.kind == "audio":
|
242
244
|
if source_audio_track:
|
243
245
|
if audio_processor:
|
@@ -393,8 +395,8 @@ class WebRtcWorker(Generic[VideoProcessorT, AudioProcessorT]):
|
|
393
395
|
self._output_video_track: Optional[MediaStreamTrack] = None
|
394
396
|
self._output_audio_track: Optional[MediaStreamTrack] = None
|
395
397
|
self._player: Optional[MediaPlayer] = None
|
396
|
-
self._relayed_source_video_track: Optional[
|
397
|
-
self._relayed_source_audio_track: Optional[
|
398
|
+
self._relayed_source_video_track: Optional[MediaStreamTrack] = None
|
399
|
+
self._relayed_source_audio_track: Optional[MediaStreamTrack] = None
|
398
400
|
|
399
401
|
self._session_shutdown_observer = SessionShutdownObserver(self.stop)
|
400
402
|
|
@@ -510,7 +512,7 @@ class WebRtcWorker(Generic[VideoProcessorT, AudioProcessorT]):
|
|
510
512
|
)
|
511
513
|
source_video_track = self._relayed_source_video_track
|
512
514
|
|
513
|
-
@self.pc.on("iceconnectionstatechange")
|
515
|
+
@self.pc.on("iceconnectionstatechange") # type: ignore
|
514
516
|
async def on_iceconnectionstatechange():
|
515
517
|
logger.info("ICE connection state is %s", self.pc.iceConnectionState)
|
516
518
|
iceConnectionState = self.pc.iceConnectionState
|
@@ -1,22 +1,15 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: streamlit-webrtc
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.50.1
|
4
4
|
Summary: Real-time video and audio processing on Streamlit
|
5
|
-
License: MIT
|
6
|
-
Author: Yuichiro Tachibana (Tsuchiya)
|
7
|
-
Author-email: t.yic.yt@gmail.com
|
8
|
-
Requires-Python: >=3.9, !=2.7.*, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*, !=3.7.*, !=3.8.*
|
9
|
-
Classifier: License :: OSI Approved :: MIT License
|
10
|
-
Classifier: Programming Language :: Python :: 3
|
11
|
-
Classifier: Programming Language :: Python :: 3.9
|
12
|
-
Classifier: Programming Language :: Python :: 3.10
|
13
|
-
Classifier: Programming Language :: Python :: 3.11
|
14
|
-
Classifier: Programming Language :: Python :: 3.12
|
15
|
-
Classifier: Programming Language :: Python :: 3.13
|
16
|
-
Requires-Dist: aiortc (>=1.9.0,<2.0.0)
|
17
|
-
Requires-Dist: packaging (>=20.0)
|
18
|
-
Requires-Dist: streamlit (>=0.84.1)
|
19
5
|
Project-URL: Repository, https://github.com/whitphx/streamlit-webrtc
|
6
|
+
Author-email: "Yuichiro Tachibana (Tsuchiya)" <t.yic.yt@gmail.com>
|
7
|
+
License-Expression: MIT
|
8
|
+
License-File: LICENSE
|
9
|
+
Requires-Python: !=3.9.7,>=3.9
|
10
|
+
Requires-Dist: aiortc>=1.9.0
|
11
|
+
Requires-Dist: packaging>=20.0
|
12
|
+
Requires-Dist: streamlit>=0.84.1
|
20
13
|
Description-Content-Type: text/markdown
|
21
14
|
|
22
15
|
# streamlit-webrtc
|
@@ -353,4 +346,3 @@ See the samples in [app.py](./app.py) for their usage.
|
|
353
346
|
<a href="https://www.buymeacoffee.com/whitphx" target="_blank" rel="noreferrer"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" width="180" height="50" ></a>
|
354
347
|
|
355
348
|
[](https://github.com/sponsors/whitphx)
|
356
|
-
|
@@ -1,15 +1,13 @@
|
|
1
|
-
streamlit_webrtc/__init__.py,sha256=
|
1
|
+
streamlit_webrtc/__init__.py,sha256=gjCUIHnoLESCSGGwvRMzUHfr8NoVw70oJJtxk6iLVss,2302
|
2
2
|
streamlit_webrtc/_compat.py,sha256=43RUwFoGdRBq8EkOdf6inWkl-qogs5lF8tc-sYrVd1s,4128
|
3
3
|
streamlit_webrtc/component.py,sha256=cwjP0-4vJSUisSKKeOrc8V3AJvYabF22QxLNsKeO2oI,24181
|
4
4
|
streamlit_webrtc/components_callbacks.py,sha256=tdrj2TlV8qcexFEdjm4PVkz8JwHffo4A8imoXOtjNHA,2401
|
5
5
|
streamlit_webrtc/config.py,sha256=Yoppkv0aHFEpHp5IWPgy3tbohAsm-FGEtoH5Zbgwltw,4114
|
6
6
|
streamlit_webrtc/eventloop.py,sha256=AFmxGlRRxVdl0cTS9pKpRZR2Mnq6v6DgudVZZ425IVw,1333
|
7
|
-
streamlit_webrtc/factory.py,sha256=
|
8
|
-
streamlit_webrtc/
|
9
|
-
streamlit_webrtc/frontend/dist/index.html,sha256=uSdhb6tIzqNrr1g-L6GZ7wR2jOTKgzWQBBRGjlmIBqw,527
|
10
|
-
streamlit_webrtc/mix.py,sha256=Cd1a3bmJdCY_5Ita-bvW0Y5a0ydLHBANojBOqJ1Govo,8692
|
7
|
+
streamlit_webrtc/factory.py,sha256=T35kCwNU8Vm4KL6KJ5HbNVtaouNw13Ilew49XSnm6X8,6820
|
8
|
+
streamlit_webrtc/mix.py,sha256=Uh9gJviP3VLxwBQ-i3RftJ8GQ5qDPGqJKgm2M-vQYo4,8775
|
11
9
|
streamlit_webrtc/models.py,sha256=3sKVjCFgX-eQ7nlMHGWxzR9ohdstxUSZaiOHjyDAHVQ,6308
|
12
|
-
streamlit_webrtc/process.py,sha256=
|
10
|
+
streamlit_webrtc/process.py,sha256=i1bSoyIAccKCKmYGnrYLq0ElPFND_5SsY1u91NY-B48,7878
|
13
11
|
streamlit_webrtc/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
14
12
|
streamlit_webrtc/receive.py,sha256=gzv1uZDTAlxJ0hyJ5LweTr0lVuoisrH5xRevGnYkWwo,2922
|
15
13
|
streamlit_webrtc/relay.py,sha256=2K2h0uNz50ARUOScYZPYlEmZb0uAi0agaYxI9z3mLeI,896
|
@@ -17,8 +15,10 @@ streamlit_webrtc/server.py,sha256=5o9E2MRIPoS18lfGPJkyhbd23RWyTwblNVVuAA2kKrA,14
|
|
17
15
|
streamlit_webrtc/session_info.py,sha256=pg_2RFexR18UfwpZT5ENcPfedEiWuAxBuXc2RRuFCaE,2341
|
18
16
|
streamlit_webrtc/shutdown.py,sha256=rbWMhOIrbOBZDlqqBTzCfs4MuHQ0UEhljkNvvXoeTz0,2171
|
19
17
|
streamlit_webrtc/source.py,sha256=haPqMZ50Xh8tg7Z1yN8Frfk8v7D3oOuKteaD59asbzs,2263
|
20
|
-
streamlit_webrtc/webrtc.py,sha256=
|
21
|
-
streamlit_webrtc
|
22
|
-
streamlit_webrtc-
|
23
|
-
streamlit_webrtc-0.
|
24
|
-
streamlit_webrtc-0.
|
18
|
+
streamlit_webrtc/webrtc.py,sha256=uRJpQhDEk47tYKg14ljIhUXPgGWQbnEmZ_Uh4Jo8ePg,27228
|
19
|
+
streamlit_webrtc/frontend/dist/index.html,sha256=1iOx-PsDxdqWKzx6SFPeQH17DsdM8NJSkfOm-XjFl5Q,527
|
20
|
+
streamlit_webrtc/frontend/dist/assets/index-1ywg1u80.js,sha256=6DS5LiYVHfQac1QqoUOdw2ALfo86UUrpvdn4Cdz-GaY,582648
|
21
|
+
streamlit_webrtc-0.50.1.dist-info/METADATA,sha256=4CPxm-P-TqNZc3SPXlZH6zdFPa_Srh1RmMAEvbBl9qQ,18640
|
22
|
+
streamlit_webrtc-0.50.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
23
|
+
streamlit_webrtc-0.50.1.dist-info/licenses/LICENSE,sha256=pwccNHVA7r4rYofGlMU10aKEU90GLUlQr8uY80PR0NQ,1081
|
24
|
+
streamlit_webrtc-0.50.1.dist-info/RECORD,,
|
File without changes
|