py-tgcalls 2.0.5__py3-none-any.whl → 2.1.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.
Files changed (65) hide show
  1. {py_tgcalls-2.0.5.dist-info → py_tgcalls-2.1.0.dist-info}/METADATA +11 -16
  2. py_tgcalls-2.1.0.dist-info/RECORD +106 -0
  3. {py_tgcalls-2.0.5.dist-info → py_tgcalls-2.1.0.dist-info}/WHEEL +1 -1
  4. pytgcalls/__init__.py +2 -0
  5. pytgcalls/__version__.py +1 -1
  6. pytgcalls/exceptions.py +7 -24
  7. pytgcalls/ffmpeg.py +2 -2
  8. pytgcalls/filters.py +52 -7
  9. pytgcalls/handlers/handlers_holder.py +1 -1
  10. pytgcalls/media_devices/__init__.py +6 -2
  11. pytgcalls/media_devices/device_info.py +8 -15
  12. pytgcalls/media_devices/input_device.py +11 -0
  13. pytgcalls/media_devices/media_devices.py +41 -92
  14. pytgcalls/media_devices/screen_device.py +10 -0
  15. pytgcalls/media_devices/speaker_device.py +10 -0
  16. pytgcalls/methods/calls/change_volume_call.py +3 -0
  17. pytgcalls/methods/calls/get_participants.py +5 -1
  18. pytgcalls/methods/calls/leave_call.py +3 -1
  19. pytgcalls/methods/stream/__init__.py +14 -10
  20. pytgcalls/methods/stream/{mute_stream.py → mute.py} +2 -2
  21. pytgcalls/methods/stream/{pause_stream.py → pause.py} +2 -2
  22. pytgcalls/methods/stream/play.py +27 -21
  23. pytgcalls/methods/stream/record.py +49 -0
  24. pytgcalls/methods/stream/{resume_stream.py → resume.py} +2 -2
  25. pytgcalls/methods/stream/send_frame.py +38 -0
  26. pytgcalls/methods/stream/{played_time.py → time.py} +5 -3
  27. pytgcalls/methods/stream/{unmute_stream.py → unmute.py} +2 -2
  28. pytgcalls/methods/utilities/__init__.py +6 -0
  29. pytgcalls/methods/utilities/call_holder.py +5 -2
  30. pytgcalls/methods/utilities/join_presentation.py +50 -0
  31. pytgcalls/methods/utilities/log_retries.py +14 -0
  32. pytgcalls/methods/utilities/start.py +136 -16
  33. pytgcalls/methods/utilities/stream_params.py +69 -22
  34. pytgcalls/methods/utilities/update_sources.py +42 -0
  35. pytgcalls/mtproto/bridged_client.py +36 -2
  36. pytgcalls/mtproto/client_cache.py +46 -16
  37. pytgcalls/mtproto/hydrogram_client.py +72 -23
  38. pytgcalls/mtproto/mtproto_client.py +32 -4
  39. pytgcalls/mtproto/pyrogram_client.py +72 -23
  40. pytgcalls/mtproto/telethon_client.py +97 -33
  41. pytgcalls/scaffold.py +15 -0
  42. pytgcalls/types/__init__.py +14 -4
  43. pytgcalls/types/calls/__init__.py +2 -0
  44. pytgcalls/types/calls/call.py +5 -3
  45. pytgcalls/types/calls/call_sources.py +4 -0
  46. pytgcalls/types/chats/group_call_participant.py +23 -0
  47. pytgcalls/types/py_object.py +9 -10
  48. pytgcalls/types/raw/audio_stream.py +3 -3
  49. pytgcalls/types/raw/stream.py +8 -4
  50. pytgcalls/types/raw/video_stream.py +5 -4
  51. pytgcalls/types/stream/__init__.py +14 -4
  52. pytgcalls/types/stream/device.py +36 -0
  53. pytgcalls/types/stream/direction.py +25 -0
  54. pytgcalls/types/stream/external_media.py +8 -0
  55. pytgcalls/types/stream/frame.py +26 -0
  56. pytgcalls/types/stream/media_stream.py +178 -107
  57. pytgcalls/types/stream/record_stream.py +99 -0
  58. pytgcalls/types/stream/stream_ended.py +32 -0
  59. pytgcalls/types/stream/stream_frames.py +20 -0
  60. py_tgcalls-2.0.5.dist-info/RECORD +0 -93
  61. pytgcalls/media_devices/screen_info.py +0 -45
  62. pytgcalls/types/stream/stream_audio_ended.py +0 -9
  63. pytgcalls/types/stream/stream_video_ended.py +0 -9
  64. {py_tgcalls-2.0.5.dist-info → py_tgcalls-2.1.0.dist-info}/LICENSE +0 -0
  65. {py_tgcalls-2.0.5.dist-info → py_tgcalls-2.1.0.dist-info}/top_level.txt +0 -0
@@ -1,42 +1,89 @@
1
+ from pathlib import Path
1
2
  from typing import Optional
3
+ from typing import Union
2
4
 
3
5
  from ntgcalls import AudioDescription
4
6
  from ntgcalls import MediaDescription
5
7
  from ntgcalls import VideoDescription
6
8
 
7
- from ...types.raw.stream import Stream
9
+ from ...media_devices.input_device import InputDevice
10
+ from ...media_devices.speaker_device import SpeakerDevice
11
+ from ...types import RecordStream
12
+ from ...types.raw import AudioStream
13
+ from ...types.raw import Stream
14
+ from ...types.raw import VideoStream
8
15
  from ...types.stream.media_stream import MediaStream
9
16
 
10
17
 
11
18
  class StreamParams:
12
19
  @staticmethod
13
- async def get_stream_params(stream: Optional[Stream]) -> MediaDescription:
14
- audio_description = None
15
- video_description = None
16
-
20
+ async def get_stream_params(
21
+ stream: Optional[Union[str, Path, InputDevice, Stream]],
22
+ ) -> MediaDescription:
17
23
  if stream is not None:
24
+ if isinstance(stream, (str, Path, InputDevice)):
25
+ stream = MediaStream(stream)
18
26
  if isinstance(stream, MediaStream):
19
27
  await stream.check_stream()
20
-
21
- if stream.stream_audio is not None:
22
- audio_description = AudioDescription(
23
- input_mode=stream.stream_audio.input_mode,
24
- input=stream.stream_audio.path,
25
- sample_rate=stream.stream_audio.parameters.bitrate,
26
- bits_per_sample=16,
27
- channel_count=stream.stream_audio.parameters.channels,
28
+ elif isinstance(stream, RecordStream):
29
+ raise ValueError(
30
+ 'Stream should be an instance of '
31
+ 'MediaStream or a raw Stream',
28
32
  )
29
33
 
30
- if stream.stream_video is not None:
31
- video_description = VideoDescription(
32
- input_mode=stream.stream_video.input_mode,
33
- input=stream.stream_video.path,
34
- width=stream.stream_video.parameters.width,
35
- height=stream.stream_video.parameters.height,
36
- fps=stream.stream_video.parameters.frame_rate,
34
+ return StreamParams._parse_stream_description(stream)
35
+
36
+ @staticmethod
37
+ def _parse_media_description(
38
+ media: Optional[Union[AudioStream, VideoStream]],
39
+ ) -> Optional[Union[AudioDescription, VideoDescription]]:
40
+ if media is not None:
41
+ if isinstance(media, AudioStream):
42
+ return AudioDescription(
43
+ media_source=media.media_source,
44
+ input=media.path,
45
+ sample_rate=media.parameters.bitrate,
46
+ channel_count=media.parameters.channels,
47
+ )
48
+ elif isinstance(media, VideoStream):
49
+ return VideoDescription(
50
+ media_source=media.media_source,
51
+ input=media.path,
52
+ width=media.parameters.width,
53
+ height=media.parameters.height,
54
+ fps=media.parameters.frame_rate,
37
55
  )
56
+ return None
38
57
 
58
+ @staticmethod
59
+ def _parse_stream_description(
60
+ stream: Optional[Stream],
61
+ ) -> MediaDescription:
39
62
  return MediaDescription(
40
- audio=audio_description,
41
- video=video_description,
63
+ microphone=StreamParams._parse_media_description(
64
+ None if stream is None else stream.microphone,
65
+ ),
66
+ speaker=StreamParams._parse_media_description(
67
+ None if stream is None else stream.speaker,
68
+ ),
69
+ camera=StreamParams._parse_media_description(
70
+ None if stream is None else stream.camera,
71
+ ),
72
+ screen=StreamParams._parse_media_description(
73
+ None if stream is None else stream.screen,
74
+ ),
42
75
  )
76
+
77
+ @staticmethod
78
+ async def get_record_params(
79
+ stream: Optional[Union[str, Path, Stream, SpeakerDevice]],
80
+ ) -> MediaDescription:
81
+ if stream is not None:
82
+ if isinstance(stream, (str, Path, SpeakerDevice)):
83
+ stream = RecordStream(stream)
84
+ if isinstance(stream, MediaStream):
85
+ raise ValueError(
86
+ 'Stream should be an instance of '
87
+ 'RecordStream or a raw Stream',
88
+ )
89
+ return StreamParams._parse_stream_description(stream) # type: ignore
@@ -0,0 +1,42 @@
1
+ from typing import Union
2
+
3
+ from ...mtproto import BridgedClient
4
+ from ...scaffold import Scaffold
5
+ from ...types.calls import CallSources
6
+
7
+
8
+ class UpdateSources(Scaffold):
9
+ async def _update_sources(
10
+ self,
11
+ chat_id: Union[int, str],
12
+ ):
13
+ participants = await self._app.get_group_call_participants(
14
+ chat_id,
15
+ )
16
+ if chat_id not in self._call_sources:
17
+ self._call_sources[chat_id] = CallSources()
18
+ for x in participants:
19
+ if x.video_info is not None and \
20
+ x.user_id not in self._call_sources[chat_id].camera:
21
+ self._call_sources[chat_id].camera[
22
+ x.user_id
23
+ ] = x.video_info.endpoint
24
+ await self._binding.add_incoming_video(
25
+ chat_id,
26
+ x.video_info.endpoint,
27
+ x.video_info.sources,
28
+ )
29
+ if x.presentation_info is not None and \
30
+ x.user_id not in self._call_sources[chat_id].presentation:
31
+ self._call_sources[chat_id].presentation[
32
+ x.user_id
33
+ ] = x.presentation_info.endpoint
34
+ await self._binding.add_incoming_video(
35
+ chat_id,
36
+ x.presentation_info.endpoint,
37
+ x.presentation_info.sources,
38
+ )
39
+ if x.user_id == BridgedClient.chat_id(
40
+ self._cache_local_peer,
41
+ ) and x.muted_by_admin:
42
+ self._need_unmute.add(chat_id)
@@ -6,6 +6,7 @@ from typing import Optional
6
6
 
7
7
  from ntgcalls import Protocol
8
8
  from ntgcalls import RTCServer
9
+ from ntgcalls import SsrcGroup
9
10
 
10
11
  from ..handlers import HandlersHolder
11
12
  from ..types import GroupCallParticipant
@@ -29,6 +30,19 @@ class BridgedClient(HandlersHolder):
29
30
  ):
30
31
  pass
31
32
 
33
+ async def join_presentation(
34
+ self,
35
+ chat_id: int,
36
+ json_join: str,
37
+ ):
38
+ pass
39
+
40
+ async def leave_presentation(
41
+ self,
42
+ chat_id: int,
43
+ ):
44
+ pass
45
+
32
46
  async def request_call(
33
47
  self,
34
48
  user_id: int,
@@ -64,6 +78,7 @@ class BridgedClient(HandlersHolder):
64
78
  async def discard_call(
65
79
  self,
66
80
  chat_id: int,
81
+ is_missed: bool,
67
82
  ):
68
83
  pass
69
84
 
@@ -97,8 +112,9 @@ class BridgedClient(HandlersHolder):
97
112
  self,
98
113
  chat_id: int,
99
114
  muted_status: Optional[bool],
100
- paused_status: Optional[bool],
101
- stopped_status: Optional[bool],
115
+ video_paused: Optional[bool],
116
+ video_stopped: Optional[bool],
117
+ presentation_paused: Optional[bool],
102
118
  participant: Any,
103
119
  ):
104
120
  pass
@@ -128,6 +144,21 @@ class BridgedClient(HandlersHolder):
128
144
  def package_name(obj):
129
145
  return str(obj.__class__.__module__).split('.')[0]
130
146
 
147
+ @staticmethod
148
+ def parse_source(source) -> Optional[GroupCallParticipant.SourceInfo]:
149
+ if not source:
150
+ return None
151
+ return GroupCallParticipant.SourceInfo(
152
+ source.endpoint,
153
+ [
154
+ SsrcGroup(
155
+ source_group.semantics,
156
+ [(ssrc & 0xFFFFFFFF) for ssrc in source_group.sources],
157
+ )
158
+ for source_group in source.source_groups
159
+ ],
160
+ )
161
+
131
162
  @staticmethod
132
163
  def parse_participant(participant):
133
164
  return GroupCallParticipant(
@@ -143,6 +174,9 @@ class BridgedClient(HandlersHolder):
143
174
  if participant.volume is not None else 100,
144
175
  bool(participant.just_joined),
145
176
  bool(participant.left),
177
+ participant.source,
178
+ BridgedClient.parse_source(participant.video),
179
+ BridgedClient.parse_source(participant.presentation),
146
180
  )
147
181
 
148
182
  @staticmethod
@@ -46,23 +46,52 @@ class ClientCache:
46
46
  pass
47
47
  return None
48
48
 
49
- def set_participants_cache(
49
+ def set_participants_cache_call(
50
50
  self,
51
51
  input_id: int,
52
52
  participant: GroupCallParticipant,
53
53
  ) -> Optional[GroupCallParticipant]:
54
54
  chat_id = self.get_chat_id(input_id)
55
55
  if chat_id is not None:
56
- participants: Optional[
57
- ParticipantList
58
- ] = self._call_participants_cache.get(
56
+ return self._internal_set_participants_cache(
59
57
  chat_id,
58
+ participant,
60
59
  )
61
- if participants is not None:
62
- participants.last_mtproto_update = (
63
- int(time()) + self._cache_duration
64
- )
65
- return participants.update_participant(participant)
60
+ return None
61
+
62
+ def set_participants_cache_chat(
63
+ self,
64
+ chat_id: int,
65
+ call_id: int,
66
+ participant: GroupCallParticipant,
67
+ ) -> Optional[GroupCallParticipant]:
68
+ if self._call_participants_cache.get(chat_id) is None:
69
+ self._call_participants_cache.put(
70
+ chat_id,
71
+ ParticipantList(
72
+ call_id,
73
+ ),
74
+ )
75
+ return self._internal_set_participants_cache(
76
+ chat_id,
77
+ participant,
78
+ )
79
+
80
+ def _internal_set_participants_cache(
81
+ self,
82
+ chat_id: int,
83
+ participant: GroupCallParticipant,
84
+ ) -> Optional[GroupCallParticipant]:
85
+ participants: Optional[
86
+ ParticipantList
87
+ ] = self._call_participants_cache.get(
88
+ chat_id,
89
+ )
90
+ if participants is not None:
91
+ participants.last_mtproto_update = (
92
+ int(time()) + self._cache_duration
93
+ )
94
+ return participants.update_participant(participant)
66
95
  return None
67
96
 
68
97
  async def get_participant_list(
@@ -90,7 +119,7 @@ class ClientCache:
90
119
  input_call,
91
120
  )
92
121
  for participant in list_participants:
93
- self.set_participants_cache(
122
+ self.set_participants_cache_call(
94
123
  input_call.id,
95
124
  participant,
96
125
  )
@@ -122,12 +151,13 @@ class ClientCache:
122
151
  input_call,
123
152
  self._cache_duration,
124
153
  )
125
- self._call_participants_cache.put(
126
- chat_id,
127
- ParticipantList(
128
- input_call.id,
129
- ),
130
- )
154
+ if self._call_participants_cache.get(chat_id) is None:
155
+ self._call_participants_cache.put(
156
+ chat_id,
157
+ ParticipantList(
158
+ input_call.id,
159
+ ),
160
+ )
131
161
 
132
162
  def drop_cache(
133
163
  self,
@@ -19,7 +19,9 @@ from hydrogram.raw.functions.phone import EditGroupCallParticipant
19
19
  from hydrogram.raw.functions.phone import GetGroupCall
20
20
  from hydrogram.raw.functions.phone import GetGroupParticipants
21
21
  from hydrogram.raw.functions.phone import JoinGroupCall
22
+ from hydrogram.raw.functions.phone import JoinGroupCallPresentation
22
23
  from hydrogram.raw.functions.phone import LeaveGroupCall
24
+ from hydrogram.raw.functions.phone import LeaveGroupCallPresentation
23
25
  from hydrogram.raw.functions.phone import RequestCall
24
26
  from hydrogram.raw.functions.phone import SendSignalingData
25
27
  from hydrogram.raw.types import Channel
@@ -41,6 +43,7 @@ from hydrogram.raw.types import PhoneCall
41
43
  from hydrogram.raw.types import PhoneCallAccepted
42
44
  from hydrogram.raw.types import PhoneCallDiscarded
43
45
  from hydrogram.raw.types import PhoneCallDiscardReasonHangup
46
+ from hydrogram.raw.types import PhoneCallDiscardReasonMissed
44
47
  from hydrogram.raw.types import PhoneCallProtocol
45
48
  from hydrogram.raw.types import PhoneCallRequested
46
49
  from hydrogram.raw.types import PhoneCallWaiting
@@ -86,7 +89,7 @@ class HydrogramClient(BridgedClient):
86
89
  ):
87
90
  user_id = self._cache.get_user_id(update.phone_call_id)
88
91
  if user_id is not None:
89
- await self.propagate(
92
+ await self._propagate(
90
93
  RawCallUpdate(
91
94
  user_id,
92
95
  RawCallUpdate.Type.SIGNALING_DATA,
@@ -110,7 +113,7 @@ class HydrogramClient(BridgedClient):
110
113
  ),
111
114
  )
112
115
  if isinstance(update.phone_call, PhoneCallAccepted):
113
- await self.propagate(
116
+ await self._propagate(
114
117
  RawCallUpdate(
115
118
  self.user_from_call(update.phone_call),
116
119
  RawCallUpdate.Type.ACCEPTED,
@@ -126,14 +129,14 @@ class HydrogramClient(BridgedClient):
126
129
  self._cache.drop_phone_call(
127
130
  user_id,
128
131
  )
129
- await self.propagate(
132
+ await self._propagate(
130
133
  ChatUpdate(
131
134
  user_id,
132
135
  ChatUpdate.Status.DISCARDED_CALL,
133
136
  ),
134
137
  )
135
138
  if isinstance(update.phone_call, PhoneCallRequested):
136
- await self.propagate(
139
+ await self._propagate(
137
140
  RawCallUpdate(
138
141
  self.user_from_call(update.phone_call),
139
142
  RawCallUpdate.Type.REQUESTED,
@@ -144,7 +147,7 @@ class HydrogramClient(BridgedClient):
144
147
  ),
145
148
  )
146
149
  if isinstance(update.phone_call, PhoneCall):
147
- await self.propagate(
150
+ await self._propagate(
148
151
  RawCallUpdate(
149
152
  self.user_from_call(update.phone_call),
150
153
  RawCallUpdate.Type.CONFIRMED,
@@ -166,12 +169,12 @@ class HydrogramClient(BridgedClient):
166
169
  ):
167
170
  participants = update.participants
168
171
  for participant in participants:
169
- result = self._cache.set_participants_cache(
172
+ result = self._cache.set_participants_cache_call(
170
173
  update.call.id,
171
174
  self.parse_participant(participant),
172
175
  )
173
176
  if result is not None:
174
- await self.propagate(
177
+ await self._propagate(
175
178
  UpdatedGroupCallParticipant(
176
179
  self._cache.get_chat_id(update.call.id),
177
180
  result,
@@ -199,7 +202,7 @@ class HydrogramClient(BridgedClient):
199
202
  GroupCallDiscarded,
200
203
  ):
201
204
  self._cache.drop_cache(chat_id)
202
- await self.propagate(
205
+ await self._propagate(
203
206
  ChatUpdate(
204
207
  chat_id,
205
208
  ChatUpdate.Status.CLOSED_VOICE_CHAT,
@@ -216,7 +219,7 @@ class HydrogramClient(BridgedClient):
216
219
  ChannelForbidden,
217
220
  ):
218
221
  self._cache.drop_cache(chat_id)
219
- await self.propagate(
222
+ await self._propagate(
220
223
  ChatUpdate(
221
224
  chat_id,
222
225
  ChatUpdate.Status.KICKED,
@@ -239,7 +242,7 @@ class HydrogramClient(BridgedClient):
239
242
  update.message.peer_id,
240
243
  PeerChat,
241
244
  ):
242
- await self.propagate(
245
+ await self._propagate(
243
246
  ChatUpdate(
244
247
  chat_id,
245
248
  ChatUpdate.Status.INVITED_VOICE_CHAT,
@@ -259,7 +262,7 @@ class HydrogramClient(BridgedClient):
259
262
  ChatForbidden,
260
263
  ):
261
264
  self._cache.drop_cache(chat_id)
262
- await self.propagate(
265
+ await self._propagate(
263
266
  ChatUpdate(
264
267
  chat_id,
265
268
  ChatUpdate.Status.KICKED,
@@ -287,7 +290,7 @@ class HydrogramClient(BridgedClient):
287
290
  self._cache.drop_cache(
288
291
  chat_id,
289
292
  )
290
- await self.propagate(
293
+ await self._propagate(
291
294
  ChatUpdate(
292
295
  chat_id,
293
296
  ChatUpdate.Status.LEFT_GROUP,
@@ -319,15 +322,22 @@ class HydrogramClient(BridgedClient):
319
322
  ).full_chat.call
320
323
 
321
324
  if input_call is not None:
322
- call: GroupCall = (
325
+ raw_call = (
323
326
  await self._app.invoke(
324
327
  GetGroupCall(
325
328
  call=input_call,
326
329
  limit=-1,
327
330
  ),
328
331
  )
329
- ).call
330
-
332
+ )
333
+ call: GroupCall = raw_call.call
334
+ participants: List[GroupCallParticipant] = raw_call.participants
335
+ for participant in participants:
336
+ self._cache.set_participants_cache_chat(
337
+ chat_id,
338
+ call.id,
339
+ self.parse_participant(participant),
340
+ )
331
341
  if call.schedule_date is not None:
332
342
  return None
333
343
 
@@ -395,12 +405,12 @@ class HydrogramClient(BridgedClient):
395
405
  )
396
406
  for update in result.updates:
397
407
  if isinstance(
398
- update,
399
- UpdateGroupCallParticipants,
408
+ update,
409
+ UpdateGroupCallParticipants,
400
410
  ):
401
411
  participants = update.participants
402
412
  for participant in participants:
403
- self._cache.set_participants_cache(
413
+ self._cache.set_participants_cache_call(
404
414
  update.call.id,
405
415
  self.parse_participant(participant),
406
416
  )
@@ -409,6 +419,37 @@ class HydrogramClient(BridgedClient):
409
419
 
410
420
  return json.dumps({'transport': None})
411
421
 
422
+ async def join_presentation(
423
+ self,
424
+ chat_id: int,
425
+ json_join: str,
426
+ ):
427
+ chat_call = await self._cache.get_full_chat(chat_id)
428
+ if chat_call is not None:
429
+ result: Updates = await self._app.invoke(
430
+ JoinGroupCallPresentation(
431
+ call=chat_call,
432
+ params=DataJSON(data=json_join),
433
+ ),
434
+ )
435
+ for update in result.updates:
436
+ if isinstance(update, UpdateGroupCallConnection):
437
+ return update.params.data
438
+
439
+ return json.dumps({'transport': None})
440
+
441
+ async def leave_presentation(
442
+ self,
443
+ chat_id: int,
444
+ ):
445
+ chat_call = await self._cache.get_full_chat(chat_id)
446
+ if chat_call is not None:
447
+ await self._app.invoke(
448
+ LeaveGroupCallPresentation(
449
+ call=chat_call,
450
+ ),
451
+ )
452
+
412
453
  async def request_call(
413
454
  self,
414
455
  user_id: int,
@@ -518,15 +559,21 @@ class HydrogramClient(BridgedClient):
518
559
  async def discard_call(
519
560
  self,
520
561
  chat_id: int,
562
+ is_missed: bool,
521
563
  ):
522
564
  peer = self._cache.get_phone_call(chat_id)
523
565
  if peer is None:
524
566
  return
567
+ reason = (
568
+ PhoneCallDiscardReasonMissed()
569
+ if is_missed
570
+ else PhoneCallDiscardReasonHangup()
571
+ )
525
572
  await self._app.invoke(
526
573
  DiscardCall(
527
574
  peer=peer,
528
575
  duration=0,
529
- reason=PhoneCallDiscardReasonHangup(),
576
+ reason=reason,
530
577
  connection_id=0,
531
578
  video=False,
532
579
  ),
@@ -554,8 +601,9 @@ class HydrogramClient(BridgedClient):
554
601
  self,
555
602
  chat_id: int,
556
603
  muted_status: Optional[bool],
557
- paused_status: Optional[bool],
558
- stopped_status: Optional[bool],
604
+ video_paused: Optional[bool],
605
+ video_stopped: Optional[bool],
606
+ presentation_paused: Optional[bool],
559
607
  participant: InputPeer,
560
608
  ):
561
609
  chat_call = await self._cache.get_full_chat(chat_id)
@@ -565,8 +613,9 @@ class HydrogramClient(BridgedClient):
565
613
  call=chat_call,
566
614
  participant=participant,
567
615
  muted=muted_status,
568
- video_stopped=stopped_status,
569
- video_paused=paused_status,
616
+ video_paused=video_paused,
617
+ video_stopped=video_stopped,
618
+ presentation_paused=presentation_paused,
570
619
  ),
571
620
  )
572
621
 
@@ -69,6 +69,30 @@ class MtProtoClient:
69
69
  else:
70
70
  raise InvalidMTProtoClient()
71
71
 
72
+ async def join_presentation(
73
+ self,
74
+ chat_id: int,
75
+ json_join: str,
76
+ ):
77
+ if self._bind_client is not None:
78
+ return await self._bind_client.join_presentation(
79
+ chat_id,
80
+ json_join,
81
+ )
82
+ else:
83
+ raise InvalidMTProtoClient()
84
+
85
+ async def leave_presentation(
86
+ self,
87
+ chat_id: int,
88
+ ):
89
+ if self._bind_client is not None:
90
+ return await self._bind_client.leave_presentation(
91
+ chat_id,
92
+ )
93
+ else:
94
+ raise InvalidMTProtoClient()
95
+
72
96
  async def request_call(
73
97
  self,
74
98
  user_id: int,
@@ -102,10 +126,12 @@ class MtProtoClient:
102
126
  async def discard_call(
103
127
  self,
104
128
  user_id: int,
129
+ is_missed: bool,
105
130
  ):
106
131
  if self._bind_client is not None:
107
132
  return await self._bind_client.discard_call(
108
133
  user_id,
134
+ is_missed,
109
135
  )
110
136
  else:
111
137
  raise InvalidMTProtoClient()
@@ -187,16 +213,18 @@ class MtProtoClient:
187
213
  self,
188
214
  chat_id: int,
189
215
  muted_status: Optional[bool],
190
- paused_status: Optional[bool],
191
- stopped_status: Optional[bool],
216
+ video_paused: Optional[bool],
217
+ video_stopped: Optional[bool],
218
+ presentation_paused: Optional[bool],
192
219
  participant: Any,
193
220
  ):
194
221
  if self._bind_client is not None:
195
222
  await self._bind_client.set_call_status(
196
223
  chat_id,
197
224
  muted_status,
198
- paused_status,
199
- stopped_status,
225
+ video_paused,
226
+ video_stopped,
227
+ presentation_paused,
200
228
  participant,
201
229
  )
202
230
  else: