Module pytgcalls.implementation
Expand source code
# tgcalls - a Python binding for C++ library by Telegram
# pytgcalls - a library connecting the Python binding with MTProto
# Copyright (C) 2020-2021 Il`ya (Marshal) <https://github.com/MarshalX>
#
# This file is part of tgcalls and pytgcalls.
#
# tgcalls and pytgcalls is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# tgcalls and pytgcalls is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License v3
# along with tgcalls. If not, see <http://www.gnu.org/licenses/>.
from pytgcalls.implementation.group_call_native import GroupCallNative
from pytgcalls.implementation.group_call import GroupCallAction
from pytgcalls.implementation.group_call import GroupCallDispatcherMixin
from pytgcalls.implementation.group_call import GroupCall
from pytgcalls.implementation.group_call_file import GroupCallFile
from pytgcalls.implementation.group_call_device import GroupCallDevice
from pytgcalls.implementation.group_call_raw import GroupCallRaw
__all__ = [
'GroupCallNative',
'GroupCall',
'GroupCallAction',
'GroupCallDispatcherMixin',
'GroupCallFile',
'GroupCallDevice',
'GroupCallRaw',
]
Sub-modules
pytgcalls.implementation.group_call
pytgcalls.implementation.group_call_device
pytgcalls.implementation.group_call_file
pytgcalls.implementation.group_call_native
pytgcalls.implementation.group_call_raw
Classes
class GroupCall (mtproto_bridge, enable_logs_to_console: bool, path_to_log_file: str, outgoing_audio_bitrate_kbit: int)
-
Helper class that provides a standard way to create an ABC using inheritance.
Expand source code
class GroupCall(ABC, GroupCallDispatcherMixin, GroupCallNative): SEND_ACTION_UPDATE_EACH = 0.45 '''How often to send speaking action to chat''' __ASYNCIO_TIMEOUT = 10 def __init__( self, mtproto_bridge, enable_logs_to_console: bool, path_to_log_file: str, outgoing_audio_bitrate_kbit: int, ): GroupCallNative.__init__( self, self.__emit_join_payload_callback, self.__network_state_updated_callback, enable_logs_to_console, path_to_log_file, outgoing_audio_bitrate_kbit, ) GroupCallDispatcherMixin.__init__(self, GroupCallAction) self.mtproto = mtproto_bridge self.mtproto.register_group_call_native_callback( self._group_call_participants_update_callback, self._group_call_update_callback ) self.invite_hash = None '''Hash from invite link to join as speaker''' self.enable_action = True '''Is enable sending of speaking action''' self.is_connected = False '''Is connected to voice chat via tgcalls''' self.__is_stop_requested = False self.__emit_join_payload_event = None self.__is_muted = True async def _group_call_participants_update_callback(self, update: UpdateGroupCallParticipantsWrapper): logger.debug('Group call participants update...') logger.debug(update) self.trigger_handlers(GroupCallAction.PARTICIPANT_LIST_UPDATED, self, update.participants) for participant in update.participants: ssrc = uint_ssrc(participant.source) # maybe (if needed) set unmute status on server side after allowing to speak by admin # also mb there is need a some delay after getting update cuz server sometimes cant handle editing properly if participant.is_self and participant.can_self_unmute: if not self.__is_muted: await self.edit_group_call(muted=False) if participant.peer == self.mtproto.join_as and ssrc != self.mtproto.my_ssrc: logger.debug(f'Not equal ssrc. Expected: {ssrc}. Actual: {self.mtproto.my_ssrc}.') await self.reconnect() async def _group_call_update_callback(self, update: UpdateGroupCallWrapper): logger.debug('Group call update...') logger.debug(update) if isinstance(update.call, GroupCallDiscardedWrapper): logger.debug('Group call discarded.') await self.stop() elif update.call.params: self.__set_join_response_payload(update.call.params.data) def __set_join_response_payload(self, payload): logger.debug('Set join response payload...') if self.__is_stop_requested: logger.debug('Set payload rejected by a stop request.') return self._set_connection_mode(tgcalls.GroupConnectionMode.GroupConnectionModeRtc) self._set_join_response_payload(payload) logger.debug('Join response payload was set.') def __emit_join_payload_callback(self, payload): logger.debug('Emit join payload callback...') if self.__is_stop_requested: logger.debug('Join group call rejected by a stop request.') return if self.mtproto.group_call is None: logger.debug('Group Call is None.') return async def _(): try: def pre_update_processing(): logger.debug(f'Set my ssrc to {payload.audioSsrc}.') self.mtproto.set_my_ssrc(payload.audioSsrc) await self.mtproto.join_group_call( self.invite_hash, payload.json, muted=True, pre_update_processing=pre_update_processing ) if self.__emit_join_payload_event: self.__emit_join_payload_event.set() logger.debug( f'Successfully connected to VC with ' f'ssrc={self.mtproto.my_ssrc} ' f'as {type(self.mtproto.join_as).__name__}.' ) except GroupcallSsrcDuplicateMuch: logger.debug('Duplicate SSRC.') await self.reconnect() asyncio.ensure_future(_(), loop=self.mtproto.get_event_loop()) def __network_state_updated_callback(self, state: bool): logger.debug('Network state updated...') if self.is_connected == state: logger.debug('Network state is same. Do nothing.') return self.is_connected = state if self.is_connected: asyncio.ensure_future(self.set_is_mute(False), loop=self.mtproto.get_event_loop()) if self.enable_action: self.__start_status_worker() self.trigger_handlers(GroupCallAction.NETWORK_STATUS_CHANGED, self, state) logger.debug(f'New network state is {self.is_connected}.') def __start_status_worker(self): async def worker(): logger.debug('Start status (call action) worker...') while self.is_connected: await self.mtproto.send_speaking_group_call_action() await asyncio.sleep(self.SEND_ACTION_UPDATE_EACH) asyncio.ensure_future(worker(), loop=self.mtproto.get_event_loop()) async def start(self, group, join_as=None, invite_hash: Optional[str] = None, enable_action=True): """Start voice chat (join and play/record from initial values). Note: Disconnect from current voice chat and connect to the new one. Multiple instances of `GroupCall` must be created for multiple voice chats at the same time. Join as by default is personal account. Args: group (`InputPeerChannel` | `InputPeerChat` | `str` | `int`): Chat ID in any form. join_as (`InputPeer` | `str` | `int`, optional): How to present yourself in participants list. invite_hash (`str`, optional): Hash from speaker invite link. enable_action (`bool`, optional): Is enables sending of speaking action. """ self.__is_stop_requested = False self.enable_action = enable_action group_call = await self.mtproto.get_and_set_group_call(group) if group_call is None: raise GroupCallNotFoundError('Chat without a voice chat') # mb move in other place. save plain join_as arg and use it in JoinGroupCall # but for now it works as optimization of requests # we resolve join_as only when try to connect # it doesnt call resolve on reconnect await self.mtproto.resolve_and_set_join_as(join_as) self.invite_hash = invite_hash self.mtproto.re_register_update_handlers() # when trying to connect to another chat or with another join_as if self.is_group_call_native_created(): await self.reconnect() # the first start else: self._setup_and_start_group_call() async def stop(self): """Properly stop tgcalls, remove MTProto handler, leave from server side.""" if not self.is_group_call_native_created(): logger.debug('Group call is not started, so there\'s nothing to stop.') return self.__is_stop_requested = True logger.debug('Stop requested.') self.mtproto.unregister_update_handlers() # to bypass recreating of outgoing audio channel self._set_is_mute(True) self._set_connection_mode(tgcalls.GroupConnectionMode.GroupConnectionModeNone) on_disconnect_event = asyncio.Event() async def post_disconnect(): await self.leave_current_group_call() self.mtproto.reset() self.__is_stop_requested = False async def on_disconnect(obj, is_connected): if is_connected: return obj._stop_audio_device_module() # need for normal waiting of stopping audio devices # destroying of webrtc client during .stop not needed yet # because we a working in the same native instance # and can reuse tis client for another connections. # In any case now its possible to reset group call ptr # self.__native_instance.stopGroupCall() await post_disconnect() obj.remove_handler(on_disconnect, GroupCallAction.NETWORK_STATUS_CHANGED) on_disconnect_event.set() if self.is_connected: self.add_handler(on_disconnect, GroupCallAction.NETWORK_STATUS_CHANGED) await asyncio.wait_for(on_disconnect_event.wait(), timeout=self.__ASYNCIO_TIMEOUT) else: await post_disconnect() logger.debug('GroupCall stopped properly.') async def reconnect(self): """Connect to voice chat using the same native instance.""" logger.debug('Reconnecting...') if not self.mtproto.group_call: raise NotConnectedError("You don't connected to voice chat.") self._set_connection_mode(tgcalls.GroupConnectionMode.GroupConnectionModeNone) self._emit_join_payload(self.__emit_join_payload_callback) # during the .stop we stop audio device module. Need to restart self.restart_recording() self.restart_playout() # cuz native instance doesnt block python self.__emit_join_payload_event = asyncio.Event() await asyncio.wait_for(self.__emit_join_payload_event.wait(), timeout=self.__ASYNCIO_TIMEOUT) async def leave_current_group_call(self): """Leave group call from server side (MTProto part).""" logger.debug('Try to leave the current group call...') try: await self.mtproto.leave_current_group_call() except Exception as e: logger.warning("Couldn't leave the group call. But no worries, you'll get removed from it in seconds.") logger.debug(e) else: logger.debug('Completely left the current group call.') async def edit_group_call(self, volume: int = None, muted=False): """Edit own settings of group call. Note: There is bug where you can try to pass `volume=100`. Args: volume (`int`): Volume. muted (`bool`): Is muted. """ await self.edit_group_call_member(self.mtproto.join_as, volume, muted) async def edit_group_call_member(self, peer, volume: int = None, muted=False): """Edit setting of user in voice chat (required voice chat management permission). Note: There is bug where you can try to pass `volume=100`. Args: peer (`InputPeer`): Participant of voice chat. volume (`int`): Volume. muted (`bool`): Is muted. """ volume = max(1, volume * 100) if volume is not None else None await self.mtproto.edit_group_call_member(peer, volume, muted) async def set_is_mute(self, is_muted: bool): """Set is mute. Args: is_muted (`bool`): Is muted. """ self.__is_muted = is_muted self._set_is_mute(is_muted) logger.debug(f'Set is muted on server side. New value: {is_muted}.') await self.edit_group_call(muted=is_muted) async def set_my_volume(self, volume): """Set volume for current client. Note: Volume value only can be in 1-200 range. There is auto normalization. Args: volume (`int` | `str` | `float`): Volume. """ # Required "Manage Voice Chats" admin permission volume = max(1, min(int(volume), 200)) logger.debug(f'Set volume to: {volume}.') await self.edit_group_call(volume) self._set_volume(uint_ssrc(self.mtproto.my_ssrc), volume / 100) # shortcuts for easy access in callbacks of events @property def client(self): return self.mtproto.client @property def full_chat(self): return self.mtproto.full_chat @property def chat_peer(self): return self.mtproto.chat_peer @property def group_call(self): return self.mtproto.group_call @property def my_ssrc(self): return self.mtproto.my_ssrc @property def my_peer(self): return self.mtproto.my_peer @property def join_as(self): return self.mtproto.join_as
Ancestors
- abc.ABC
- GroupCallDispatcherMixin
- pytgcalls.dispatcher.dispatcher_mixin.DispatcherMixin
- GroupCallNative
Subclasses
Class variables
var SEND_ACTION_UPDATE_EACH
-
How often to send speaking action to chat
Instance variables
var chat_peer
-
Expand source code
@property def chat_peer(self): return self.mtproto.chat_peer
var client
-
Expand source code
@property def client(self): return self.mtproto.client
var enable_action
-
Is enable sending of speaking action
var full_chat
-
Expand source code
@property def full_chat(self): return self.mtproto.full_chat
var group_call
-
Expand source code
@property def group_call(self): return self.mtproto.group_call
var invite_hash
-
Hash from invite link to join as speaker
var is_connected
-
Is connected to voice chat via tgcalls
var join_as
-
Expand source code
@property def join_as(self): return self.mtproto.join_as
var my_peer
-
Expand source code
@property def my_peer(self): return self.mtproto.my_peer
var my_ssrc
-
Expand source code
@property def my_ssrc(self): return self.mtproto.my_ssrc
Methods
async def edit_group_call(self, volume: int = None, muted=False)
-
Edit own settings of group call.
Note
There is bug where you can try to pass
volume=100
.Args
volume (
int
): Volume. muted (bool
): Is muted.Expand source code
async def edit_group_call(self, volume: int = None, muted=False): """Edit own settings of group call. Note: There is bug where you can try to pass `volume=100`. Args: volume (`int`): Volume. muted (`bool`): Is muted. """ await self.edit_group_call_member(self.mtproto.join_as, volume, muted)
async def edit_group_call_member(self, peer, volume: int = None, muted=False)
-
Edit setting of user in voice chat (required voice chat management permission).
Note
There is bug where you can try to pass
volume=100
.Args
peer (
InputPeer
): Participant of voice chat. volume (int
): Volume. muted (bool
): Is muted.Expand source code
async def edit_group_call_member(self, peer, volume: int = None, muted=False): """Edit setting of user in voice chat (required voice chat management permission). Note: There is bug where you can try to pass `volume=100`. Args: peer (`InputPeer`): Participant of voice chat. volume (`int`): Volume. muted (`bool`): Is muted. """ volume = max(1, volume * 100) if volume is not None else None await self.mtproto.edit_group_call_member(peer, volume, muted)
async def leave_current_group_call(self)
-
Leave group call from server side (MTProto part).
Expand source code
async def leave_current_group_call(self): """Leave group call from server side (MTProto part).""" logger.debug('Try to leave the current group call...') try: await self.mtproto.leave_current_group_call() except Exception as e: logger.warning("Couldn't leave the group call. But no worries, you'll get removed from it in seconds.") logger.debug(e) else: logger.debug('Completely left the current group call.')
def on_network_status_changed(self, func: Callable) ‑> Callable
-
Inherited from:
GroupCallDispatcherMixin
.on_network_status_changed
When a status of network will be changed …
def on_participant_list_updated(self, func: Callable) ‑> Callable
-
Inherited from:
GroupCallDispatcherMixin
.on_participant_list_updated
When a list of participant will be updated …
def print_available_playout_devices(self)
-
Inherited from:
GroupCallNative
.print_available_playout_devices
Print name and guid of available playout audio devices in system. Just helper method …
def print_available_recording_devices(self)
-
Inherited from:
GroupCallNative
.print_available_recording_devices
Print name and guid of available recording audio devices in system. Just helper method …
async def reconnect(self)
-
Connect to voice chat using the same native instance.
Expand source code
async def reconnect(self): """Connect to voice chat using the same native instance.""" logger.debug('Reconnecting...') if not self.mtproto.group_call: raise NotConnectedError("You don't connected to voice chat.") self._set_connection_mode(tgcalls.GroupConnectionMode.GroupConnectionModeNone) self._emit_join_payload(self.__emit_join_payload_callback) # during the .stop we stop audio device module. Need to restart self.restart_recording() self.restart_playout() # cuz native instance doesnt block python self.__emit_join_payload_event = asyncio.Event() await asyncio.wait_for(self.__emit_join_payload_event.wait(), timeout=self.__ASYNCIO_TIMEOUT)
async def set_is_mute(self, is_muted: bool)
-
Set is mute.
Args
is_muted (
bool
): Is muted.Expand source code
async def set_is_mute(self, is_muted: bool): """Set is mute. Args: is_muted (`bool`): Is muted. """ self.__is_muted = is_muted self._set_is_mute(is_muted) logger.debug(f'Set is muted on server side. New value: {is_muted}.') await self.edit_group_call(muted=is_muted)
async def set_my_volume(self, volume)
-
Set volume for current client.
Note
Volume value only can be in 1-200 range. There is auto normalization.
Args
volume (
int
|str
|float
): Volume.Expand source code
async def set_my_volume(self, volume): """Set volume for current client. Note: Volume value only can be in 1-200 range. There is auto normalization. Args: volume (`int` | `str` | `float`): Volume. """ # Required "Manage Voice Chats" admin permission volume = max(1, min(int(volume), 200)) logger.debug(f'Set volume to: {volume}.') await self.edit_group_call(volume) self._set_volume(uint_ssrc(self.mtproto.my_ssrc), volume / 100)
async def start(self, group, join_as=None, invite_hash: Optional[str] = None, enable_action=True)
-
Start voice chat (join and play/record from initial values).
Note
Disconnect from current voice chat and connect to the new one. Multiple instances of
GroupCall
must be created for multiple voice chats at the same time. Join as by default is personal account.Args
group (
InputPeerChannel
|InputPeerChat
|str
|int
): Chat ID in any form. join_as (InputPeer
|str
|int
, optional): How to present yourself in participants list. invite_hash (str
, optional): Hash from speaker invite link. enable_action (bool
, optional): Is enables sending of speaking action.Expand source code
async def start(self, group, join_as=None, invite_hash: Optional[str] = None, enable_action=True): """Start voice chat (join and play/record from initial values). Note: Disconnect from current voice chat and connect to the new one. Multiple instances of `GroupCall` must be created for multiple voice chats at the same time. Join as by default is personal account. Args: group (`InputPeerChannel` | `InputPeerChat` | `str` | `int`): Chat ID in any form. join_as (`InputPeer` | `str` | `int`, optional): How to present yourself in participants list. invite_hash (`str`, optional): Hash from speaker invite link. enable_action (`bool`, optional): Is enables sending of speaking action. """ self.__is_stop_requested = False self.enable_action = enable_action group_call = await self.mtproto.get_and_set_group_call(group) if group_call is None: raise GroupCallNotFoundError('Chat without a voice chat') # mb move in other place. save plain join_as arg and use it in JoinGroupCall # but for now it works as optimization of requests # we resolve join_as only when try to connect # it doesnt call resolve on reconnect await self.mtproto.resolve_and_set_join_as(join_as) self.invite_hash = invite_hash self.mtproto.re_register_update_handlers() # when trying to connect to another chat or with another join_as if self.is_group_call_native_created(): await self.reconnect() # the first start else: self._setup_and_start_group_call()
async def stop(self)
-
Properly stop tgcalls, remove MTProto handler, leave from server side.
Expand source code
async def stop(self): """Properly stop tgcalls, remove MTProto handler, leave from server side.""" if not self.is_group_call_native_created(): logger.debug('Group call is not started, so there\'s nothing to stop.') return self.__is_stop_requested = True logger.debug('Stop requested.') self.mtproto.unregister_update_handlers() # to bypass recreating of outgoing audio channel self._set_is_mute(True) self._set_connection_mode(tgcalls.GroupConnectionMode.GroupConnectionModeNone) on_disconnect_event = asyncio.Event() async def post_disconnect(): await self.leave_current_group_call() self.mtproto.reset() self.__is_stop_requested = False async def on_disconnect(obj, is_connected): if is_connected: return obj._stop_audio_device_module() # need for normal waiting of stopping audio devices # destroying of webrtc client during .stop not needed yet # because we a working in the same native instance # and can reuse tis client for another connections. # In any case now its possible to reset group call ptr # self.__native_instance.stopGroupCall() await post_disconnect() obj.remove_handler(on_disconnect, GroupCallAction.NETWORK_STATUS_CHANGED) on_disconnect_event.set() if self.is_connected: self.add_handler(on_disconnect, GroupCallAction.NETWORK_STATUS_CHANGED) await asyncio.wait_for(on_disconnect_event.wait(), timeout=self.__ASYNCIO_TIMEOUT) else: await post_disconnect() logger.debug('GroupCall stopped properly.')
class GroupCallAction
-
Expand source code
class GroupCallAction: NETWORK_STATUS_CHANGED = Action() '''When a status of network will be changed.''' PARTICIPANT_LIST_UPDATED = Action() '''When a list of participant will be updated.'''
Subclasses
Class variables
var NETWORK_STATUS_CHANGED
-
When a status of network will be changed.
var PARTICIPANT_LIST_UPDATED
-
When a list of participant will be updated.
class GroupCallDevice (mtproto_bridge, audio_input_device: Optional[str] = None, audio_output_device: Optional[str] = None, enable_logs_to_console=False, path_to_log_file=None, outgoing_audio_bitrate_kbit=128)
-
Helper class that provides a standard way to create an ABC using inheritance.
Expand source code
class GroupCallDevice(GroupCall): def __init__( self, mtproto_bridge, audio_input_device: Optional[str] = None, audio_output_device: Optional[str] = None, enable_logs_to_console=False, path_to_log_file=None, outgoing_audio_bitrate_kbit=128, ): super().__init__(mtproto_bridge, enable_logs_to_console, path_to_log_file, outgoing_audio_bitrate_kbit) self.__is_playout_paused = False self.__is_recording_paused = False self.__audio_input_device = audio_input_device or '' self.__audio_output_device = audio_output_device or '' def _setup_and_start_group_call(self): self._start_native_group_call(self.__audio_input_device, self.__audio_output_device) @property def audio_input_device(self): """Get audio input device name or GUID Note: To get system recording device list you can use `get_recording_devices()` method. """ return self.__audio_input_device @audio_input_device.setter def audio_input_device(self, name=None): self.set_audio_input_device(name) @property def audio_output_device(self): """Get audio output device name or GUID Note: To get system playout device list you can use `get_playout_devices()` method. """ return self.__audio_output_device @audio_output_device.setter def audio_output_device(self, name=None): self.set_audio_output_device(name)
Ancestors
- GroupCall
- abc.ABC
- GroupCallDispatcherMixin
- pytgcalls.dispatcher.dispatcher_mixin.DispatcherMixin
- GroupCallNative
Class variables
var SEND_ACTION_UPDATE_EACH
-
Inherited from:
GroupCall
.SEND_ACTION_UPDATE_EACH
How often to send speaking action to chat
Instance variables
var audio_input_device
-
Get audio input device name or GUID
Note
To get system recording device list you can use
get_recording_devices()
method.Expand source code
@property def audio_input_device(self): """Get audio input device name or GUID Note: To get system recording device list you can use `get_recording_devices()` method. """ return self.__audio_input_device
var audio_output_device
-
Get audio output device name or GUID
Note
To get system playout device list you can use
get_playout_devices()
method.Expand source code
@property def audio_output_device(self): """Get audio output device name or GUID Note: To get system playout device list you can use `get_playout_devices()` method. """ return self.__audio_output_device
var enable_action
-
Inherited from:
GroupCall
.enable_action
Is enable sending of speaking action
var invite_hash
-
Inherited from:
GroupCall
.invite_hash
Hash from invite link to join as speaker
var is_connected
-
Inherited from:
GroupCall
.is_connected
Is connected to voice chat via tgcalls
Methods
async def edit_group_call(self, volume: int = None, muted=False)
-
Inherited from:
GroupCall
.edit_group_call
Edit own settings of group call …
async def edit_group_call_member(self, peer, volume: int = None, muted=False)
-
Inherited from:
GroupCall
.edit_group_call_member
Edit setting of user in voice chat (required voice chat management permission) …
async def leave_current_group_call(self)
-
Inherited from:
GroupCall
.leave_current_group_call
Leave group call from server side (MTProto part).
def on_network_status_changed(self, func: Callable) ‑> Callable
-
Inherited from:
GroupCall
.on_network_status_changed
When a status of network will be changed …
def on_participant_list_updated(self, func: Callable) ‑> Callable
-
Inherited from:
GroupCall
.on_participant_list_updated
When a list of participant will be updated …
def print_available_playout_devices(self)
-
Inherited from:
GroupCall
.print_available_playout_devices
Print name and guid of available playout audio devices in system. Just helper method …
def print_available_recording_devices(self)
-
Inherited from:
GroupCall
.print_available_recording_devices
Print name and guid of available recording audio devices in system. Just helper method …
async def reconnect(self)
-
Inherited from:
GroupCall
.reconnect
Connect to voice chat using the same native instance.
async def set_is_mute(self, is_muted: bool)
-
Inherited from:
GroupCall
.set_is_mute
Set is mute …
async def set_my_volume(self, volume)
-
Inherited from:
GroupCall
.set_my_volume
Set volume for current client …
async def start(self, group, join_as=None, invite_hash: Optional[str] = None, enable_action=True)
-
Inherited from:
GroupCall
.start
Start voice chat (join and play/record from initial values) …
async def stop(self)
-
Inherited from:
GroupCall
.stop
Properly stop tgcalls, remove MTProto handler, leave from server side.
class GroupCallDispatcherMixin (actions)
-
Expand source code
class GroupCallDispatcherMixin(DispatcherMixin): def on_network_status_changed(self, func: Callable) -> Callable: """When a status of network will be changed. Args: func (`Callable`): A functions that accept group_call and is_connected args. Returns: `Callable`: passed to args callback function. """ return self.add_handler(func, GroupCallAction.NETWORK_STATUS_CHANGED) def on_participant_list_updated(self, func: Callable) -> Callable: """When a list of participant will be updated. Args: func (`Callable`): A functions that accept group_call and participants args. Note: The `participants` arg is a `list` of `GroupCallParticipantWrapper`. It contains only updated participants! It's not a list of all participants! Returns: `Callable`: passed to args callback function. """ return self.add_handler(func, GroupCallAction.PARTICIPANT_LIST_UPDATED)
Ancestors
- pytgcalls.dispatcher.dispatcher_mixin.DispatcherMixin
Subclasses
Methods
def on_network_status_changed(self, func: Callable) ‑> Callable
-
When a status of network will be changed.
Args
func (
Callable
): A functions that accept group_call and is_connected args.Returns
Callable
: passed to args callback function.Expand source code
def on_network_status_changed(self, func: Callable) -> Callable: """When a status of network will be changed. Args: func (`Callable`): A functions that accept group_call and is_connected args. Returns: `Callable`: passed to args callback function. """ return self.add_handler(func, GroupCallAction.NETWORK_STATUS_CHANGED)
def on_participant_list_updated(self, func: Callable) ‑> Callable
-
When a list of participant will be updated.
Args
func (
Callable
): A functions that accept group_call and participants args.Note
The
participants
arg is alist
ofGroupCallParticipantWrapper
. It contains only updated participants! It's not a list of all participants!Returns
Callable
: passed to args callback function.Expand source code
def on_participant_list_updated(self, func: Callable) -> Callable: """When a list of participant will be updated. Args: func (`Callable`): A functions that accept group_call and participants args. Note: The `participants` arg is a `list` of `GroupCallParticipantWrapper`. It contains only updated participants! It's not a list of all participants! Returns: `Callable`: passed to args callback function. """ return self.add_handler(func, GroupCallAction.PARTICIPANT_LIST_UPDATED)
class GroupCallFile (mtproto_bridge, input_filename: str = None, output_filename: str = None, play_on_repeat=True, enable_logs_to_console=False, path_to_log_file=None, outgoing_audio_bitrate_kbit=128)
-
Helper class that provides a standard way to create an ABC using inheritance.
Expand source code
class GroupCallFile(GroupCall, GroupCallFileDispatcherMixin): def __init__( self, mtproto_bridge, input_filename: str = None, output_filename: str = None, play_on_repeat=True, enable_logs_to_console=False, path_to_log_file=None, outgoing_audio_bitrate_kbit=128, ): super().__init__(mtproto_bridge, enable_logs_to_console, path_to_log_file, outgoing_audio_bitrate_kbit) super(GroupCallFileDispatcherMixin, self).__init__(GroupCallFileAction) self.play_on_repeat = play_on_repeat '''When the file ends, play it again''' self.__is_playout_paused = False self.__is_recording_paused = False self.__input_filename = input_filename or '' self.__output_filename = output_filename or '' self.__file_audio_device_descriptor = None def __create_and_return_file_audio_device_descriptor(self): self.__file_audio_device_descriptor = tgcalls.FileAudioDeviceDescriptor() self.__file_audio_device_descriptor.getInputFilename = self.__get_input_filename_callback self.__file_audio_device_descriptor.getOutputFilename = self.__get_output_filename_callback self.__file_audio_device_descriptor.isEndlessPlayout = self.__is_endless_playout_callback self.__file_audio_device_descriptor.isPlayoutPaused = self.__is_playout_paused_callback self.__file_audio_device_descriptor.isRecordingPaused = self.__is_recording_paused_callback self.__file_audio_device_descriptor.playoutEndedCallback = self.__playout_ended_callback return self.__file_audio_device_descriptor def _setup_and_start_group_call(self): self._start_native_group_call(self.__create_and_return_file_audio_device_descriptor()) def stop_playout(self): """Stop playing of file.""" self.input_filename = '' def stop_output(self): """Stop recording to file.""" self.output_filename = '' @property def input_filename(self): """Input filename (or path) to play.""" return self.__input_filename @input_filename.setter def input_filename(self, filename): self.__input_filename = filename or '' if self.is_connected: self.restart_playout() @property def output_filename(self): """Output filename (or path) to record.""" return self.__output_filename @output_filename.setter def output_filename(self, filename): self.__output_filename = filename or '' if self.is_connected: self.restart_recording() def pause_playout(self): """Pause playout (playing from file).""" self.__is_playout_paused = True def resume_playout(self): """Resume playout (playing from file).""" self.__is_playout_paused = False def pause_recording(self): """Pause recording (output to file).""" self.__is_recording_paused = True def resume_recording(self): """Resume recording (output to file).""" self.__is_recording_paused = False def __get_input_filename_callback(self): return self.__input_filename def __get_output_filename_callback(self): return self.__output_filename def __is_endless_playout_callback(self): return self.play_on_repeat def __is_playout_paused_callback(self): return self.__is_playout_paused def __is_recording_paused_callback(self): return self.__is_recording_paused def __playout_ended_callback(self, input_filename: str): self.trigger_handlers(GroupCallFileAction.PLAYOUT_ENDED, self, input_filename)
Ancestors
- GroupCall
- abc.ABC
- GroupCallFileDispatcherMixin
- GroupCallDispatcherMixin
- pytgcalls.dispatcher.dispatcher_mixin.DispatcherMixin
- GroupCallNative
Class variables
var SEND_ACTION_UPDATE_EACH
-
Inherited from:
GroupCall
.SEND_ACTION_UPDATE_EACH
How often to send speaking action to chat
Instance variables
var enable_action
-
Inherited from:
GroupCall
.enable_action
Is enable sending of speaking action
var input_filename
-
Input filename (or path) to play.
Expand source code
@property def input_filename(self): """Input filename (or path) to play.""" return self.__input_filename
var invite_hash
-
Inherited from:
GroupCall
.invite_hash
Hash from invite link to join as speaker
var is_connected
-
Inherited from:
GroupCall
.is_connected
Is connected to voice chat via tgcalls
var output_filename
-
Output filename (or path) to record.
Expand source code
@property def output_filename(self): """Output filename (or path) to record.""" return self.__output_filename
var play_on_repeat
-
When the file ends, play it again
Methods
async def edit_group_call(self, volume: int = None, muted=False)
-
Inherited from:
GroupCall
.edit_group_call
Edit own settings of group call …
async def edit_group_call_member(self, peer, volume: int = None, muted=False)
-
Inherited from:
GroupCall
.edit_group_call_member
Edit setting of user in voice chat (required voice chat management permission) …
async def leave_current_group_call(self)
-
Inherited from:
GroupCall
.leave_current_group_call
Leave group call from server side (MTProto part).
def on_network_status_changed(self, func: Callable) ‑> Callable
-
Inherited from:
GroupCall
.on_network_status_changed
When a status of network will be changed …
def on_participant_list_updated(self, func: Callable) ‑> Callable
-
Inherited from:
GroupCall
.on_participant_list_updated
When a list of participant will be updated …
def on_playout_ended(self, func: Callable) ‑> Callable
-
Inherited from:
GroupCallFileDispatcherMixin
.on_playout_ended
When a input file is ended …
def pause_playout(self)
-
Pause playout (playing from file).
Expand source code
def pause_playout(self): """Pause playout (playing from file).""" self.__is_playout_paused = True
def pause_recording(self)
-
Pause recording (output to file).
Expand source code
def pause_recording(self): """Pause recording (output to file).""" self.__is_recording_paused = True
def print_available_playout_devices(self)
-
Inherited from:
GroupCall
.print_available_playout_devices
Print name and guid of available playout audio devices in system. Just helper method …
def print_available_recording_devices(self)
-
Inherited from:
GroupCall
.print_available_recording_devices
Print name and guid of available recording audio devices in system. Just helper method …
async def reconnect(self)
-
Inherited from:
GroupCall
.reconnect
Connect to voice chat using the same native instance.
def resume_playout(self)
-
Resume playout (playing from file).
Expand source code
def resume_playout(self): """Resume playout (playing from file).""" self.__is_playout_paused = False
def resume_recording(self)
-
Resume recording (output to file).
Expand source code
def resume_recording(self): """Resume recording (output to file).""" self.__is_recording_paused = False
async def set_is_mute(self, is_muted: bool)
-
Inherited from:
GroupCall
.set_is_mute
Set is mute …
async def set_my_volume(self, volume)
-
Inherited from:
GroupCall
.set_my_volume
Set volume for current client …
async def start(self, group, join_as=None, invite_hash: Optional[str] = None, enable_action=True)
-
Inherited from:
GroupCall
.start
Start voice chat (join and play/record from initial values) …
async def stop(self)
-
Inherited from:
GroupCall
.stop
Properly stop tgcalls, remove MTProto handler, leave from server side.
def stop_output(self)
-
Stop recording to file.
Expand source code
def stop_output(self): """Stop recording to file.""" self.output_filename = ''
def stop_playout(self)
-
Stop playing of file.
Expand source code
def stop_playout(self): """Stop playing of file.""" self.input_filename = ''
class GroupCallNative (emit_join_payload_callback, network_state_updated_callback, enable_logs_to_console: bool, path_to_log_file: str, outgoing_audio_bitrate_kbit: int)
-
Create NativeInstance of tgcalls C++ part.
Args
enable_logs_to_console (
bool
): Is enable logs to stderr from tgcalls. path_to_log_file (str
, optional): Path to log file for logs of tgcalls.Expand source code
class GroupCallNative: def __init__( self, emit_join_payload_callback, network_state_updated_callback, enable_logs_to_console: bool, path_to_log_file: str, outgoing_audio_bitrate_kbit: int, ): """Create NativeInstance of tgcalls C++ part. Args: enable_logs_to_console (`bool`): Is enable logs to stderr from tgcalls. path_to_log_file (`str`, optional): Path to log file for logs of tgcalls. """ # bypass None value if not path_to_log_file: path_to_log_file = '' logger.debug('Create a new native instance...') self.__native_instance = tgcalls.NativeInstance(enable_logs_to_console, path_to_log_file) self.__native_instance.setupGroupCall( emit_join_payload_callback, network_state_updated_callback, outgoing_audio_bitrate_kbit, ) logger.debug('Native instance created.') def is_group_call_native_created(self): return self.__native_instance.isGroupCallNativeCreated() def _setup_and_start_group_call(self): raise NotImplementedError() @if_native_instance_created def _set_connection_mode(self, mode: tgcalls.GroupConnectionMode, keep_broadcast_if_was_enabled=False): logger.debug(f'Set native connection mode {mode}.') self.__native_instance.setConnectionMode(mode, keep_broadcast_if_was_enabled) @if_native_instance_created def _emit_join_payload(self, callback): logger.debug(f'Trigger native emit join payload.') self.__native_instance.emitJoinPayload(callback) def _start_native_group_call(self, *args): logger.debug('Start native group call...') self.__native_instance.startGroupCall(*args) @if_native_instance_created def _emit_join_payload(self, callback): logger.debug('Emit native join payload.') self.__native_instance.emitJoinPayload(callback) @if_native_instance_created def _set_join_response_payload(self, payload): logger.debug('Set native join response payload...') self.__native_instance.setJoinResponsePayload(payload) @if_native_instance_created def _set_is_mute(self, is_muted: bool): logger.debug(f'Set is muted on native instance side. New value: {is_muted}.') self.__native_instance.setIsMuted(is_muted) @if_native_instance_created def _set_volume(self, ssrc, volume): logger.debug(f'Set native volume for {ssrc} to {volume}.') self.__native_instance.setVolume(ssrc, volume) @if_native_instance_created def _stop_audio_device_module(self): logger.debug(f'Stop audio device module.') self.__native_instance.stopAudioDeviceModule() @if_native_instance_created def _start_audio_device_module(self): logger.debug(f'Start audio device module.') self.__native_instance.startAudioDeviceModule() @if_native_instance_created def get_playout_devices(self) -> List['tgcalls.AudioDevice']: """Get available playout audio devices in the system. Note: `tgcalls.AudioDevice` have 2 attributes: name, guid. """ logger.debug('Get native playout devices.') return self.__native_instance.getPlayoutDevices() @if_native_instance_created def get_recording_devices(self) -> List['tgcalls.AudioDevice']: """Get available recording audio devices in the system. Note: `tgcalls.AudioDevice` have 2 attributes: name, guid. """ logger.debug('Get native recording devices.') return self.__native_instance.getRecordingDevices() @if_native_instance_created def set_audio_input_device(self, name: Optional[str] = None): """Set audio input device. Note: If `name` is `None`, will use default system device. And this is works only at first device initialization time! Args: name (`str`): Name or GUID of device. """ logger.debug(f'Set native audio input device to {name}.') self.__native_instance.setAudioInputDevice(name or '') @if_native_instance_created def set_audio_output_device(self, name: Optional[str] = None): """Set audio output device. Note: If `name` is `None`, will use default system device. And this is works only at first device initialization time! Args: name (`str`): Name or GUID of device. """ logger.debug(f'Set native audio output device to {name}.') self.__native_instance.setAudioOutputDevice(name or '') @if_native_instance_created def restart_playout(self): """Start play current input file from start or just reload file audio device. Note: Device restart needed to apply new filename in tgcalls. """ logger.debug(f'Restart native audio input device.') self.__native_instance.restartAudioInputDevice() @if_native_instance_created def restart_recording(self): """Start recording to output file from begin or just restart recording device. Note: Device restart needed to apply new filename in tgcalls. """ logger.debug(f'Restart native audio output device.') self.__native_instance.restartAudioOutputDevice() # legacy below def print_available_playout_devices(self): """Print name and guid of available playout audio devices in system. Just helper method Note: You should use this method after calling .start()! """ warnings.warn("It's a deprecated method. Use .get_recording_devices() instead", DeprecationWarning, 2) for device in self.get_playout_devices(): print(f'Playout device \n name: {device.name} \n guid: {device.guid}') def print_available_recording_devices(self): """Print name and guid of available recording audio devices in system. Just helper method Note: You should use this method after calling .start()! """ warnings.warn("It's a deprecated method. Use .get_playout_devices() instead", DeprecationWarning, 2) for device in self.get_recording_devices(): print(f'Recording device \n name: {device.name} \n guid: {device.guid}')
Subclasses
Methods
def get_playout_devices(self, *args, **kwargs)
-
Expand source code
def wrapper(self, *args, **kwargs): if self.is_group_call_native_created(): return func(self, *args, **kwargs) else: raise CallBeforeStartError("You can't use this method before calling .start()")
def get_recording_devices(self, *args, **kwargs)
-
Expand source code
def wrapper(self, *args, **kwargs): if self.is_group_call_native_created(): return func(self, *args, **kwargs) else: raise CallBeforeStartError("You can't use this method before calling .start()")
def is_group_call_native_created(self)
-
Expand source code
def is_group_call_native_created(self): return self.__native_instance.isGroupCallNativeCreated()
def print_available_playout_devices(self)
-
Print name and guid of available playout audio devices in system. Just helper method
Note
You should use this method after calling .start()!
Expand source code
def print_available_playout_devices(self): """Print name and guid of available playout audio devices in system. Just helper method Note: You should use this method after calling .start()! """ warnings.warn("It's a deprecated method. Use .get_recording_devices() instead", DeprecationWarning, 2) for device in self.get_playout_devices(): print(f'Playout device \n name: {device.name} \n guid: {device.guid}')
def print_available_recording_devices(self)
-
Print name and guid of available recording audio devices in system. Just helper method
Note
You should use this method after calling .start()!
Expand source code
def print_available_recording_devices(self): """Print name and guid of available recording audio devices in system. Just helper method Note: You should use this method after calling .start()! """ warnings.warn("It's a deprecated method. Use .get_playout_devices() instead", DeprecationWarning, 2) for device in self.get_recording_devices(): print(f'Recording device \n name: {device.name} \n guid: {device.guid}')
def restart_playout(self, *args, **kwargs)
-
Expand source code
def wrapper(self, *args, **kwargs): if self.is_group_call_native_created(): return func(self, *args, **kwargs) else: raise CallBeforeStartError("You can't use this method before calling .start()")
def restart_recording(self, *args, **kwargs)
-
Expand source code
def wrapper(self, *args, **kwargs): if self.is_group_call_native_created(): return func(self, *args, **kwargs) else: raise CallBeforeStartError("You can't use this method before calling .start()")
def set_audio_input_device(self, *args, **kwargs)
-
Expand source code
def wrapper(self, *args, **kwargs): if self.is_group_call_native_created(): return func(self, *args, **kwargs) else: raise CallBeforeStartError("You can't use this method before calling .start()")
def set_audio_output_device(self, *args, **kwargs)
-
Expand source code
def wrapper(self, *args, **kwargs): if self.is_group_call_native_created(): return func(self, *args, **kwargs) else: raise CallBeforeStartError("You can't use this method before calling .start()")
class GroupCallRaw (mtproto_bridge, on_played_data: Callable[[_ForwardRef('GroupCallRaw'), int], bytes] = None, on_recorded_data: Callable[[_ForwardRef('GroupCallRaw'), bytes, int], NoneType] = None, enable_logs_to_console=False, path_to_log_file=None, outgoing_audio_bitrate_kbit=128)
-
Helper class that provides a standard way to create an ABC using inheritance.
Expand source code
class GroupCallRaw(GroupCall): def __init__( self, mtproto_bridge, on_played_data: Callable[['GroupCallRaw', int], bytes] = None, on_recorded_data: Callable[['GroupCallRaw', bytes, int], None] = None, enable_logs_to_console=False, path_to_log_file=None, outgoing_audio_bitrate_kbit=128, ): super().__init__(mtproto_bridge, enable_logs_to_console, path_to_log_file, outgoing_audio_bitrate_kbit) self.__is_playout_paused = False self.__is_recording_paused = False self.__raw_audio_device_descriptor = None self.on_played_data = on_played_data self.on_recorded_data = on_recorded_data def __create_and_return_raw_audio_device_descriptor(self): self.__raw_audio_device_descriptor = tgcalls.RawAudioDeviceDescriptor() self.__raw_audio_device_descriptor.getPlayedBufferCallback = self.__get_played_buffer_callback self.__raw_audio_device_descriptor.setRecordedBufferCallback = self.__set_recorded_buffer_callback self.__raw_audio_device_descriptor.isPlayoutPaused = self.__is_playout_paused_callback self.__raw_audio_device_descriptor.isRecordingPaused = self.__is_recording_paused_callback return self.__raw_audio_device_descriptor def _setup_and_start_group_call(self): self._start_native_group_call(self.__create_and_return_raw_audio_device_descriptor()) def __get_played_buffer_callback(self, length: int): frame = b'' if self.on_played_data: data = self.on_played_data(self, length) if data: frame = data return frame.ljust(length, b'\0') def __set_recorded_buffer_callback(self, frame: bytes, length: int): if self.on_recorded_data: self.on_recorded_data(self, frame, length) def pause_playout(self): """Pause playout (playing from callback).""" self.__is_playout_paused = True def resume_playout(self): """Resume playout (playing from callback).""" self.__is_playout_paused = False def pause_recording(self): """Pause recording (output to callback).""" self.__is_recording_paused = True def resume_recording(self): """Resume recording (output to callback).""" self.__is_recording_paused = False def __is_playout_paused_callback(self): return self.__is_playout_paused def __is_recording_paused_callback(self): return self.__is_recording_paused """Stop requesting new data to send.""" stop_playout = pause_playout """Stop getting raw data.""" stop_output = pause_recording
Ancestors
- GroupCall
- abc.ABC
- GroupCallDispatcherMixin
- pytgcalls.dispatcher.dispatcher_mixin.DispatcherMixin
- GroupCallNative
Class variables
var SEND_ACTION_UPDATE_EACH
-
Inherited from:
GroupCall
.SEND_ACTION_UPDATE_EACH
How often to send speaking action to chat
Instance variables
var enable_action
-
Inherited from:
GroupCall
.enable_action
Is enable sending of speaking action
var invite_hash
-
Inherited from:
GroupCall
.invite_hash
Hash from invite link to join as speaker
var is_connected
-
Inherited from:
GroupCall
.is_connected
Is connected to voice chat via tgcalls
Methods
async def edit_group_call(self, volume: int = None, muted=False)
-
Inherited from:
GroupCall
.edit_group_call
Edit own settings of group call …
async def edit_group_call_member(self, peer, volume: int = None, muted=False)
-
Inherited from:
GroupCall
.edit_group_call_member
Edit setting of user in voice chat (required voice chat management permission) …
async def leave_current_group_call(self)
-
Inherited from:
GroupCall
.leave_current_group_call
Leave group call from server side (MTProto part).
def on_network_status_changed(self, func: Callable) ‑> Callable
-
Inherited from:
GroupCall
.on_network_status_changed
When a status of network will be changed …
def on_participant_list_updated(self, func: Callable) ‑> Callable
-
Inherited from:
GroupCall
.on_participant_list_updated
When a list of participant will be updated …
def pause_playout(self)
-
Pause playout (playing from callback).
Expand source code
def pause_playout(self): """Pause playout (playing from callback).""" self.__is_playout_paused = True
def pause_recording(self)
-
Pause recording (output to callback).
Expand source code
def pause_recording(self): """Pause recording (output to callback).""" self.__is_recording_paused = True
def print_available_playout_devices(self)
-
Inherited from:
GroupCall
.print_available_playout_devices
Print name and guid of available playout audio devices in system. Just helper method …
def print_available_recording_devices(self)
-
Inherited from:
GroupCall
.print_available_recording_devices
Print name and guid of available recording audio devices in system. Just helper method …
async def reconnect(self)
-
Inherited from:
GroupCall
.reconnect
Connect to voice chat using the same native instance.
def resume_playout(self)
-
Resume playout (playing from callback).
Expand source code
def resume_playout(self): """Resume playout (playing from callback).""" self.__is_playout_paused = False
def resume_recording(self)
-
Resume recording (output to callback).
Expand source code
def resume_recording(self): """Resume recording (output to callback).""" self.__is_recording_paused = False
async def set_is_mute(self, is_muted: bool)
-
Inherited from:
GroupCall
.set_is_mute
Set is mute …
async def set_my_volume(self, volume)
-
Inherited from:
GroupCall
.set_my_volume
Set volume for current client …
async def start(self, group, join_as=None, invite_hash: Optional[str] = None, enable_action=True)
-
Inherited from:
GroupCall
.start
Start voice chat (join and play/record from initial values) …
async def stop(self)
-
Inherited from:
GroupCall
.stop
Properly stop tgcalls, remove MTProto handler, leave from server side.
def stop_output(self)
-
Pause recording (output to callback).
Expand source code
def pause_recording(self): """Pause recording (output to callback).""" self.__is_recording_paused = True
def stop_playout(self)
-
Pause playout (playing from callback).
Expand source code
def pause_playout(self): """Pause playout (playing from callback).""" self.__is_playout_paused = True