py-tgcalls 2.1.0rc7__py3-none-any.whl → 2.1.2b1__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 (46) hide show
  1. py_tgcalls-2.1.2b1.dist-info/METADATA +126 -0
  2. {py_tgcalls-2.1.0rc7.dist-info → py_tgcalls-2.1.2b1.dist-info}/RECORD +44 -29
  3. {py_tgcalls-2.1.0rc7.dist-info → py_tgcalls-2.1.2b1.dist-info}/WHEEL +1 -1
  4. pytgcalls/__version__.py +1 -1
  5. pytgcalls/exceptions.py +7 -31
  6. pytgcalls/methods/__init__.py +2 -0
  7. pytgcalls/methods/calls/leave_call.py +1 -1
  8. pytgcalls/methods/internal/__init__.py +35 -0
  9. pytgcalls/methods/internal/clear_cache.py +8 -0
  10. pytgcalls/methods/internal/clear_call.py +20 -0
  11. pytgcalls/methods/internal/connect_call.py +133 -0
  12. pytgcalls/methods/internal/emit_sig_data.py +9 -0
  13. pytgcalls/methods/internal/handle_connection_changed.py +27 -0
  14. pytgcalls/methods/internal/handle_mtproto_updates.py +175 -0
  15. pytgcalls/methods/internal/handle_stream_ended.py +23 -0
  16. pytgcalls/methods/internal/handle_stream_frame.py +41 -0
  17. pytgcalls/methods/internal/join_presentation.py +58 -0
  18. pytgcalls/methods/internal/request_broadcast_part.py +42 -0
  19. pytgcalls/methods/internal/request_broadcast_timestamp.py +25 -0
  20. pytgcalls/methods/internal/switch_connection.py +35 -0
  21. pytgcalls/methods/internal/update_status.py +22 -0
  22. pytgcalls/methods/stream/play.py +7 -106
  23. pytgcalls/methods/stream/record.py +0 -6
  24. pytgcalls/methods/utilities/__init__.py +0 -6
  25. pytgcalls/methods/utilities/start.py +23 -289
  26. pytgcalls/mtproto/bridged_client.py +30 -8
  27. pytgcalls/mtproto/hydrogram_client.py +77 -5
  28. pytgcalls/mtproto/mtproto_client.py +37 -4
  29. pytgcalls/mtproto/pyrogram_client.py +81 -9
  30. pytgcalls/mtproto/telethon_client.py +77 -5
  31. pytgcalls/mutex.py +13 -1
  32. pytgcalls/pytgcalls.py +3 -0
  33. pytgcalls/scaffold.py +79 -0
  34. pytgcalls/types/__init__.py +2 -0
  35. pytgcalls/types/calls/__init__.py +2 -0
  36. pytgcalls/types/calls/pending_connection.py +17 -0
  37. pytgcalls/types/chats/chat_update.py +8 -1
  38. pytgcalls/types/py_object.py +9 -10
  39. pytgcalls/types/stream/media_stream.py +1 -1
  40. pytgcalls/wait_counter_lock.py +20 -0
  41. py_tgcalls-2.1.0rc7.dist-info/METADATA +0 -292
  42. pytgcalls/methods/utilities/join_presentation.py +0 -50
  43. {py_tgcalls-2.1.0rc7.dist-info → py_tgcalls-2.1.2b1.dist-info/licenses}/LICENSE +0 -0
  44. {py_tgcalls-2.1.0rc7.dist-info → py_tgcalls-2.1.2b1.dist-info}/top_level.txt +0 -0
  45. /pytgcalls/methods/{utilities → internal}/log_retries.py +0 -0
  46. /pytgcalls/methods/{utilities → internal}/update_sources.py +0 -0
@@ -0,0 +1,175 @@
1
+ from ntgcalls import ConnectionError
2
+ from ntgcalls import ConnectionNotFound
3
+
4
+ from ...exceptions import CallBusy
5
+ from ...exceptions import CallDeclined
6
+ from ...exceptions import CallDiscarded
7
+ from ...mtproto import BridgedClient
8
+ from ...scaffold import Scaffold
9
+ from ...types import CallData
10
+ from ...types import ChatUpdate
11
+ from ...types import GroupCallParticipant
12
+ from ...types import RawCallUpdate
13
+ from ...types import Update
14
+ from ...types import UpdatedGroupCallParticipant
15
+
16
+
17
+ class HandleMTProtoUpdates(Scaffold):
18
+ async def _handle_mtproto_updates(self, update: Update):
19
+ chat_id = update.chat_id
20
+ if update.chat_id in self._p2p_configs:
21
+ p2p_config = self._p2p_configs[chat_id]
22
+ if not p2p_config.wait_data.done():
23
+ if isinstance(update, RawCallUpdate):
24
+ if update.status & RawCallUpdate.Type.UPDATED_CALL:
25
+ p2p_config.wait_data.set_result(
26
+ update,
27
+ )
28
+ if isinstance(update, ChatUpdate) and \
29
+ p2p_config.outgoing:
30
+ if update.status & ChatUpdate.Status.DISCARDED_CALL:
31
+ self._wait_connect.pop(chat_id, None)
32
+ p2p_config.wait_data.set_exception(
33
+ CallBusy(
34
+ chat_id,
35
+ ) if update.status &
36
+ ChatUpdate.Status.BUSY_CALL else
37
+ CallDeclined(
38
+ chat_id,
39
+ ),
40
+ )
41
+ if chat_id in self._wait_connect and \
42
+ not self._wait_connect[chat_id].done():
43
+ if isinstance(update, ChatUpdate):
44
+ if update.status & ChatUpdate.Status.DISCARDED_CALL:
45
+ self._wait_connect[chat_id].set_exception(
46
+ CallDiscarded(
47
+ chat_id,
48
+ ),
49
+ )
50
+ if isinstance(update, RawCallUpdate):
51
+ if update.status & RawCallUpdate.Type.REQUESTED:
52
+ self._p2p_configs[chat_id] = CallData(
53
+ await self._app.get_dhc(),
54
+ self.loop,
55
+ update.g_a_or_b,
56
+ )
57
+ update = ChatUpdate(
58
+ chat_id,
59
+ ChatUpdate.Status.INCOMING_CALL,
60
+ )
61
+ if isinstance(update, RawCallUpdate):
62
+ if update.status & RawCallUpdate.Type.SIGNALING_DATA:
63
+ try:
64
+ await self._binding.send_signaling(
65
+ update.chat_id,
66
+ update.signaling_data,
67
+ )
68
+ except (ConnectionNotFound, ConnectionError):
69
+ pass
70
+ if isinstance(update, ChatUpdate):
71
+ if update.status & ChatUpdate.Status.LEFT_CALL:
72
+ await self._clear_call(chat_id)
73
+ if isinstance(update, UpdatedGroupCallParticipant):
74
+ participant = update.participant
75
+ action = participant.action
76
+ chat_peer = self._cache_user_peer.get(chat_id)
77
+ user_id = participant.user_id
78
+ if chat_id in self._call_sources:
79
+ call_sources = self._call_sources[chat_id]
80
+ was_camera = user_id in call_sources.camera
81
+ was_screen = user_id in call_sources.presentation
82
+
83
+ if was_camera != participant.video_camera:
84
+ if participant.video_info:
85
+ self._call_sources[chat_id].camera[
86
+ user_id
87
+ ] = participant.video_info.endpoint
88
+ try:
89
+ await self._binding.add_incoming_video(
90
+ chat_id,
91
+ participant.video_info.endpoint,
92
+ participant.video_info.sources,
93
+ )
94
+ except (ConnectionNotFound, ConnectionError):
95
+ pass
96
+ elif user_id in self._call_sources[chat_id].camera:
97
+ try:
98
+ await self._binding.remove_incoming_video(
99
+ chat_id,
100
+ self._call_sources[
101
+ chat_id
102
+ ].camera[user_id],
103
+ )
104
+ except (ConnectionNotFound, ConnectionError):
105
+ pass
106
+ self._call_sources[chat_id].camera.pop(
107
+ user_id, None,
108
+ )
109
+
110
+ if was_screen != participant.screen_sharing:
111
+ if participant.presentation_info:
112
+ self._call_sources[chat_id].presentation[
113
+ user_id
114
+ ] = participant.presentation_info.endpoint
115
+ try:
116
+ await self._binding.add_incoming_video(
117
+ chat_id,
118
+ participant.presentation_info.endpoint,
119
+ participant.presentation_info.sources,
120
+ )
121
+ except (ConnectionNotFound, ConnectionError):
122
+ pass
123
+ elif user_id in self._call_sources[
124
+ chat_id
125
+ ].presentation:
126
+ try:
127
+ await self._binding.remove_incoming_video(
128
+ chat_id,
129
+ self._call_sources[
130
+ chat_id
131
+ ].presentation[user_id],
132
+ )
133
+ except (ConnectionNotFound, ConnectionError):
134
+ pass
135
+ self._call_sources[chat_id].presentation.pop(
136
+ user_id, None,
137
+ )
138
+
139
+ if chat_peer:
140
+ is_self = BridgedClient.chat_id(
141
+ chat_peer,
142
+ ) == participant.user_id if chat_peer else False
143
+ if is_self:
144
+ if action == GroupCallParticipant.Action.LEFT:
145
+ if await self._clear_call(chat_id):
146
+ await self._propagate(
147
+ ChatUpdate(
148
+ chat_id,
149
+ ChatUpdate.Status.KICKED,
150
+ ),
151
+ self,
152
+ )
153
+ if (
154
+ chat_id in self._need_unmute and
155
+ action == GroupCallParticipant.Action.UPDATED and
156
+ not participant.muted_by_admin
157
+ ):
158
+ await self._update_status(
159
+ chat_id,
160
+ await self._binding.get_state(chat_id),
161
+ )
162
+ await self._switch_connection(chat_id)
163
+
164
+ if (
165
+ participant.muted_by_admin and
166
+ action != GroupCallParticipant.Action.LEFT
167
+ ):
168
+ self._need_unmute.add(chat_id)
169
+ else:
170
+ self._need_unmute.discard(chat_id)
171
+ if not isinstance(update, RawCallUpdate):
172
+ await self._propagate(
173
+ update,
174
+ self,
175
+ )
@@ -0,0 +1,23 @@
1
+ from ntgcalls import StreamDevice
2
+ from ntgcalls import StreamType
3
+
4
+ from ...scaffold import Scaffold
5
+ from ...types import Device
6
+ from ...types import StreamEnded
7
+
8
+
9
+ class HandleStreamEnded(Scaffold):
10
+ async def _handle_stream_ended(
11
+ self,
12
+ chat_id: int,
13
+ stream_type: StreamType,
14
+ device: StreamDevice,
15
+ ):
16
+ await self._propagate(
17
+ StreamEnded(
18
+ chat_id,
19
+ StreamEnded.Type.from_raw(stream_type),
20
+ Device.from_raw(device),
21
+ ),
22
+ self,
23
+ )
@@ -0,0 +1,41 @@
1
+ from typing import List
2
+
3
+ from ntgcalls import Frame as RawFrame
4
+ from ntgcalls import StreamDevice
5
+ from ntgcalls import StreamMode
6
+
7
+ from ...scaffold import Scaffold
8
+ from ...types import Device
9
+ from ...types import Direction
10
+ from ...types import Frame
11
+ from ...types import StreamFrames
12
+
13
+
14
+ class HandleStreamFrame(Scaffold):
15
+ async def _handle_stream_frame(
16
+ self,
17
+ chat_id: int,
18
+ mode: StreamMode,
19
+ device: StreamDevice,
20
+ frames: List[RawFrame],
21
+ ):
22
+ await self._propagate(
23
+ StreamFrames(
24
+ chat_id,
25
+ Direction.from_raw(mode),
26
+ Device.from_raw(device),
27
+ [
28
+ Frame(
29
+ x.ssrc,
30
+ x.data,
31
+ Frame.Info(
32
+ x.frame_data.absolute_capture_timestamp_ms,
33
+ x.frame_data.width,
34
+ x.frame_data.height,
35
+ x.frame_data.rotation,
36
+ ),
37
+ ) for x in frames
38
+ ],
39
+ ),
40
+ self,
41
+ )
@@ -0,0 +1,58 @@
1
+ from typing import Union
2
+
3
+ from ntgcalls import ConnectionError
4
+ from ntgcalls import ConnectionMode
5
+ from ntgcalls import TelegramServerError
6
+
7
+ from ...scaffold import Scaffold
8
+
9
+
10
+ class JoinPresentation(Scaffold):
11
+ async def _join_presentation(
12
+ self,
13
+ chat_id: Union[int, str],
14
+ join: bool,
15
+ ):
16
+ connection_mode = await self._binding.get_connection_mode(
17
+ chat_id,
18
+ )
19
+ if connection_mode == ConnectionMode.STREAM:
20
+ if chat_id in self._pending_connections:
21
+ self._pending_connections[chat_id].presentation = join
22
+ elif connection_mode == ConnectionMode.RTC:
23
+ if join:
24
+ if chat_id in self._presentations:
25
+ return
26
+ for retries in range(4):
27
+ try:
28
+ self._wait_connect[
29
+ chat_id
30
+ ] = self.loop.create_future()
31
+ payload = await self._binding.init_presentation(
32
+ chat_id,
33
+ )
34
+ result_params = await self._app.join_presentation(
35
+ chat_id,
36
+ payload,
37
+ )
38
+ await self._binding.connect(
39
+ chat_id,
40
+ result_params,
41
+ True,
42
+ )
43
+ await self._wait_connect[chat_id]
44
+ self._presentations.add(chat_id)
45
+ break
46
+ except TelegramServerError:
47
+ if retries == 3:
48
+ raise
49
+ self._log_retries(retries)
50
+ finally:
51
+ self._wait_connect.pop(chat_id, None)
52
+ elif chat_id in self._presentations:
53
+ try:
54
+ await self._binding.stop_presentation(chat_id)
55
+ await self._app.leave_presentation(chat_id)
56
+ except ConnectionError:
57
+ pass
58
+ self._presentations.discard(chat_id)
@@ -0,0 +1,42 @@
1
+ from ntgcalls import ConnectionError
2
+ from ntgcalls import ConnectionNotFound
3
+ from ntgcalls import MediaSegmentStatus
4
+ from ntgcalls import SegmentPartRequest
5
+
6
+ from ...scaffold import Scaffold
7
+
8
+
9
+ class RequestBroadcastPart(Scaffold):
10
+ async def _request_broadcast_part(
11
+ self,
12
+ chat_id: int,
13
+ part_request: SegmentPartRequest,
14
+ ):
15
+ part_status = MediaSegmentStatus.NOT_READY
16
+ # noinspection PyBroadException
17
+ try:
18
+ part = await self._app.download_stream(
19
+ chat_id,
20
+ part_request.timestamp,
21
+ part_request.limit,
22
+ part_request.channel_id
23
+ if part_request.channel_id > 0 else None,
24
+ part_request.quality,
25
+ )
26
+ if part is not None:
27
+ part_status = MediaSegmentStatus.SUCCESS
28
+ except Exception:
29
+ part = None
30
+ part_status = MediaSegmentStatus.RESYNC_NEEDED
31
+
32
+ try:
33
+ await self._binding.send_broadcast_part(
34
+ chat_id,
35
+ part_request.segment_id,
36
+ part_request.part_id,
37
+ part_status,
38
+ part_request.quality_update,
39
+ part,
40
+ )
41
+ except (ConnectionError, ConnectionNotFound):
42
+ pass
@@ -0,0 +1,25 @@
1
+ from ntgcalls import ConnectionError
2
+ from ntgcalls import ConnectionNotFound
3
+
4
+ from ...scaffold import Scaffold
5
+
6
+
7
+ class RequestBroadcastTimestamp(Scaffold):
8
+ async def _request_broadcast_timestamp(
9
+ self,
10
+ chat_id: int,
11
+ ):
12
+ # noinspection PyBroadException
13
+ try:
14
+ time = await self._app.get_stream_timestamp(
15
+ chat_id,
16
+ )
17
+ except Exception:
18
+ time = 0
19
+ try:
20
+ await self._binding.send_broadcast_timestamp(
21
+ chat_id,
22
+ time,
23
+ )
24
+ except (ConnectionError, ConnectionNotFound):
25
+ pass
@@ -0,0 +1,35 @@
1
+ import logging
2
+
3
+ from ntgcalls import ConnectionMode
4
+
5
+ from ...scaffold import Scaffold
6
+ from ...types.calls import CallSources
7
+
8
+ py_logger = logging.getLogger('pytgcalls')
9
+
10
+
11
+ class SwitchConnection(Scaffold):
12
+ async def _switch_connection(self, chat_id: int):
13
+ try:
14
+ connection_mode = await self._binding.get_connection_mode(
15
+ chat_id,
16
+ )
17
+ if connection_mode == ConnectionMode.STREAM and \
18
+ chat_id in self._pending_connections:
19
+ connection = self._pending_connections[chat_id]
20
+ await self._connect_call(
21
+ chat_id,
22
+ connection.media_description,
23
+ connection.config,
24
+ connection.payload,
25
+ )
26
+ if connection.presentation:
27
+ await self._join_presentation(
28
+ chat_id,
29
+ True,
30
+ )
31
+ self._call_sources[chat_id] = CallSources()
32
+ await self._update_sources(chat_id)
33
+ self._pending_connections.pop(chat_id)
34
+ except Exception as e:
35
+ py_logger.debug(f'SetPresentationStatus: {e}')
@@ -0,0 +1,22 @@
1
+ import logging
2
+
3
+ from ntgcalls import MediaState
4
+
5
+ from ...scaffold import Scaffold
6
+
7
+ py_logger = logging.getLogger('pytgcalls')
8
+
9
+
10
+ class UpdateStatus(Scaffold):
11
+ async def _update_status(self, chat_id: int, state: MediaState):
12
+ try:
13
+ await self._app.set_call_status(
14
+ chat_id,
15
+ state.muted,
16
+ state.video_paused,
17
+ state.video_stopped,
18
+ state.presentation_paused,
19
+ self._cache_user_peer.get(chat_id),
20
+ )
21
+ except Exception as e:
22
+ py_logger.debug(f'SetVideoCallStatus: {e}')
@@ -1,27 +1,19 @@
1
- import asyncio
2
1
  import logging
3
2
  from pathlib import Path
4
3
  from typing import Optional
5
4
  from typing import Union
6
5
 
7
- from ntgcalls import ConnectionNotFound
8
6
  from ntgcalls import FileError
9
7
  from ntgcalls import StreamMode
10
- from ntgcalls import TelegramServerError
11
- from ntgcalls import TransportParseException
12
8
 
13
9
  from ...exceptions import NoActiveGroupCall
14
- from ...exceptions import TimedOutAnswer
15
- from ...exceptions import UnMuteNeeded
16
10
  from ...media_devices.input_device import InputDevice
17
11
  from ...mtproto_required import mtproto_required
18
12
  from ...mutex import mutex
19
13
  from ...scaffold import Scaffold
20
14
  from ...statictypes import statictypes
21
15
  from ...types import CallConfig
22
- from ...types import CallData
23
16
  from ...types import GroupCallConfig
24
- from ...types import RawCallUpdate
25
17
  from ...types.raw import Stream
26
18
  from ..utilities.stream_params import StreamParams
27
19
 
@@ -29,9 +21,9 @@ py_logger = logging.getLogger('pytgcalls')
29
21
 
30
22
 
31
23
  class Play(Scaffold):
32
- @mutex
33
24
  @statictypes
34
25
  @mtproto_required
26
+ @mutex
35
27
  async def play(
36
28
  self,
37
29
  chat_id: Union[int, str],
@@ -79,101 +71,12 @@ class Play(Scaffold):
79
71
  raise NoActiveGroupCall()
80
72
 
81
73
  try:
82
- for retries in range(4):
83
- try:
84
- self._wait_connect[chat_id] = self.loop.create_future()
85
- if isinstance(config, GroupCallConfig):
86
- payload: str = await self._binding.create_call(
87
- chat_id,
88
- media_description,
89
- )
90
- result_params = await self._app.join_group_call(
91
- chat_id,
92
- payload,
93
- config.invite_hash,
94
- media_description.camera is None and
95
- media_description.screen is None,
96
- self._cache_user_peer.get(chat_id),
97
- )
98
- await self._binding.connect(
99
- chat_id,
100
- result_params,
101
- False,
102
- )
103
- else:
104
- data = self._p2p_configs.setdefault(
105
- chat_id,
106
- CallData(await self._app.get_dhc(), self.loop),
107
- )
108
- await self._binding.create_p2p_call(
109
- chat_id,
110
- media_description,
111
- )
112
- data.g_a_or_b = await self._binding.init_exchange(
113
- chat_id,
114
- data.dh_config,
115
- data.g_a_or_b,
116
- )
117
- if not data.outgoing:
118
- await self._app.accept_call(
119
- chat_id,
120
- data.g_a_or_b,
121
- self._binding.get_protocol(),
122
- )
123
- else:
124
- await self._app.request_call(
125
- chat_id,
126
- data.g_a_or_b,
127
- self._binding.get_protocol(),
128
- )
129
-
130
- try:
131
- result: RawCallUpdate = await asyncio.wait_for(
132
- data.wait_data,
133
- timeout=config.timeout,
134
- )
135
- auth_params = await self._binding.exchange_keys(
136
- chat_id,
137
- result.g_a_or_b,
138
- result.fingerprint,
139
- )
140
- if result.status & RawCallUpdate.Type.ACCEPTED:
141
- result.protocol = await self._app.confirm_call(
142
- chat_id,
143
- auth_params.g_a_or_b,
144
- auth_params.key_fingerprint,
145
- self._binding.get_protocol(),
146
- )
147
- await self._binding.connect_p2p(
148
- chat_id,
149
- result.protocol.rtc_servers,
150
- result.protocol.library_versions,
151
- result.protocol.p2p_allowed,
152
- )
153
- except asyncio.TimeoutError:
154
- try:
155
- await self._binding.stop(chat_id)
156
- except ConnectionNotFound:
157
- pass
158
- await self._app.discard_call(chat_id, True)
159
- raise TimedOutAnswer()
160
- finally:
161
- self._p2p_configs.pop(chat_id, None)
162
- await self._wait_connect[chat_id]
163
- break
164
- except TelegramServerError:
165
- if retries == 3 or is_p2p:
166
- raise
167
- self._log_retries(retries)
168
- except Exception:
169
- try:
170
- await self._binding.stop(chat_id)
171
- except ConnectionNotFound:
172
- pass
173
- raise
174
- finally:
175
- self._wait_connect.pop(chat_id, None)
176
-
74
+ await self._connect_call(
75
+ chat_id, # type: ignore
76
+ media_description,
77
+ config,
78
+ None,
79
+ )
177
80
  if isinstance(config, GroupCallConfig):
178
81
  await self._join_presentation(
179
82
  chat_id,
@@ -182,8 +85,6 @@ class Play(Scaffold):
182
85
  await self._update_sources(chat_id)
183
86
  except FileError as e:
184
87
  raise FileNotFoundError(e)
185
- except TransportParseException:
186
- raise UnMuteNeeded()
187
88
  except Exception:
188
89
  if isinstance(config, GroupCallConfig):
189
90
  self._cache_user_peer.pop(chat_id)
@@ -39,11 +39,5 @@ class Record(Scaffold):
39
39
  StreamMode.PLAYBACK,
40
40
  media_description,
41
41
  )
42
- if isinstance(chat_id, int) and chat_id < 0:
43
- await self._join_presentation(
44
- chat_id,
45
- media_description.screen is not None,
46
- )
47
- await self._update_sources(chat_id)
48
42
  except FileError as e:
49
43
  raise FileNotFoundError(e)
@@ -1,25 +1,19 @@
1
1
  from .cache_peer import CachePeer
2
2
  from .call_holder import CallHolder
3
3
  from .cpu_usage import CpuUsage
4
- from .join_presentation import JoinPresentation
5
- from .log_retries import LogRetries
6
4
  from .ping import Ping
7
5
  from .resolve_chat_id import ResolveChatID
8
6
  from .run import Run
9
7
  from .start import Start
10
- from .update_sources import UpdateSources
11
8
 
12
9
 
13
10
  class Utilities(
14
11
  CachePeer,
15
12
  CallHolder,
16
13
  CpuUsage,
17
- JoinPresentation,
18
- LogRetries,
19
14
  Ping,
20
15
  ResolveChatID,
21
16
  Run,
22
17
  Start,
23
- UpdateSources,
24
18
  ):
25
19
  pass