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.
- {py_tgcalls-2.0.5.dist-info → py_tgcalls-2.1.0.dist-info}/METADATA +11 -16
- py_tgcalls-2.1.0.dist-info/RECORD +106 -0
- {py_tgcalls-2.0.5.dist-info → py_tgcalls-2.1.0.dist-info}/WHEEL +1 -1
- pytgcalls/__init__.py +2 -0
- pytgcalls/__version__.py +1 -1
- pytgcalls/exceptions.py +7 -24
- pytgcalls/ffmpeg.py +2 -2
- pytgcalls/filters.py +52 -7
- pytgcalls/handlers/handlers_holder.py +1 -1
- pytgcalls/media_devices/__init__.py +6 -2
- pytgcalls/media_devices/device_info.py +8 -15
- pytgcalls/media_devices/input_device.py +11 -0
- pytgcalls/media_devices/media_devices.py +41 -92
- pytgcalls/media_devices/screen_device.py +10 -0
- pytgcalls/media_devices/speaker_device.py +10 -0
- pytgcalls/methods/calls/change_volume_call.py +3 -0
- pytgcalls/methods/calls/get_participants.py +5 -1
- pytgcalls/methods/calls/leave_call.py +3 -1
- pytgcalls/methods/stream/__init__.py +14 -10
- pytgcalls/methods/stream/{mute_stream.py → mute.py} +2 -2
- pytgcalls/methods/stream/{pause_stream.py → pause.py} +2 -2
- pytgcalls/methods/stream/play.py +27 -21
- pytgcalls/methods/stream/record.py +49 -0
- pytgcalls/methods/stream/{resume_stream.py → resume.py} +2 -2
- pytgcalls/methods/stream/send_frame.py +38 -0
- pytgcalls/methods/stream/{played_time.py → time.py} +5 -3
- pytgcalls/methods/stream/{unmute_stream.py → unmute.py} +2 -2
- pytgcalls/methods/utilities/__init__.py +6 -0
- pytgcalls/methods/utilities/call_holder.py +5 -2
- pytgcalls/methods/utilities/join_presentation.py +50 -0
- pytgcalls/methods/utilities/log_retries.py +14 -0
- pytgcalls/methods/utilities/start.py +136 -16
- pytgcalls/methods/utilities/stream_params.py +69 -22
- pytgcalls/methods/utilities/update_sources.py +42 -0
- pytgcalls/mtproto/bridged_client.py +36 -2
- pytgcalls/mtproto/client_cache.py +46 -16
- pytgcalls/mtproto/hydrogram_client.py +72 -23
- pytgcalls/mtproto/mtproto_client.py +32 -4
- pytgcalls/mtproto/pyrogram_client.py +72 -23
- pytgcalls/mtproto/telethon_client.py +97 -33
- pytgcalls/scaffold.py +15 -0
- pytgcalls/types/__init__.py +14 -4
- pytgcalls/types/calls/__init__.py +2 -0
- pytgcalls/types/calls/call.py +5 -3
- pytgcalls/types/calls/call_sources.py +4 -0
- pytgcalls/types/chats/group_call_participant.py +23 -0
- pytgcalls/types/py_object.py +9 -10
- pytgcalls/types/raw/audio_stream.py +3 -3
- pytgcalls/types/raw/stream.py +8 -4
- pytgcalls/types/raw/video_stream.py +5 -4
- pytgcalls/types/stream/__init__.py +14 -4
- pytgcalls/types/stream/device.py +36 -0
- pytgcalls/types/stream/direction.py +25 -0
- pytgcalls/types/stream/external_media.py +8 -0
- pytgcalls/types/stream/frame.py +26 -0
- pytgcalls/types/stream/media_stream.py +178 -107
- pytgcalls/types/stream/record_stream.py +99 -0
- pytgcalls/types/stream/stream_ended.py +32 -0
- pytgcalls/types/stream/stream_frames.py +20 -0
- py_tgcalls-2.0.5.dist-info/RECORD +0 -93
- pytgcalls/media_devices/screen_info.py +0 -45
- pytgcalls/types/stream/stream_audio_ended.py +0 -9
- pytgcalls/types/stream/stream_video_ended.py +0 -9
- {py_tgcalls-2.0.5.dist-info → py_tgcalls-2.1.0.dist-info}/LICENSE +0 -0
- {py_tgcalls-2.0.5.dist-info → py_tgcalls-2.1.0.dist-info}/top_level.txt +0 -0
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
from .
|
|
2
|
-
from .
|
|
1
|
+
from .mute import Mute
|
|
2
|
+
from .pause import Pause
|
|
3
3
|
from .play import Play
|
|
4
|
-
from .
|
|
5
|
-
from .
|
|
6
|
-
from .
|
|
4
|
+
from .record import Record
|
|
5
|
+
from .resume import Resume
|
|
6
|
+
from .send_frame import SendFrame
|
|
7
|
+
from .time import Time
|
|
8
|
+
from .unmute import UnMute
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
class StreamMethods(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
Mute,
|
|
13
|
+
Pause,
|
|
12
14
|
Play,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
Record,
|
|
16
|
+
SendFrame,
|
|
17
|
+
Time,
|
|
18
|
+
Resume,
|
|
19
|
+
UnMute,
|
|
16
20
|
):
|
|
17
21
|
pass
|
|
@@ -8,10 +8,10 @@ from ...scaffold import Scaffold
|
|
|
8
8
|
from ...statictypes import statictypes
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
class
|
|
11
|
+
class Mute(Scaffold):
|
|
12
12
|
@statictypes
|
|
13
13
|
@mtproto_required
|
|
14
|
-
async def
|
|
14
|
+
async def mute(
|
|
15
15
|
self,
|
|
16
16
|
chat_id: Union[int, str],
|
|
17
17
|
):
|
|
@@ -8,10 +8,10 @@ from ...scaffold import Scaffold
|
|
|
8
8
|
from ...statictypes import statictypes
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
class
|
|
11
|
+
class Pause(Scaffold):
|
|
12
12
|
@statictypes
|
|
13
13
|
@mtproto_required
|
|
14
|
-
async def
|
|
14
|
+
async def pause(
|
|
15
15
|
self,
|
|
16
16
|
chat_id: Union[int, str],
|
|
17
17
|
):
|
pytgcalls/methods/stream/play.py
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import logging
|
|
3
|
+
from pathlib import Path
|
|
3
4
|
from typing import Optional
|
|
4
5
|
from typing import Union
|
|
5
6
|
|
|
6
7
|
from ntgcalls import ConnectionNotFound
|
|
7
8
|
from ntgcalls import FileError
|
|
8
|
-
from ntgcalls import
|
|
9
|
+
from ntgcalls import StreamMode
|
|
9
10
|
from ntgcalls import TelegramServerError
|
|
11
|
+
from ntgcalls import TransportParseException
|
|
10
12
|
|
|
11
13
|
from ...exceptions import NoActiveGroupCall
|
|
12
14
|
from ...exceptions import TimedOutAnswer
|
|
13
15
|
from ...exceptions import UnMuteNeeded
|
|
14
|
-
from ...
|
|
16
|
+
from ...media_devices.input_device import InputDevice
|
|
15
17
|
from ...mtproto_required import mtproto_required
|
|
16
18
|
from ...mutex import mutex
|
|
17
19
|
from ...scaffold import Scaffold
|
|
@@ -33,7 +35,7 @@ class Play(Scaffold):
|
|
|
33
35
|
async def play(
|
|
34
36
|
self,
|
|
35
37
|
chat_id: Union[int, str],
|
|
36
|
-
stream: Optional[Stream] = None,
|
|
38
|
+
stream: Optional[Union[str, Path, InputDevice, Stream]] = None,
|
|
37
39
|
config: Optional[Union[CallConfig, GroupCallConfig]] = None,
|
|
38
40
|
):
|
|
39
41
|
chat_id = await self.resolve_chat_id(chat_id)
|
|
@@ -50,8 +52,9 @@ class Play(Scaffold):
|
|
|
50
52
|
|
|
51
53
|
if chat_id in await self._binding.calls():
|
|
52
54
|
try:
|
|
53
|
-
return await self._binding.
|
|
55
|
+
return await self._binding.set_stream_sources(
|
|
54
56
|
chat_id,
|
|
57
|
+
StreamMode.CAPTURE,
|
|
55
58
|
media_description,
|
|
56
59
|
)
|
|
57
60
|
except FileError as e:
|
|
@@ -80,31 +83,36 @@ class Play(Scaffold):
|
|
|
80
83
|
try:
|
|
81
84
|
self._wait_connect[chat_id] = self.loop.create_future()
|
|
82
85
|
if isinstance(config, GroupCallConfig):
|
|
83
|
-
|
|
86
|
+
payload: str = await self._binding.create_call(
|
|
84
87
|
chat_id,
|
|
85
88
|
media_description,
|
|
86
89
|
)
|
|
87
90
|
result_params = await self._app.join_group_call(
|
|
88
91
|
chat_id,
|
|
89
|
-
|
|
92
|
+
payload,
|
|
90
93
|
config.invite_hash,
|
|
91
|
-
media_description.
|
|
94
|
+
media_description.camera is None and
|
|
95
|
+
media_description.screen is None,
|
|
92
96
|
self._cache_user_peer.get(chat_id),
|
|
93
97
|
)
|
|
94
98
|
await self._binding.connect(
|
|
95
99
|
chat_id,
|
|
96
100
|
result_params,
|
|
101
|
+
False,
|
|
97
102
|
)
|
|
98
103
|
else:
|
|
99
104
|
data = self._p2p_configs.setdefault(
|
|
100
105
|
chat_id,
|
|
101
106
|
CallData(await self._app.get_dhc(), self.loop),
|
|
102
107
|
)
|
|
103
|
-
|
|
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(
|
|
104
113
|
chat_id,
|
|
105
114
|
data.dh_config,
|
|
106
115
|
data.g_a_or_b,
|
|
107
|
-
media_description,
|
|
108
116
|
)
|
|
109
117
|
if not data.outgoing:
|
|
110
118
|
await self._app.accept_call(
|
|
@@ -143,7 +151,11 @@ class Play(Scaffold):
|
|
|
143
151
|
result.protocol.p2p_allowed,
|
|
144
152
|
)
|
|
145
153
|
except asyncio.TimeoutError:
|
|
146
|
-
|
|
154
|
+
try:
|
|
155
|
+
await self._binding.stop(chat_id)
|
|
156
|
+
except ConnectionNotFound:
|
|
157
|
+
pass
|
|
158
|
+
await self._app.discard_call(chat_id, True)
|
|
147
159
|
raise TimedOutAnswer()
|
|
148
160
|
finally:
|
|
149
161
|
self._p2p_configs.pop(chat_id, None)
|
|
@@ -152,10 +164,7 @@ class Play(Scaffold):
|
|
|
152
164
|
except TelegramServerError:
|
|
153
165
|
if retries == 3 or is_p2p:
|
|
154
166
|
raise
|
|
155
|
-
(
|
|
156
|
-
f'Telegram is having some internal server issues. '
|
|
157
|
-
f'Retrying {retries + 1} of 3',
|
|
158
|
-
)
|
|
167
|
+
self._log_retries(retries)
|
|
159
168
|
except Exception:
|
|
160
169
|
try:
|
|
161
170
|
await self._binding.stop(chat_id)
|
|
@@ -166,17 +175,14 @@ class Play(Scaffold):
|
|
|
166
175
|
self._wait_connect.pop(chat_id, None)
|
|
167
176
|
|
|
168
177
|
if isinstance(config, GroupCallConfig):
|
|
169
|
-
|
|
178
|
+
await self._join_presentation(
|
|
170
179
|
chat_id,
|
|
180
|
+
media_description.screen is not None,
|
|
171
181
|
)
|
|
172
|
-
|
|
173
|
-
if x.user_id == BridgedClient.chat_id(
|
|
174
|
-
self._cache_local_peer,
|
|
175
|
-
) and x.muted_by_admin:
|
|
176
|
-
self._need_unmute.add(chat_id)
|
|
182
|
+
await self._update_sources(chat_id)
|
|
177
183
|
except FileError as e:
|
|
178
184
|
raise FileNotFoundError(e)
|
|
179
|
-
except
|
|
185
|
+
except TransportParseException:
|
|
180
186
|
raise UnMuteNeeded()
|
|
181
187
|
except Exception:
|
|
182
188
|
if isinstance(config, GroupCallConfig):
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from typing import Union
|
|
5
|
+
|
|
6
|
+
from ntgcalls import FileError
|
|
7
|
+
from ntgcalls import StreamMode
|
|
8
|
+
|
|
9
|
+
from ...media_devices import SpeakerDevice
|
|
10
|
+
from ...mtproto_required import mtproto_required
|
|
11
|
+
from ...scaffold import Scaffold
|
|
12
|
+
from ...statictypes import statictypes
|
|
13
|
+
from ...types import CallConfig
|
|
14
|
+
from ...types import GroupCallConfig
|
|
15
|
+
from ...types.raw import Stream
|
|
16
|
+
from ..utilities.stream_params import StreamParams
|
|
17
|
+
|
|
18
|
+
py_logger = logging.getLogger('pytgcalls')
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Record(Scaffold):
|
|
22
|
+
@statictypes
|
|
23
|
+
@mtproto_required
|
|
24
|
+
async def record(
|
|
25
|
+
self,
|
|
26
|
+
chat_id: Union[int, str],
|
|
27
|
+
stream: Optional[Union[str, Path, Stream, SpeakerDevice]] = None,
|
|
28
|
+
config: Optional[Union[CallConfig, GroupCallConfig]] = None,
|
|
29
|
+
):
|
|
30
|
+
chat_id = await self.resolve_chat_id(chat_id)
|
|
31
|
+
media_description = await StreamParams.get_record_params(
|
|
32
|
+
stream,
|
|
33
|
+
)
|
|
34
|
+
if chat_id not in await self._binding.calls():
|
|
35
|
+
await self.play(chat_id, config=config)
|
|
36
|
+
try:
|
|
37
|
+
await self._binding.set_stream_sources(
|
|
38
|
+
chat_id,
|
|
39
|
+
StreamMode.PLAYBACK,
|
|
40
|
+
media_description,
|
|
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
|
+
except FileError as e:
|
|
49
|
+
raise FileNotFoundError(e)
|
|
@@ -8,10 +8,10 @@ from ...scaffold import Scaffold
|
|
|
8
8
|
from ...statictypes import statictypes
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
class
|
|
11
|
+
class Resume(Scaffold):
|
|
12
12
|
@statictypes
|
|
13
13
|
@mtproto_required
|
|
14
|
-
async def
|
|
14
|
+
async def resume(
|
|
15
15
|
self,
|
|
16
16
|
chat_id: Union[int, str],
|
|
17
17
|
):
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
|
|
3
|
+
from ntgcalls import ConnectionNotFound
|
|
4
|
+
from ntgcalls import FrameData
|
|
5
|
+
|
|
6
|
+
from ...exceptions import NotInCallError
|
|
7
|
+
from ...mtproto_required import mtproto_required
|
|
8
|
+
from ...scaffold import Scaffold
|
|
9
|
+
from ...statictypes import statictypes
|
|
10
|
+
from ...types import Device
|
|
11
|
+
from ...types import Frame
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SendFrame(Scaffold):
|
|
15
|
+
@statictypes
|
|
16
|
+
@mtproto_required
|
|
17
|
+
async def send_frame(
|
|
18
|
+
self,
|
|
19
|
+
chat_id: Union[int, str],
|
|
20
|
+
device: Device,
|
|
21
|
+
data: bytes,
|
|
22
|
+
frame_data: Frame.Info = Frame.Info(),
|
|
23
|
+
):
|
|
24
|
+
chat_id = await self.resolve_chat_id(chat_id)
|
|
25
|
+
try:
|
|
26
|
+
return await self._binding.send_external_frame(
|
|
27
|
+
chat_id,
|
|
28
|
+
Device.to_raw(device),
|
|
29
|
+
data,
|
|
30
|
+
FrameData(
|
|
31
|
+
frame_data.capture_time,
|
|
32
|
+
frame_data.rotation,
|
|
33
|
+
frame_data.width,
|
|
34
|
+
frame_data.height,
|
|
35
|
+
),
|
|
36
|
+
)
|
|
37
|
+
except ConnectionNotFound:
|
|
38
|
+
raise NotInCallError()
|
|
@@ -6,17 +6,19 @@ from ...exceptions import NotInCallError
|
|
|
6
6
|
from ...mtproto_required import mtproto_required
|
|
7
7
|
from ...scaffold import Scaffold
|
|
8
8
|
from ...statictypes import statictypes
|
|
9
|
+
from ...types import Direction
|
|
9
10
|
|
|
10
11
|
|
|
11
|
-
class
|
|
12
|
+
class Time(Scaffold):
|
|
12
13
|
@statictypes
|
|
13
14
|
@mtproto_required
|
|
14
|
-
async def
|
|
15
|
+
async def time(
|
|
15
16
|
self,
|
|
16
17
|
chat_id: Union[int, str],
|
|
18
|
+
direction: Direction = Direction.OUTGOING,
|
|
17
19
|
):
|
|
18
20
|
chat_id = await self.resolve_chat_id(chat_id)
|
|
19
21
|
try:
|
|
20
|
-
return await self._binding.time(chat_id)
|
|
22
|
+
return await self._binding.time(chat_id, direction.to_raw())
|
|
21
23
|
except ConnectionNotFound:
|
|
22
24
|
raise NotInCallError()
|
|
@@ -8,10 +8,10 @@ from ...scaffold import Scaffold
|
|
|
8
8
|
from ...statictypes import statictypes
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
class
|
|
11
|
+
class UnMute(Scaffold):
|
|
12
12
|
@statictypes
|
|
13
13
|
@mtproto_required
|
|
14
|
-
async def
|
|
14
|
+
async def unmute(
|
|
15
15
|
self,
|
|
16
16
|
chat_id: Union[int, str],
|
|
17
17
|
):
|
|
@@ -1,19 +1,25 @@
|
|
|
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
|
|
4
6
|
from .ping import Ping
|
|
5
7
|
from .resolve_chat_id import ResolveChatID
|
|
6
8
|
from .run import Run
|
|
7
9
|
from .start import Start
|
|
10
|
+
from .update_sources import UpdateSources
|
|
8
11
|
|
|
9
12
|
|
|
10
13
|
class Utilities(
|
|
11
14
|
CachePeer,
|
|
12
15
|
CallHolder,
|
|
13
16
|
CpuUsage,
|
|
17
|
+
JoinPresentation,
|
|
18
|
+
LogRetries,
|
|
14
19
|
Ping,
|
|
15
20
|
ResolveChatID,
|
|
16
21
|
Run,
|
|
17
22
|
Start,
|
|
23
|
+
UpdateSources,
|
|
18
24
|
):
|
|
19
25
|
pass
|
|
@@ -9,7 +9,7 @@ class CallHolder(Scaffold):
|
|
|
9
9
|
def __init__(self):
|
|
10
10
|
super().__init__()
|
|
11
11
|
self._conversions = {
|
|
12
|
-
StreamStatus.
|
|
12
|
+
StreamStatus.ACTIVE: Call.Status.ACTIVE,
|
|
13
13
|
StreamStatus.PAUSED: Call.Status.PAUSED,
|
|
14
14
|
StreamStatus.IDLING: Call.Status.IDLE,
|
|
15
15
|
}
|
|
@@ -18,7 +18,10 @@ class CallHolder(Scaffold):
|
|
|
18
18
|
async def calls(self):
|
|
19
19
|
calls_list = await self._binding.calls()
|
|
20
20
|
return Dict({
|
|
21
|
-
x: Call(
|
|
21
|
+
x: Call(
|
|
22
|
+
x, self._conversions[calls_list[x].playback],
|
|
23
|
+
self._conversions[calls_list[x].capture],
|
|
24
|
+
)
|
|
22
25
|
for x in calls_list
|
|
23
26
|
})
|
|
24
27
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
|
|
3
|
+
from ntgcalls import ConnectionError
|
|
4
|
+
from ntgcalls import TelegramServerError
|
|
5
|
+
|
|
6
|
+
from ...scaffold import Scaffold
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class JoinPresentation(Scaffold):
|
|
10
|
+
async def _join_presentation(
|
|
11
|
+
self,
|
|
12
|
+
chat_id: Union[int, str],
|
|
13
|
+
join: bool,
|
|
14
|
+
):
|
|
15
|
+
if join:
|
|
16
|
+
if chat_id in self._presentations:
|
|
17
|
+
return
|
|
18
|
+
for retries in range(4):
|
|
19
|
+
try:
|
|
20
|
+
self._wait_connect[
|
|
21
|
+
chat_id
|
|
22
|
+
] = self.loop.create_future()
|
|
23
|
+
payload = await self._binding.init_presentation(
|
|
24
|
+
chat_id,
|
|
25
|
+
)
|
|
26
|
+
result_params = await self._app.join_presentation(
|
|
27
|
+
chat_id,
|
|
28
|
+
payload,
|
|
29
|
+
)
|
|
30
|
+
await self._binding.connect(
|
|
31
|
+
chat_id,
|
|
32
|
+
result_params,
|
|
33
|
+
True,
|
|
34
|
+
)
|
|
35
|
+
await self._wait_connect[chat_id]
|
|
36
|
+
self._presentations.add(chat_id)
|
|
37
|
+
break
|
|
38
|
+
except TelegramServerError:
|
|
39
|
+
if retries == 3:
|
|
40
|
+
raise
|
|
41
|
+
self._log_retries(retries)
|
|
42
|
+
finally:
|
|
43
|
+
self._wait_connect.pop(chat_id, None)
|
|
44
|
+
elif chat_id in self._presentations:
|
|
45
|
+
try:
|
|
46
|
+
await self._binding.stop_presentation(chat_id)
|
|
47
|
+
await self._app.leave_presentation(chat_id)
|
|
48
|
+
except ConnectionError:
|
|
49
|
+
pass
|
|
50
|
+
self._presentations.discard(chat_id)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from ...scaffold import Scaffold
|
|
4
|
+
|
|
5
|
+
py_logger = logging.getLogger('pytgcalls')
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LogRetries(Scaffold):
|
|
9
|
+
@staticmethod
|
|
10
|
+
def _log_retries(r: int):
|
|
11
|
+
(py_logger.warning if r >= 1 else py_logger.info)(
|
|
12
|
+
f'Telegram is having some internal server issues. '
|
|
13
|
+
f'Retrying {r + 1} of 3',
|
|
14
|
+
)
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import logging
|
|
3
|
+
from typing import List
|
|
3
4
|
|
|
4
5
|
from ntgcalls import ConnectionError
|
|
5
6
|
from ntgcalls import ConnectionNotFound
|
|
6
7
|
from ntgcalls import ConnectionState
|
|
8
|
+
from ntgcalls import Frame as RawFrame
|
|
7
9
|
from ntgcalls import MediaState
|
|
10
|
+
from ntgcalls import NetworkInfo
|
|
11
|
+
from ntgcalls import StreamDevice
|
|
12
|
+
from ntgcalls import StreamMode
|
|
8
13
|
from ntgcalls import StreamType
|
|
9
14
|
from ntgcalls import TelegramServerError
|
|
10
15
|
|
|
@@ -16,10 +21,13 @@ from ...pytgcalls_session import PyTgCallsSession
|
|
|
16
21
|
from ...scaffold import Scaffold
|
|
17
22
|
from ...types import CallData
|
|
18
23
|
from ...types import ChatUpdate
|
|
24
|
+
from ...types import Device
|
|
25
|
+
from ...types import Direction
|
|
26
|
+
from ...types import Frame
|
|
19
27
|
from ...types import GroupCallParticipant
|
|
20
28
|
from ...types import RawCallUpdate
|
|
21
|
-
from ...types import
|
|
22
|
-
from ...types import
|
|
29
|
+
from ...types import StreamEnded
|
|
30
|
+
from ...types import StreamFrames
|
|
23
31
|
from ...types import Update
|
|
24
32
|
from ...types import UpdatedGroupCallParticipant
|
|
25
33
|
|
|
@@ -43,6 +51,7 @@ class Start(Scaffold):
|
|
|
43
51
|
if isinstance(update, ChatUpdate) and \
|
|
44
52
|
p2p_config.outgoing:
|
|
45
53
|
if update.status & ChatUpdate.Status.DISCARDED_CALL:
|
|
54
|
+
self._wait_connect.pop(chat_id, None)
|
|
46
55
|
p2p_config.wait_data.set_exception(
|
|
47
56
|
CallDeclined(
|
|
48
57
|
chat_id,
|
|
@@ -84,6 +93,68 @@ class Start(Scaffold):
|
|
|
84
93
|
participant = update.participant
|
|
85
94
|
action = participant.action
|
|
86
95
|
chat_peer = self._cache_user_peer.get(chat_id)
|
|
96
|
+
user_id = participant.user_id
|
|
97
|
+
if chat_id in self._call_sources:
|
|
98
|
+
call_sources = self._call_sources[chat_id]
|
|
99
|
+
was_camera = user_id in call_sources.camera
|
|
100
|
+
was_screen = user_id in call_sources.presentation
|
|
101
|
+
|
|
102
|
+
if was_camera != participant.video_camera:
|
|
103
|
+
if participant.video_info:
|
|
104
|
+
self._call_sources[chat_id].camera[
|
|
105
|
+
user_id
|
|
106
|
+
] = participant.video_info.endpoint
|
|
107
|
+
try:
|
|
108
|
+
await self._binding.add_incoming_video(
|
|
109
|
+
chat_id,
|
|
110
|
+
participant.video_info.endpoint,
|
|
111
|
+
participant.video_info.sources,
|
|
112
|
+
)
|
|
113
|
+
except (ConnectionNotFound, ConnectionError):
|
|
114
|
+
pass
|
|
115
|
+
elif user_id in self._call_sources[chat_id].camera:
|
|
116
|
+
try:
|
|
117
|
+
await self._binding.remove_incoming_video(
|
|
118
|
+
chat_id,
|
|
119
|
+
self._call_sources[
|
|
120
|
+
chat_id
|
|
121
|
+
].camera[user_id],
|
|
122
|
+
)
|
|
123
|
+
except (ConnectionNotFound, ConnectionError):
|
|
124
|
+
pass
|
|
125
|
+
self._call_sources[chat_id].camera.pop(
|
|
126
|
+
user_id, None,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
if was_screen != participant.screen_sharing:
|
|
130
|
+
if participant.presentation_info:
|
|
131
|
+
self._call_sources[chat_id].presentation[
|
|
132
|
+
user_id
|
|
133
|
+
] = participant.presentation_info.endpoint
|
|
134
|
+
try:
|
|
135
|
+
await self._binding.add_incoming_video(
|
|
136
|
+
chat_id,
|
|
137
|
+
participant.presentation_info.endpoint,
|
|
138
|
+
participant.presentation_info.sources,
|
|
139
|
+
)
|
|
140
|
+
except (ConnectionNotFound, ConnectionError):
|
|
141
|
+
pass
|
|
142
|
+
elif user_id in self._call_sources[
|
|
143
|
+
chat_id
|
|
144
|
+
].presentation:
|
|
145
|
+
try:
|
|
146
|
+
await self._binding.remove_incoming_video(
|
|
147
|
+
chat_id,
|
|
148
|
+
self._call_sources[
|
|
149
|
+
chat_id
|
|
150
|
+
].presentation[user_id],
|
|
151
|
+
)
|
|
152
|
+
except (ConnectionNotFound, ConnectionError):
|
|
153
|
+
pass
|
|
154
|
+
self._call_sources[chat_id].presentation.pop(
|
|
155
|
+
user_id, None,
|
|
156
|
+
)
|
|
157
|
+
|
|
87
158
|
if chat_peer:
|
|
88
159
|
is_self = BridgedClient.chat_id(
|
|
89
160
|
chat_peer,
|
|
@@ -91,7 +162,7 @@ class Start(Scaffold):
|
|
|
91
162
|
if is_self:
|
|
92
163
|
if action == GroupCallParticipant.Action.LEFT:
|
|
93
164
|
if await clear_call(chat_id):
|
|
94
|
-
await self.
|
|
165
|
+
await self._propagate(
|
|
95
166
|
ChatUpdate(
|
|
96
167
|
chat_id,
|
|
97
168
|
ChatUpdate.Status.KICKED,
|
|
@@ -119,7 +190,7 @@ class Start(Scaffold):
|
|
|
119
190
|
else:
|
|
120
191
|
self._need_unmute.discard(chat_id)
|
|
121
192
|
if not isinstance(update, RawCallUpdate):
|
|
122
|
-
await self.
|
|
193
|
+
await self._propagate(
|
|
123
194
|
update,
|
|
124
195
|
self,
|
|
125
196
|
)
|
|
@@ -141,18 +212,22 @@ class Start(Scaffold):
|
|
|
141
212
|
state.muted,
|
|
142
213
|
state.video_paused,
|
|
143
214
|
state.video_stopped,
|
|
215
|
+
state.presentation_paused,
|
|
144
216
|
self._cache_user_peer.get(chat_id),
|
|
145
217
|
)
|
|
146
218
|
except Exception as e:
|
|
147
219
|
py_logger.debug(f'SetVideoCallStatus: {e}')
|
|
148
220
|
|
|
149
|
-
async def stream_ended(
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
221
|
+
async def stream_ended(
|
|
222
|
+
chat_id: int,
|
|
223
|
+
stream_type: StreamType,
|
|
224
|
+
device: StreamDevice,
|
|
225
|
+
):
|
|
226
|
+
await self._propagate(
|
|
227
|
+
StreamEnded(
|
|
155
228
|
chat_id,
|
|
229
|
+
StreamEnded.Type.from_raw(stream_type),
|
|
230
|
+
Device.from_raw(device),
|
|
156
231
|
),
|
|
157
232
|
self,
|
|
158
233
|
)
|
|
@@ -166,7 +241,38 @@ class Start(Scaffold):
|
|
|
166
241
|
except (ConnectionError, ConnectionNotFound):
|
|
167
242
|
pass
|
|
168
243
|
|
|
169
|
-
async def
|
|
244
|
+
async def stream_frame(
|
|
245
|
+
chat_id: int,
|
|
246
|
+
mode: StreamMode,
|
|
247
|
+
device: StreamDevice,
|
|
248
|
+
frames: List[RawFrame],
|
|
249
|
+
):
|
|
250
|
+
await self._propagate(
|
|
251
|
+
StreamFrames(
|
|
252
|
+
chat_id,
|
|
253
|
+
Direction.from_raw(mode),
|
|
254
|
+
Device.from_raw(device),
|
|
255
|
+
[
|
|
256
|
+
Frame(
|
|
257
|
+
x.ssrc,
|
|
258
|
+
x.data,
|
|
259
|
+
Frame.Info(
|
|
260
|
+
x.frame_data.absolute_capture_timestamp_ms,
|
|
261
|
+
x.frame_data.width,
|
|
262
|
+
x.frame_data.height,
|
|
263
|
+
x.frame_data.rotation,
|
|
264
|
+
),
|
|
265
|
+
) for x in frames
|
|
266
|
+
],
|
|
267
|
+
),
|
|
268
|
+
self,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
async def connection_changed(
|
|
272
|
+
chat_id: int,
|
|
273
|
+
net_state: NetworkInfo,
|
|
274
|
+
):
|
|
275
|
+
state = net_state.state
|
|
170
276
|
if state == ConnectionState.CONNECTING:
|
|
171
277
|
return
|
|
172
278
|
if chat_id in self._wait_connect:
|
|
@@ -209,20 +315,22 @@ class Start(Scaffold):
|
|
|
209
315
|
self._handle_mtproto()
|
|
210
316
|
|
|
211
317
|
self._binding.on_stream_end(
|
|
212
|
-
lambda chat_id,
|
|
213
|
-
|
|
318
|
+
lambda chat_id, stream_type, device:
|
|
319
|
+
asyncio.run_coroutine_threadsafe(
|
|
320
|
+
stream_ended(chat_id, stream_type, device),
|
|
214
321
|
self.loop,
|
|
215
322
|
),
|
|
216
323
|
)
|
|
217
324
|
self._binding.on_upgrade(
|
|
218
|
-
lambda chat_id, state:
|
|
325
|
+
lambda chat_id, state:
|
|
326
|
+
asyncio.run_coroutine_threadsafe(
|
|
219
327
|
update_status(chat_id, state),
|
|
220
328
|
self.loop,
|
|
221
329
|
),
|
|
222
330
|
)
|
|
223
331
|
self._binding.on_connection_change(
|
|
224
|
-
lambda chat_id,
|
|
225
|
-
connection_changed(chat_id,
|
|
332
|
+
lambda chat_id, net_state: asyncio.run_coroutine_threadsafe(
|
|
333
|
+
connection_changed(chat_id, net_state),
|
|
226
334
|
self.loop,
|
|
227
335
|
),
|
|
228
336
|
)
|
|
@@ -232,6 +340,18 @@ class Start(Scaffold):
|
|
|
232
340
|
self.loop,
|
|
233
341
|
),
|
|
234
342
|
)
|
|
343
|
+
self._binding.on_frames(
|
|
344
|
+
lambda chat_id, mode, device, frames:
|
|
345
|
+
asyncio.run_coroutine_threadsafe(
|
|
346
|
+
stream_frame(
|
|
347
|
+
chat_id,
|
|
348
|
+
mode,
|
|
349
|
+
device,
|
|
350
|
+
frames,
|
|
351
|
+
),
|
|
352
|
+
self.loop,
|
|
353
|
+
),
|
|
354
|
+
)
|
|
235
355
|
await PyTgCallsSession().start()
|
|
236
356
|
else:
|
|
237
357
|
raise PyTgCallsAlreadyRunning()
|