streamlit-webrtc 0.49.4__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.
@@ -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",
@@ -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
- ) -> MediaStreamTrack:
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: int
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")(functools.partial(self.remove_input_proxy, input_proxy))
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
- latest_frames = [f for f in latest_frames if f is not None]
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":
@@ -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()
@@ -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[MediaRelay] = None
397
- self._relayed_source_audio_track: Optional[MediaRelay] = None
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.3
1
+ Metadata-Version: 2.4
2
2
  Name: streamlit-webrtc
3
- Version: 0.49.4
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
  [![GitHub Sponsors](https://img.shields.io/github/sponsors/whitphx?label=Sponsor%20me%20on%20GitHub%20Sponsors&style=social)](https://github.com/sponsors/whitphx)
356
-
@@ -1,15 +1,13 @@
1
- streamlit_webrtc/__init__.py,sha256=q5YB2Sx6amHctUMrZ76UqLgB5mrdR6ArRDK6eiKyKBo,2254
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=XeHefJ6d_aHE7Oy_oqz5dWsfVKuVxjtifilvs4cMBWs,6806
8
- streamlit_webrtc/frontend/dist/assets/index-1ywg1u80.js,sha256=6DS5LiYVHfQac1QqoUOdw2ALfo86UUrpvdn4Cdz-GaY,582648
9
- streamlit_webrtc/frontend/dist/index.html,sha256=1iOx-PsDxdqWKzx6SFPeQH17DsdM8NJSkfOm-XjFl5Q,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=rBxrWFVc-3RUaeOsx-34G5hj49Tl5acYNIn11s8YWqo,7826
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=grVZTC1wnL8ZpPO0vjABarvAzXzinTAWSSND-SdTAzc,27064
21
- streamlit_webrtc-0.49.4.dist-info/LICENSE,sha256=pwccNHVA7r4rYofGlMU10aKEU90GLUlQr8uY80PR0NQ,1081
22
- streamlit_webrtc-0.49.4.dist-info/METADATA,sha256=ksnZBxMGHj7LhqDxmA2Rfkk3_lO-jYyCbjg7FN7yQoE,19063
23
- streamlit_webrtc-0.49.4.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
24
- streamlit_webrtc-0.49.4.dist-info/RECORD,,
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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.1.1
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any