streamlit-webrtc 0.51.2__py3-none-any.whl → 0.52.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.
@@ -7,7 +7,6 @@ except ModuleNotFoundError:
7
7
  import importlib_metadata # type: ignore
8
8
 
9
9
  from .component import (
10
- ClientSettings,
11
10
  WebRtcStreamerContext,
12
11
  WebRtcStreamerState,
13
12
  webrtc_streamer,
@@ -58,7 +57,6 @@ VideoTransformerFactory = VideoProcessorFactory
58
57
 
59
58
  __all__ = [
60
59
  "webrtc_streamer",
61
- "ClientSettings",
62
60
  "AudioProcessorBase",
63
61
  "AudioProcessorFactory",
64
62
  "AudioReceiver",
@@ -1,4 +1,3 @@
1
- import copy
2
1
  import json
3
2
  import logging
4
3
  import os
@@ -11,7 +10,6 @@ from typing import (
11
10
  Generic,
12
11
  NamedTuple,
13
12
  Optional,
14
- TypedDict,
15
13
  Union,
16
14
  cast,
17
15
  overload,
@@ -19,6 +17,7 @@ from typing import (
19
17
 
20
18
  import streamlit as st
21
19
  import streamlit.components.v1 as components
20
+ from aiortc import RTCConfiguration
22
21
  from aiortc.mediastreams import MediaStreamTrack
23
22
 
24
23
  from streamlit_webrtc.models import (
@@ -37,11 +36,14 @@ from .config import (
37
36
  DEFAULT_VIDEO_HTML_ATTRS,
38
37
  AudioHTMLAttributes,
39
38
  MediaStreamConstraints,
40
- RTCConfiguration,
41
39
  Translations,
42
40
  VideoHTMLAttributes,
41
+ compile_ice_servers,
42
+ compile_rtc_configuration,
43
+ )
44
+ from .credentials import (
45
+ get_available_ice_servers,
43
46
  )
44
- from .credentials import get_hf_ice_servers, get_twilio_ice_servers
45
47
  from .session_info import get_script_run_count, get_this_session_info
46
48
  from .webrtc import (
47
49
  AudioProcessorFactory,
@@ -72,11 +74,6 @@ else:
72
74
  _component_func = components.declare_component("webrtc_streamer", path=build_dir)
73
75
 
74
76
 
75
- class ClientSettings(TypedDict, total=False):
76
- rtc_configuration: RTCConfiguration
77
- media_stream_constraints: MediaStreamConstraints
78
-
79
-
80
77
  class WebRtcStreamerState(NamedTuple):
81
78
  playing: bool
82
79
  signalling: bool
@@ -93,7 +90,6 @@ class WebRtcStreamerContext(Generic[VideoProcessorT, AudioProcessorT]):
93
90
  _worker_ref: "Optional[weakref.ReferenceType[WebRtcWorker[VideoProcessorT, AudioProcessorT]]]" # noqa
94
91
 
95
92
  _component_value_snapshot: Union[ComponentValueSnapshot, None]
96
- _rtc_configuration: Optional[Union[Dict[str, Any], RTCConfiguration]]
97
93
 
98
94
  def __init__(
99
95
  self,
@@ -103,7 +99,6 @@ class WebRtcStreamerContext(Generic[VideoProcessorT, AudioProcessorT]):
103
99
  self._set_worker(worker)
104
100
  self._set_state(state)
105
101
  self._component_value_snapshot = None
106
- self._rtc_configuration = None
107
102
 
108
103
  def _set_worker(
109
104
  self, worker: Optional[WebRtcWorker[VideoProcessorT, AudioProcessorT]]
@@ -246,7 +241,6 @@ def webrtc_streamer(
246
241
  translations: Optional[Translations] = None,
247
242
  on_change: Optional[Callable] = None,
248
243
  # Deprecated. Just for backward compatibility
249
- client_settings: Optional[Union[ClientSettings, Dict]] = None,
250
244
  video_transformer_factory: None = None,
251
245
  async_transform: Optional[bool] = None,
252
246
  ) -> WebRtcStreamerContext:
@@ -287,7 +281,6 @@ def webrtc_streamer(
287
281
  translations: Optional[Translations] = None,
288
282
  on_change: Optional[Callable] = None,
289
283
  # Deprecated. Just for backward compatibility
290
- client_settings: Optional[Union[ClientSettings, Dict]] = None,
291
284
  video_transformer_factory: None = None,
292
285
  async_transform: Optional[bool] = None,
293
286
  ) -> WebRtcStreamerContext[VideoProcessorT, Any]:
@@ -324,7 +317,6 @@ def webrtc_streamer(
324
317
  translations: Optional[Translations] = None,
325
318
  on_change: Optional[Callable] = None,
326
319
  # Deprecated. Just for backward compatibility
327
- client_settings: Optional[Union[ClientSettings, Dict]] = None,
328
320
  video_transformer_factory: None = None,
329
321
  async_transform: Optional[bool] = None,
330
322
  ) -> WebRtcStreamerContext[Any, AudioProcessorT]:
@@ -361,7 +353,6 @@ def webrtc_streamer(
361
353
  translations: Optional[Translations] = None,
362
354
  on_change: Optional[Callable] = None,
363
355
  # Deprecated. Just for backward compatibility
364
- client_settings: Optional[Union[ClientSettings, Dict]] = None,
365
356
  video_transformer_factory: None = None,
366
357
  async_transform: Optional[bool] = None,
367
358
  ) -> WebRtcStreamerContext[VideoProcessorT, AudioProcessorT]:
@@ -397,7 +388,6 @@ def webrtc_streamer(
397
388
  translations: Optional[Translations] = None,
398
389
  on_change: Optional[Callable] = None,
399
390
  # Deprecated. Just for backward compatibility
400
- client_settings: Optional[Union[ClientSettings, Dict]] = None,
401
391
  video_transformer_factory=None,
402
392
  async_transform: Optional[bool] = None,
403
393
  ) -> WebRtcStreamerContext[VideoProcessorT, AudioProcessorT]:
@@ -420,19 +410,6 @@ def webrtc_streamer(
420
410
  stacklevel=2,
421
411
  )
422
412
  async_processing = async_transform
423
- if client_settings is not None:
424
- warnings.warn(
425
- "The argument client_settings is deprecated. "
426
- "Use rtc_configuration and media_stream_constraints instead.",
427
- DeprecationWarning,
428
- stacklevel=2,
429
- )
430
- rtc_configuration = (
431
- client_settings.get("rtc_configuration") if client_settings else None
432
- )
433
- media_stream_constraints = (
434
- client_settings.get("media_stream_constraints") if client_settings else None
435
- )
436
413
 
437
414
  if media_stream_constraints is None:
438
415
  media_stream_constraints = DEFAULT_MEDIA_STREAM_CONSTRAINTS
@@ -460,49 +437,6 @@ def webrtc_streamer(
460
437
  )
461
438
  st.session_state[key] = context
462
439
 
463
- if context._rtc_configuration is None:
464
- context._rtc_configuration = copy.deepcopy(rtc_configuration)
465
- if context._rtc_configuration is None or (
466
- isinstance(context._rtc_configuration, dict)
467
- and context._rtc_configuration.get("iceServers") is None
468
- ):
469
- LOGGER.info(
470
- "rtc_configuration.iceServers is not set. Try to set it automatically."
471
- )
472
- if hf_token := os.getenv("HF_TOKEN"):
473
- LOGGER.info("Try to use TURN server from Hugging Face.")
474
- try:
475
- ice_servers = get_hf_ice_servers(hf_token)
476
- if context._rtc_configuration is None:
477
- context._rtc_configuration = {}
478
- LOGGER.info("Successfully got TURN credentials from Hugging Face.")
479
- context._rtc_configuration["iceServers"] = ice_servers
480
- except Exception as e:
481
- LOGGER.error("Failed to get TURN credentials from Hugging Face: %s", e)
482
- elif os.getenv("TWILIO_ACCOUNT_SID") and os.getenv("TWILIO_AUTH_TOKEN"):
483
- LOGGER.info("Try to use TURN server from Twilio.")
484
- twilio_sid = os.getenv("TWILIO_ACCOUNT_SID")
485
- twilio_token = os.getenv("TWILIO_AUTH_TOKEN")
486
- try:
487
- ice_servers = get_twilio_ice_servers(twilio_sid, twilio_token)
488
- if context._rtc_configuration is None:
489
- context._rtc_configuration = {}
490
- LOGGER.info("Successfully got TURN credentials from Twilio.")
491
- context._rtc_configuration["iceServers"] = ice_servers
492
- except Exception as e:
493
- LOGGER.error("Failed to get TURN credentials from Twilio: %s", e)
494
- else:
495
- LOGGER.info("Use STUN server from Google.")
496
- # TODO: Check network reachability and unset ice_servers if failed
497
- ice_servers = [{"urls": "stun:stun.l.google.com:19302"}]
498
- if ice_servers:
499
- if context._rtc_configuration is None:
500
- context._rtc_configuration = {}
501
- LOGGER.info("Successfully got STUN server from Google.")
502
- context._rtc_configuration["iceServers"] = [
503
- {"urls": "stun:stun.l.google.com:19302"}
504
- ]
505
-
506
440
  webrtc_worker = context._get_worker()
507
441
 
508
442
  sdp_answer_json = None
@@ -539,8 +473,6 @@ def webrtc_streamer(
539
473
  key=frontend_key,
540
474
  sdp_answer_json=sdp_answer_json,
541
475
  mode=mode.name,
542
- settings=client_settings,
543
- rtc_configuration=context._rtc_configuration,
544
476
  media_stream_constraints=media_stream_constraints,
545
477
  video_html_attrs=video_html_attrs,
546
478
  audio_html_attrs=audio_html_attrs,
@@ -597,8 +529,6 @@ def webrtc_streamer(
597
529
  context._set_worker(None)
598
530
  webrtc_worker = None
599
531
 
600
- context._rtc_configuration = None
601
-
602
532
  # Rerun to unset the SDP answer from the frontend args
603
533
  rerun()
604
534
 
@@ -622,8 +552,23 @@ def webrtc_streamer(
622
552
  'Create a new worker (key="%s").',
623
553
  key,
624
554
  )
555
+
556
+ aiortc_rtc_configuration = (
557
+ compile_rtc_configuration(rtc_configuration)
558
+ if rtc_configuration and isinstance(rtc_configuration, dict)
559
+ else RTCConfiguration()
560
+ )
561
+
562
+ if aiortc_rtc_configuration.iceServers is None:
563
+ LOGGER.info(
564
+ "rtc_configuration.iceServers is not set. Try to set it automatically."
565
+ )
566
+ ice_servers = get_available_ice_servers()
567
+ aiortc_rtc_configuration.iceServers = compile_ice_servers(ice_servers)
568
+
625
569
  webrtc_worker = WebRtcWorker(
626
570
  mode=mode,
571
+ rtc_configuration=aiortc_rtc_configuration,
627
572
  player_factory=player_factory,
628
573
  in_recorder_factory=in_recorder_factory,
629
574
  out_recorder_factory=out_recorder_factory,
@@ -1,4 +1,11 @@
1
- from typing import Dict, List, Optional, TypedDict, Union
1
+ from typing import Any, Dict, List, Optional, TypedDict, Union
2
+
3
+ from aiortc import (
4
+ RTCConfiguration as AiortcRTCConfiguration,
5
+ )
6
+ from aiortc import (
7
+ RTCIceServer as AiortcRTCIceServer,
8
+ )
2
9
 
3
10
  RTCIceServer = TypedDict(
4
11
  "RTCIceServer",
@@ -15,6 +22,44 @@ class RTCConfiguration(TypedDict, total=False):
15
22
  iceServers: Optional[List[RTCIceServer]]
16
23
 
17
24
 
25
+ def compile_rtc_ice_server(
26
+ ice_server: Union[RTCIceServer, dict[str, Any]],
27
+ ) -> AiortcRTCIceServer:
28
+ if not isinstance(ice_server, dict):
29
+ raise ValueError("ice_server must be a dict")
30
+ if "urls" not in ice_server:
31
+ raise ValueError("ice_server must have a urls key")
32
+
33
+ return AiortcRTCIceServer(
34
+ urls=ice_server["urls"], # type: ignore # aiortc's type def is incorrect
35
+ username=ice_server.get("username"),
36
+ credential=ice_server.get("credential"),
37
+ )
38
+
39
+
40
+ def compile_ice_servers(
41
+ ice_servers: Union[List[RTCIceServer], List[dict[str, Any]]],
42
+ ) -> List[AiortcRTCIceServer]:
43
+ return [
44
+ compile_rtc_ice_server(server)
45
+ for server in ice_servers
46
+ if isinstance(server, dict) and "urls" in server
47
+ ]
48
+
49
+
50
+ def compile_rtc_configuration(
51
+ rtc_configuration: Union[RTCConfiguration, dict[str, Any]],
52
+ ) -> AiortcRTCConfiguration:
53
+ if not isinstance(rtc_configuration, dict):
54
+ raise ValueError("rtc_configuration must be a dict")
55
+ ice_servers = rtc_configuration.get("iceServers", [])
56
+ if not isinstance(ice_servers, list):
57
+ raise ValueError("iceServers must be a list")
58
+ return AiortcRTCConfiguration(
59
+ iceServers=compile_ice_servers(ice_servers),
60
+ )
61
+
62
+
18
63
  Number = Union[int, float]
19
64
 
20
65
 
@@ -24,6 +24,7 @@ SOFTWARE.
24
24
  # Original: https://github.com/freddyaboulton/fastrtc/blob/66f0a81b76684c5d58761464fb67642891066f93/LICENSE
25
25
 
26
26
  import json
27
+ import logging
27
28
  import os
28
29
  import urllib.error
29
30
  import urllib.request
@@ -31,6 +32,8 @@ from typing import List, Optional
31
32
 
32
33
  from .config import RTCIceServer
33
34
 
35
+ LOGGER = logging.getLogger(__name__)
36
+
34
37
 
35
38
  def get_hf_ice_servers(token: Optional[str] = None) -> List[RTCIceServer]:
36
39
  if token is None:
@@ -67,11 +70,35 @@ def get_twilio_ice_servers(
67
70
  raise ImportError("Please install twilio with `pip install twilio`")
68
71
 
69
72
  if not twilio_sid and not twilio_token:
70
- twilio_sid = os.environ.get("TWILIO_ACCOUNT_SID")
71
- twilio_token = os.environ.get("TWILIO_AUTH_TOKEN")
73
+ twilio_sid = os.getenv("TWILIO_ACCOUNT_SID")
74
+ twilio_token = os.getenv("TWILIO_AUTH_TOKEN")
75
+
76
+ if twilio_sid is None or twilio_token is None:
77
+ raise ValueError("TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN must be set")
72
78
 
73
79
  client = Client(twilio_sid, twilio_token)
74
80
 
75
81
  token = client.tokens.create()
76
82
 
77
83
  return token.ice_servers
84
+
85
+
86
+ def get_available_ice_servers() -> List[RTCIceServer]:
87
+ try:
88
+ LOGGER.info("Try to use TURN server from Hugging Face.")
89
+ ice_servers = get_hf_ice_servers()
90
+ LOGGER.info("Successfully got TURN credentials from Hugging Face.")
91
+ return ice_servers
92
+ except Exception as e:
93
+ LOGGER.info("Failed to get TURN credentials from Hugging Face: %s", e)
94
+
95
+ try:
96
+ LOGGER.info("Try to use TURN server from Twilio.")
97
+ ice_servers = get_twilio_ice_servers()
98
+ LOGGER.info("Successfully got TURN credentials from Twilio.")
99
+ return ice_servers
100
+ except Exception as e:
101
+ LOGGER.info("Failed to get TURN credentials from Twilio: %s", e)
102
+
103
+ LOGGER.info("Use STUN server from Google.")
104
+ return [RTCIceServer(urls="stun:stun.l.google.com:19302")]