agora-python-server-sdk 2.4.6__tar.gz → 2.4.8__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.
Files changed (52) hide show
  1. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/PKG-INFO +34 -2
  2. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/README.md +33 -1
  3. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtc/_ctypes_handle/_ctypes_data.py +6 -3
  4. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtc/agora_base.py +2 -1
  5. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtc/local_video_track.py +2 -2
  6. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtc/rtc_connection.py +22 -2
  7. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtm/_ctypes_handle/_ctypes_data.py +2 -1
  8. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtm/rtm_client.py +13 -6
  9. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora_python_server_sdk.egg-info/PKG-INFO +34 -2
  10. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora_python_server_sdk.egg-info/SOURCES.txt +4 -1
  11. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/setup.py +1 -1
  12. agora_python_server_sdk-2.4.8/tests/test_agora_base.py +51 -0
  13. agora_python_server_sdk-2.4.8/tests/test_rtc_ctypes_data.py +97 -0
  14. agora_python_server_sdk-2.4.8/tests/test_rtm_ctypes_data.py +72 -0
  15. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/MANIFEST.in +0 -0
  16. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/__init__.py +0 -0
  17. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtc/__init__.py +0 -0
  18. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtc/_ctypes_handle/_audio_frame_observer.py +0 -0
  19. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtc/_ctypes_handle/_local_user_observer.py +0 -0
  20. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtc/_ctypes_handle/_rtc_connection_observer.py +0 -0
  21. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtc/_ctypes_handle/_video_encoded_frame_observer.py +0 -0
  22. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtc/_ctypes_handle/_video_frame_observer.py +0 -0
  23. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtc/_utils/globals.py +0 -0
  24. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtc/agora_parameter.py +0 -0
  25. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtc/agora_service.py +0 -0
  26. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtc/audio_encoded_frame_sender.py +0 -0
  27. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtc/audio_frame_observer.py +0 -0
  28. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtc/audio_pcm_data_sender.py +0 -0
  29. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtc/audio_sessionctrl.py +0 -0
  30. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtc/audio_vad_manager.py +0 -0
  31. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtc/external_audio_processor.py +0 -0
  32. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtc/local_audio_track.py +0 -0
  33. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtc/local_user.py +0 -0
  34. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtc/local_user_observer.py +0 -0
  35. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtc/media_node_factory.py +0 -0
  36. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtc/remote_audio_track.py +0 -0
  37. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtc/remote_video_track.py +0 -0
  38. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtc/rtc_connection_observer.py +0 -0
  39. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtc/utils/audio_consumer.py +0 -0
  40. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtc/utils/vad_dump.py +0 -0
  41. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtc/video_encoded_frame_observer.py +0 -0
  42. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtc/video_encoded_image_sender.py +0 -0
  43. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtc/video_frame_observer.py +0 -0
  44. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtc/video_frame_sender.py +0 -0
  45. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtc/voice_detection.py +0 -0
  46. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtm/__init__.py +0 -0
  47. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtm/rtm_base.py +0 -0
  48. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora/rtm/rtm_event_handler.py +0 -0
  49. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora_python_server_sdk.egg-info/dependency_links.txt +0 -0
  50. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/agora_python_server_sdk.egg-info/top_level.txt +0 -0
  51. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/pyproject.toml +0 -0
  52. {agora_python_server_sdk-2.4.6 → agora_python_server_sdk-2.4.8}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: agora_python_server_sdk
3
- Version: 2.4.6
3
+ Version: 2.4.8
4
4
  Summary: A Python SDK for Agora Server
5
5
  Home-page: https://github.com/AgoraIO-Extensions/Agora-Python-Server-SDK
6
6
  Classifier: Intended Audience :: Developers
@@ -63,7 +63,39 @@ export LD_LIBRARY_PATH=/site_packages/agora/agora_sdk/
63
63
  python agora_rtc/examples/example_audio_pcm_send.py --appId=xxx --channelId=xxx --userId=xxx --audioFile=./test_data/demo.pcm --sampleRate=16000 --numOfChannels=1
64
64
  ```
65
65
 
66
- # Change log
66
+ # Release Note
67
+
68
+ ## 2026.06.24 Release Version 2.4.8
69
+
70
+ - **Bugfixes**:
71
+ - Fixed an implementation error in `encryptionconfigure`.
72
+ - **New Features**:
73
+ - Added `ut test case`
74
+ - Usage:
75
+ cd agora_rtc
76
+ pip install pytest
77
+ PYTHONPATH=. pytest tests/ -v
78
+ or pytest tests/ -v
79
+
80
+ ## 2026.06.24 Release Version 2.4.7
81
+
82
+ - **New Features**:
83
+ - Added `RTCConnection.get_sid()` and `RTCConnection.set_simulcast_stream()` interfaces for retrieving SID and configuring Simulcast stream capabilities.
84
+ - In `EncryptionConfig`, added the `datastream_encryption_enabled` option to support data stream encryption.
85
+
86
+ - **Improvements and Enhancements**:
87
+ - Improved the Simulcast Stream configuration process to further simplify the usage of `LocalVideoTrack.enable_simulcast_stream()`.
88
+ - Adjusted audio encoding timestamp behavior: when `capture_time_ms` is not set, the current time will be used by default.
89
+ - Optimized RTC/RTM binary message processing to enhance raw data passthrough capability.
90
+ - Enhanced lifecycle management of RTM ctypes callback objects to improve overall stability.
91
+
92
+ - **Examples and Documentation**:
93
+ - Updated RTM/RTC example code, adding detailed examples for Simulcast and binary message transmission.
94
+ - Updated macOS SDK version records.
95
+
96
+ ## 2026.06.01 Release Version 2.4.6
97
+ - **Update**: Updated the RTC SDK to version 174, resolving the issue where two same UIDs could join the same channel.
98
+
67
99
  ## 2026.05.08 Release Version 2.4.5
68
100
  - **Update**: Update arm rtc sdk, which can run basic function on arm64 platform, the vad function is not supported yet.
69
101
 
@@ -48,7 +48,39 @@ export LD_LIBRARY_PATH=/site_packages/agora/agora_sdk/
48
48
  python agora_rtc/examples/example_audio_pcm_send.py --appId=xxx --channelId=xxx --userId=xxx --audioFile=./test_data/demo.pcm --sampleRate=16000 --numOfChannels=1
49
49
  ```
50
50
 
51
- # Change log
51
+ # Release Note
52
+
53
+ ## 2026.06.24 Release Version 2.4.8
54
+
55
+ - **Bugfixes**:
56
+ - Fixed an implementation error in `encryptionconfigure`.
57
+ - **New Features**:
58
+ - Added `ut test case`
59
+ - Usage:
60
+ cd agora_rtc
61
+ pip install pytest
62
+ PYTHONPATH=. pytest tests/ -v
63
+ or pytest tests/ -v
64
+
65
+ ## 2026.06.24 Release Version 2.4.7
66
+
67
+ - **New Features**:
68
+ - Added `RTCConnection.get_sid()` and `RTCConnection.set_simulcast_stream()` interfaces for retrieving SID and configuring Simulcast stream capabilities.
69
+ - In `EncryptionConfig`, added the `datastream_encryption_enabled` option to support data stream encryption.
70
+
71
+ - **Improvements and Enhancements**:
72
+ - Improved the Simulcast Stream configuration process to further simplify the usage of `LocalVideoTrack.enable_simulcast_stream()`.
73
+ - Adjusted audio encoding timestamp behavior: when `capture_time_ms` is not set, the current time will be used by default.
74
+ - Optimized RTC/RTM binary message processing to enhance raw data passthrough capability.
75
+ - Enhanced lifecycle management of RTM ctypes callback objects to improve overall stability.
76
+
77
+ - **Examples and Documentation**:
78
+ - Updated RTM/RTC example code, adding detailed examples for Simulcast and binary message transmission.
79
+ - Updated macOS SDK version records.
80
+
81
+ ## 2026.06.01 Release Version 2.4.6
82
+ - **Update**: Updated the RTC SDK to version 174, resolving the issue where two same UIDs could join the same channel.
83
+
52
84
  ## 2026.05.08 Release Version 2.4.5
53
85
  - **Update**: Update arm rtc sdk, which can run basic function on arm64 platform, the vad function is not supported yet.
54
86
 
@@ -1246,14 +1246,16 @@ class EncryptionConfigInner(ctypes.Structure):
1246
1246
  _fields_ = [
1247
1247
  ("encryption_mode", ctypes.c_int),
1248
1248
  ("encryption_key", ctypes.c_char_p),
1249
- ("encryption_kdf_salt", ctypes.c_uint8 * 32)
1249
+ ("encryption_kdf_salt", ctypes.c_uint8 * 32),
1250
+ ("datastream_encryption_enabled", ctypes.c_bool),
1250
1251
  ]
1251
1252
 
1252
1253
  def get(self):
1253
1254
  return EncryptionConfig(
1254
1255
  encryption_mode=self.encryption_mode,
1255
1256
  encryption_key=self.encryption_key.decode() if self.encryption_key else "",
1256
- encryption_kdf_salt=bytearray(bytes(self.encryption_kdf_salt))
1257
+ encryption_kdf_salt=bytearray(bytes(self.encryption_kdf_salt)),
1258
+ datastream_encryption_enabled=self.datastream_encryption_enabled
1257
1259
  )
1258
1260
 
1259
1261
  @staticmethod
@@ -1276,7 +1278,8 @@ class EncryptionConfigInner(ctypes.Structure):
1276
1278
  return EncryptionConfigInner(
1277
1279
  config.encryption_mode,
1278
1280
  encryption_key,
1279
- encryption_kdf_salt
1281
+ encryption_kdf_salt,
1282
+ config.datastream_encryption_enabled
1280
1283
  )
1281
1284
 
1282
1285
 
@@ -566,7 +566,7 @@ class EncodedAudioFrameInfo:
566
566
  samples_per_channel: int = 1024
567
567
  send_even_if_empty: int = 1
568
568
  number_of_channels: int = 1
569
- capture_time_ms: int = 0
569
+ capture_time_ms: int = -1 #-1 means not set, when push_audio_encoded_data, if capture_time_ms is not set, will use the current time
570
570
 
571
571
 
572
572
  @dataclass(kw_only=True)
@@ -701,6 +701,7 @@ class EncryptionConfig:
701
701
  encryption_mode: int
702
702
  encryption_key: str
703
703
  encryption_kdf_salt: bytearray = None
704
+ datastream_encryption_enabled: bool = False
704
705
  @dataclass(kw_only=True)
705
706
  class CapabilityItem:
706
707
  id: int
@@ -53,8 +53,8 @@ class LocalVideoTrack:
53
53
  ret = agora_local_video_track_set_video_encoder_config(self.track_handle, ctypes.byref(VideoEncoderConfigurationInner.create(config)))
54
54
  return ret
55
55
 
56
- def enable_simulcast_stream(self, enabled, config):
57
- ret = agora_local_video_track_enable_simulcast_stream(self.track_handle, enabled, ctypes.byref(config))
56
+ def enable_simulcast_stream(self, enabled, config: SimulcastStreamConfig):
57
+ ret = agora_local_video_track_enable_simulcast_stream(self.track_handle, enabled, ctypes.byref(SimulcastStreamConfigInner.create(config)))
58
58
  return ret
59
59
 
60
60
  # def update_simulcast_stream(self, enabled, config):
@@ -87,6 +87,11 @@ agora_local_audio_track_set_total_extra_send_ms = agora_lib.agora_local_audio_tr
87
87
  agora_local_audio_track_set_total_extra_send_ms.restype = AGORA_API_C_INT
88
88
  agora_local_audio_track_set_total_extra_send_ms.argtypes = [AGORA_HANDLE, ctypes.c_uint64]
89
89
 
90
+ #get sid
91
+ agora_rtc_conn_get_sid = agora_lib.agora_rtc_conn_get_sid
92
+ agora_rtc_conn_get_sid.restype = ctypes.c_char_p
93
+ agora_rtc_conn_get_sid.argtypes = [AGORA_HANDLE]
94
+
90
95
  #global variable
91
96
  _is_deliver_mute_data_has_set: bool = False
92
97
 
@@ -208,11 +213,13 @@ class RTCConnection:
208
213
  # send data stream message to connection
209
214
  def send_stream_message(self, data) -> int:
210
215
  length = len(data)
211
- c_data = ctypes.c_char_p(data)
216
+ #c_data = ctypes.c_char_p(data)
217
+ arr = (ctypes.c_char * length).from_buffer(data) # zero copy
218
+ ptr = ctypes.cast(arr, ctypes.POINTER(ctypes.c_char_p))
212
219
  ret = agora_rtc_conn_send_stream_message(
213
220
  self.conn_handle,
214
221
  self._data_stream_id,
215
- c_data,
222
+ ptr,
216
223
  length
217
224
  )
218
225
  return ret
@@ -631,4 +638,17 @@ class RTCConnection:
631
638
  ret = self.local_user._send_intra_request(remote_uid)
632
639
  return ret
633
640
  pass
641
+ def set_simulcast_stream(self, enabled: bool, config: SimulcastStreamConfig)->int:
642
+ ret = -1000
643
+ if self._video_track:
644
+ ret = self._video_track.enable_simulcast_stream(enabled, config)
645
+ return ret
646
+ pass
647
+ def get_sid(self)->str:
648
+ ret = -1000
649
+ if self.conn_handle is None:
650
+ return ""
651
+ sid = agora_rtc_conn_get_sid(self.conn_handle)
652
+ return sid.decode() if sid else ""
653
+ pass
634
654
 
@@ -182,12 +182,13 @@ class MessageEventInner(ctypes.Structure):
182
182
  ("customType", ctypes.c_char_p)
183
183
  ]
184
184
  def get(self) -> MessageEvent:
185
+ message = ctypes.string_at(self.message, self.messageLength) if self.message else b""
185
186
  return MessageEvent(
186
187
  channel_type=self.channelType,
187
188
  message_type=self.messageType,
188
189
  channel_name=self.channelName.decode('utf-8') if self.channelName else "",
189
190
  channel_topic=self.channelTopic.decode('utf-8') if self.channelTopic else "",
190
- message=self.message.decode('utf-8') if self.message else "",
191
+ message=message,
191
192
  message_length=self.messageLength,
192
193
  publisher=self.publisher.decode('utf-8') if self.publisher else "",
193
194
  custom_type=self.customType.decode('utf-8') if self.customType else "",
@@ -100,12 +100,13 @@ class RTMClient:
100
100
  #register event handler from python to ctypes
101
101
 
102
102
  config_inner = RtmConfigInner.create(config)
103
- c_event_handler = RtmEventHandlerInner(config.event_handler, self)
104
- config_inner.eventHandler = ctypes.cast(ctypes.byref(c_event_handler), ctypes.c_void_p)
103
+ self._event_handler = RtmEventHandlerInner(config.event_handler, self)
104
+ config_inner.eventHandler = ctypes.cast(ctypes.byref(self._event_handler), ctypes.c_void_p)
105
105
  self.client_handle = agora_rtm_client_create(ctypes.byref(config_inner), ctypes.byref(ret))
106
106
  print(f"create_rtm_client ret: {ret.value}, client_handle: {self.client_handle}")
107
107
  print(f"error reason: {self.get_error_reason(ret.value)}")
108
108
  self.is_valid = self.client_handle is not None and ret.value == 0
109
+ self._config_inner = config_inner
109
110
  def _is_valid(self)->bool:
110
111
  return self.is_valid
111
112
 
@@ -116,6 +117,8 @@ class RTMClient:
116
117
  if ret == 0:
117
118
  self.client_handle = None
118
119
  self.config = None
120
+ self._event_handler = None # release 之后再清
121
+ self._config_inner = None
119
122
  def login(self, token: str)->(int, int):
120
123
  #convert from str to c_char_p without memory copy
121
124
  bytes_data = token.encode('utf-8')
@@ -132,13 +135,17 @@ class RTMClient:
132
135
  request_id = ctypes.c_uint64(0)
133
136
  ret = agora_rtm_client_renew_token(self.client_handle, token.encode(), ctypes.byref(request_id))
134
137
  return ret, int(request_id.value)
135
- def publish(self, channel_name: str, message: str, options: PublishOptions) ->(int, uint64_t):
138
+ def publish(self, channel_name: str, message, options: PublishOptions) ->(int, uint64_t):
136
139
  inner_options = PublishOptionsInner.create(options)
137
140
  request_id = ctypes.c_uint64(0)
138
- ret = agora_rtm_client_publish(self.client_handle, channel_name.encode(), message.encode(), len(message), ctypes.byref(inner_options), ctypes.byref(request_id))
141
+ #change msg to c_char_p without memory copy
142
+ length = len(message)
143
+ arr = (ctypes.c_char * length).from_buffer_copy(message)
144
+ ptr = ctypes.cast(arr, ctypes.c_char_p)
145
+ ret = agora_rtm_client_publish(self.client_handle, channel_name.encode(), ptr, length, ctypes.byref(inner_options), ctypes.byref(request_id))
139
146
  return ret, int(request_id.value)
140
147
 
141
- def send_channel_message(self, channel_name: str, message: str) ->(int, uint64_t):
148
+ def send_channel_message(self, channel_name: str, message) ->(int, uint64_t):
142
149
  publish_options = PublishOptions(
143
150
  channel_type=RtmChannelType.RTM_CHANNEL_TYPE_MESSAGE,
144
151
  message_type=RtmMessageType.RTM_MESSAGE_TYPE_BINARY,
@@ -147,7 +154,7 @@ class RTMClient:
147
154
  )
148
155
  ret, request_id = self.publish(channel_name, message, publish_options)
149
156
  return ret, request_id
150
- def send_user_message(self, user_id: str, message: str) ->(int, uint64_t):
157
+ def send_user_message(self, user_id: str, message) ->(int, uint64_t):
151
158
  publish_options = PublishOptions(
152
159
  channel_type=RtmChannelType.RTM_CHANNEL_TYPE_USER,
153
160
  message_type=RtmMessageType.RTM_MESSAGE_TYPE_BINARY,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: agora_python_server_sdk
3
- Version: 2.4.6
3
+ Version: 2.4.8
4
4
  Summary: A Python SDK for Agora Server
5
5
  Home-page: https://github.com/AgoraIO-Extensions/Agora-Python-Server-SDK
6
6
  Classifier: Intended Audience :: Developers
@@ -63,7 +63,39 @@ export LD_LIBRARY_PATH=/site_packages/agora/agora_sdk/
63
63
  python agora_rtc/examples/example_audio_pcm_send.py --appId=xxx --channelId=xxx --userId=xxx --audioFile=./test_data/demo.pcm --sampleRate=16000 --numOfChannels=1
64
64
  ```
65
65
 
66
- # Change log
66
+ # Release Note
67
+
68
+ ## 2026.06.24 Release Version 2.4.8
69
+
70
+ - **Bugfixes**:
71
+ - Fixed an implementation error in `encryptionconfigure`.
72
+ - **New Features**:
73
+ - Added `ut test case`
74
+ - Usage:
75
+ cd agora_rtc
76
+ pip install pytest
77
+ PYTHONPATH=. pytest tests/ -v
78
+ or pytest tests/ -v
79
+
80
+ ## 2026.06.24 Release Version 2.4.7
81
+
82
+ - **New Features**:
83
+ - Added `RTCConnection.get_sid()` and `RTCConnection.set_simulcast_stream()` interfaces for retrieving SID and configuring Simulcast stream capabilities.
84
+ - In `EncryptionConfig`, added the `datastream_encryption_enabled` option to support data stream encryption.
85
+
86
+ - **Improvements and Enhancements**:
87
+ - Improved the Simulcast Stream configuration process to further simplify the usage of `LocalVideoTrack.enable_simulcast_stream()`.
88
+ - Adjusted audio encoding timestamp behavior: when `capture_time_ms` is not set, the current time will be used by default.
89
+ - Optimized RTC/RTM binary message processing to enhance raw data passthrough capability.
90
+ - Enhanced lifecycle management of RTM ctypes callback objects to improve overall stability.
91
+
92
+ - **Examples and Documentation**:
93
+ - Updated RTM/RTC example code, adding detailed examples for Simulcast and binary message transmission.
94
+ - Updated macOS SDK version records.
95
+
96
+ ## 2026.06.01 Release Version 2.4.6
97
+ - **Update**: Updated the RTC SDK to version 174, resolving the issue where two same UIDs could join the same channel.
98
+
67
99
  ## 2026.05.08 Release Version 2.4.5
68
100
  - **Update**: Update arm rtc sdk, which can run basic function on arm64 platform, the vad function is not supported yet.
69
101
 
@@ -44,4 +44,7 @@ agora/rtm/_ctypes_handle/_ctypes_data.py
44
44
  agora_python_server_sdk.egg-info/PKG-INFO
45
45
  agora_python_server_sdk.egg-info/SOURCES.txt
46
46
  agora_python_server_sdk.egg-info/dependency_links.txt
47
- agora_python_server_sdk.egg-info/top_level.txt
47
+ agora_python_server_sdk.egg-info/top_level.txt
48
+ tests/test_agora_base.py
49
+ tests/test_rtc_ctypes_data.py
50
+ tests/test_rtm_ctypes_data.py
@@ -134,7 +134,7 @@ class CustomInstallCommand(install):
134
134
 
135
135
  setup(
136
136
  name='agora_python_server_sdk',
137
- version='2.4.6',
137
+ version='2.4.8',
138
138
  description='A Python SDK for Agora Server',
139
139
  long_description=open('README.md').read(),
140
140
  long_description_content_type='text/markdown',
@@ -0,0 +1,51 @@
1
+ """
2
+ author: Wei
3
+ date: 2026-07-01
4
+ """
5
+
6
+ from agora.rtc.agora_base import (
7
+ AreaCode,
8
+ ChannelProfileType,
9
+ ClientRoleType,
10
+ RTCConnInfo,
11
+ VideoCodecType,
12
+ APMConfig,
13
+ )
14
+
15
+
16
+ def test_channel_profile_enum():
17
+ assert ChannelProfileType.CHANNEL_PROFILE_COMMUNICATION == 0
18
+ assert ChannelProfileType.CHANNEL_PROFILE_LIVE_BROADCASTING == 1
19
+
20
+
21
+ def test_client_role_enum():
22
+ assert ClientRoleType.CLIENT_ROLE_BROADCASTER == 1
23
+ assert ClientRoleType.CLIENT_ROLE_AUDIENCE == 2
24
+
25
+
26
+ def test_video_codec_enum():
27
+ assert VideoCodecType.VIDEO_CODEC_H264 == 2
28
+ assert VideoCodecType.VIDEO_CODEC_AV1 == 12
29
+
30
+
31
+ def test_area_code_enum():
32
+ assert AreaCode.AREA_CODE_GLOB == 0xFFFFFFFF
33
+
34
+
35
+ def test_rtc_conn_info_dataclass():
36
+ info = RTCConnInfo(
37
+ id=1,
38
+ channel_id="test_channel",
39
+ state=2,
40
+ local_user_id="user_1",
41
+ internal_uid=10086,
42
+ )
43
+ assert info.channel_id == "test_channel"
44
+ assert info.internal_uid == 10086
45
+
46
+
47
+ def test_apm_config_to_json_string():
48
+ config = APMConfig()
49
+ json_str = config._to_json_string()
50
+ assert '"enabled":false' in json_str
51
+ assert '"ainsModelPref":10' in json_str
@@ -0,0 +1,97 @@
1
+ """
2
+ author: Wei
3
+ date: 2026-07-01
4
+ """
5
+
6
+ from agora.rtc.agora_base import (
7
+ AudioEncoderConfiguration,
8
+ AudioProfileType,
9
+ AudioSubscriptionOptions,
10
+ EncryptionConfig,
11
+ EncodedVideoFrameInfo,
12
+ RTCConnInfo,
13
+ VideoCodecType,
14
+ VideoDimensions,
15
+ )
16
+ from agora.rtc._ctypes_handle._ctypes_data import (
17
+ AudioEncoderConfigurationInner,
18
+ AudioSubscriptionOptionsInner,
19
+ EncryptionConfigInner,
20
+ EncodedVideoFrameInfoInner,
21
+ RTCConnInfoInner,
22
+ VideoDimensionsInner,
23
+ )
24
+
25
+
26
+ def test_audio_encoder_configuration_inner_create():
27
+ config = AudioEncoderConfiguration(audioProfile=AudioProfileType.AUDIO_PROFILE_MUSIC_STANDARD)
28
+ inner = AudioEncoderConfigurationInner.create(config)
29
+ assert inner.audioProfile == AudioProfileType.AUDIO_PROFILE_MUSIC_STANDARD
30
+
31
+
32
+ def test_rtc_conn_info_inner_create_and_get():
33
+ info = RTCConnInfo(
34
+ id=1,
35
+ channel_id="channel_a",
36
+ state=2,
37
+ local_user_id="user_1",
38
+ internal_uid=10086,
39
+ )
40
+ inner = RTCConnInfoInner.create(info)
41
+ assert inner.channel_id == b"channel_a"
42
+ assert inner.local_user_id == b"user_1"
43
+
44
+ restored = inner.get()
45
+ assert restored.channel_id == "channel_a"
46
+ assert restored.local_user_id == "user_1"
47
+ assert restored.internal_uid == 10086
48
+
49
+
50
+ def test_audio_subscription_options_inner_create_and_get():
51
+ options = AudioSubscriptionOptions(
52
+ pcm_data_only=1,
53
+ bytes_per_sample=2,
54
+ number_of_channels=2,
55
+ sample_rate_hz=48000,
56
+ )
57
+ inner = AudioSubscriptionOptionsInner.create(options)
58
+ assert inner.pcm_data_only == 1
59
+ assert inner.sample_rate_hz == 48000
60
+
61
+ restored = inner.get()
62
+ assert restored.number_of_channels == 2
63
+ assert restored.sample_rate_hz == 48000
64
+
65
+
66
+ def test_encryption_config_inner_create_and_get():
67
+ config = EncryptionConfig(
68
+ encryption_mode=1,
69
+ encryption_key="secret",
70
+ encryption_kdf_salt=bytearray(b"salt"),
71
+ datastream_encryption_enabled=True,
72
+ )
73
+ inner = EncryptionConfigInner.create(config)
74
+ assert inner.encryption_key == b"secret"
75
+ assert inner.encryption_kdf_salt[0] == ord("s")
76
+
77
+ restored = inner.get()
78
+ assert restored.encryption_key == "secret"
79
+ assert restored.datastream_encryption_enabled is True
80
+
81
+
82
+ def test_video_dimensions_and_encoded_frame_info_inner():
83
+ dimensions = VideoDimensions(width=1280, height=720)
84
+ dim_inner = VideoDimensionsInner.create(dimensions)
85
+ assert dim_inner.width == 1280
86
+ assert dim_inner.height == 720
87
+ assert dim_inner.get().height == 720
88
+
89
+ info = EncodedVideoFrameInfo(
90
+ codec_type=VideoCodecType.VIDEO_CODEC_H264,
91
+ width=1280,
92
+ height=720,
93
+ frames_per_second=30,
94
+ )
95
+ info_inner = EncodedVideoFrameInfoInner.create(info)
96
+ assert info_inner.codec_type == VideoCodecType.VIDEO_CODEC_H264
97
+ assert info_inner.get().frames_per_second == 30
@@ -0,0 +1,72 @@
1
+ """
2
+ author: Wei
3
+ date: 2026-07-01
4
+ """
5
+
6
+ import ctypes
7
+
8
+ from agora.rtm.rtm_base import (
9
+ PublishOptions,
10
+ RtmEncryptionConfig,
11
+ RtmLogConfig,
12
+ RtmLogLevel,
13
+ SubscribeOptions,
14
+ )
15
+ from agora.rtm._ctypes_handle._ctypes_data import (
16
+ MessageEventInner,
17
+ PublishOptionsInner,
18
+ RtmEncryptionConfigInner,
19
+ RtmLogConfigInner,
20
+ SubscribeOptionsInner,
21
+ )
22
+
23
+
24
+ def test_subscribe_options_inner_create():
25
+ options = SubscribeOptions(with_message=False, with_presence=True)
26
+ inner = SubscribeOptionsInner.create(options)
27
+ assert inner.withMessage is False
28
+ assert inner.withPresence is True
29
+
30
+
31
+ def test_rtm_encryption_config_inner_create():
32
+ config = RtmEncryptionConfig(encryption_key="secret", encryption_salt="salt")
33
+ inner = RtmEncryptionConfigInner.create(config)
34
+ assert inner.encryptionKey == b"secret"
35
+ assert inner.encryptionSalt[0] == ord("s")
36
+
37
+
38
+ def test_rtm_log_config_inner_create():
39
+ config = RtmLogConfig(
40
+ file_path="/tmp/rtm.log",
41
+ file_size_kb=1024,
42
+ log_level=RtmLogLevel.RTM_LOG_LEVEL_INFO,
43
+ )
44
+ inner = RtmLogConfigInner.create(config)
45
+ assert inner.filePath == b"/tmp/rtm.log"
46
+ assert inner.fileSizeInKB == 1024
47
+ assert inner.level == RtmLogLevel.RTM_LOG_LEVEL_INFO
48
+
49
+
50
+ def test_publish_options_inner_create():
51
+ options = PublishOptions(custom_type="demo", store_in_history=True)
52
+ inner = PublishOptionsInner.create(options)
53
+ assert inner.customType == b"demo"
54
+ assert inner.storeInHistory is True
55
+
56
+
57
+ def test_message_event_inner_get():
58
+ payload = b"hello"
59
+ inner = MessageEventInner(
60
+ channelType=1,
61
+ messageType=0,
62
+ channelName=b"channel_a",
63
+ channelTopic=b"topic_a",
64
+ message=ctypes.c_char_p(payload),
65
+ messageLength=len(payload),
66
+ publisher=b"user_1",
67
+ customType=b"text",
68
+ )
69
+ event = inner.get()
70
+ assert event.channel_name == "channel_a"
71
+ assert event.message == payload
72
+ assert event.publisher == "user_1"