MarshalX / tgcalls

Voice chats, private incoming and outgoing calls in Telegram for Developers
https://t.me/tgcallslib
GNU Lesser General Public License v3.0
522 stars 93 forks source link

Group call does not actually work before fully pass the whole handler #34

Closed dashezup closed 3 years ago

dashezup commented 3 years ago

I want to have "join -> record -> leave" done in one handler, but it records empty audio, and the "output.raw" even keep growing after left the chat (I even have .stop_output() in the code).

Step to reproduce:

  1. start the userbot with the plugin
  2. send !record in a voice chat where there are sound
  3. you can see it prints "Joined" "False" "False" "Left"

And then convert "output.raw" to opus, and then listen to it and you will find that it does not have sound. (or use audacity to check the waveform)

Code, pyrogram smart plugin ``` import os import asyncio from pyrogram import Client, filters from pyrogram.types import Message from pytgcalls import GroupCall @Client.on_message(filters.text & filters.outgoing & ~filters.edited & filters.command("record", prefixes="!")) async def test(client, message: Message): open("output.raw", 'w').close() group_call = GroupCall(client, output_filename="output.raw") await group_call.start(message.chat.id, False) print("Joined") print(repr(group_call.is_connected)) await asyncio.sleep(15) print(repr(group_call.is_connected)) group_call.stop_output() await group_call.stop() print("Left") ```
MarshalX commented 3 years ago

Is the problem present on version 0.0.6?

MarshalX commented 3 years ago

The change in the connection status is really not visible from the smart plugin. I heard that many have problems with these smart plugins. Not everything works 1: 1 as with the conventional approach. No ideas.

dashezup commented 3 years ago

Is the problem present on version 0.0.6?

Yes, this issue still remain on 0.0.6, just "output.raw" does not keep growing after .stop(). and this problem happens no matter if the handler is inside a smart plugin or not.

I have tried the following two ways to add the handler without smart plugin, both reproduce the same issue, prints "Joined" "False" "False" "Left" and output.raw is empty audio.

Code 1, handler inside main script, without smart plugin ``` import os import asyncio from pyrogram import Client, idle, filters from pyrogram.types import Message from pytgcalls import GroupCall api_id = None api_hash = "" app = Client("userbot", api_id, api_hash) @app.on_message(filters.text & filters.outgoing & ~filters.edited & filters.command("record", prefixes="!")) async def test(client, message: Message): open("output.raw", 'w').close() group_call = GroupCall(client, output_filename="output.raw") await group_call.start(message.chat.id, False) print("Joined") print(repr(group_call.is_connected)) await asyncio.sleep(15) print(repr(group_call.is_connected)) group_call.stop_output() await group_call.stop() print("Left") app.start() print('>>> USERBOT STARTED') idle() app.stop() print('\n>>> USERBOT STOPPED') ```
code 2, handler inside main(), without smart plugin ``` import os import asyncio from pyrogram import Client, idle, filters from pyrogram.types import Message from pytgcalls import GroupCall api_id = None api_hash = "" async def main(client): await client.start() while not client.is_connected: await asyncio.sleep(1) @client.on_message(filters.text & filters.outgoing & ~filters.edited & filters.command("record", prefixes="!")) async def test(c, message: Message): open("output.raw", 'w').close() group_call = GroupCall(c, output_filename="output.raw") await group_call.start(message.chat.id, False) print("Joined") print(repr(group_call.is_connected)) await asyncio.sleep(15) print(repr(group_call.is_connected)) group_call.stop_output() await group_call.stop() print("Left") await idle() if __name__ == '__main__': pyro_client = Client("userbot", api_id, api_hash) loop = asyncio.get_event_loop() loop.run_until_complete(main(pyro_client)) ```
MarshalX commented 3 years ago

Msg with question: https://t.me/pyrogramchat/299265

Is this real asynchrony? Why does the code below, after several executions (8 in my case), completely block the pyrogram client? And generally blocks the receipt and processing of new updates while True: await asyncio.sleep(1) This code is located in the on_message() handler

That is, it depends on the number of workers... But workers are not about asynchrony

Answer: https://t.me/pyrogramchat/299272

Pyrogram is async, but currently puts a cap on how many tasks it can spawn concurrently because some parts would not work otherwise (continue/stop_propagation). the cap can be configured with Client's workers parameter. I plan to remove this "limitation" in future versions, but this requires rethinking the whole update handling logic.

About your issue: if you know something can take lots of time (and thus blocking there) you'd better offload that to another task and keep your update handlers short lived.

while not self.is_connected:
    await asyncio.sleep(1)

Code above blocks tgcalls completely. No further connection is possible. Possibly due to thread safety not to have access to the variable.

MarshalX commented 3 years ago

To solve ur problem, you need to use callbacks. I will add the ability to register custom handlers for the connect and disconnect action

MarshalX commented 3 years ago

Example of usage of own callback:

        group_call = pytgcalls.GroupCall(client, output_filename='output.raw')
        await group_call.start(message.chat.id)

        @group_call.on_network_status_changed
        async def network_status_changed_handler(gc: pytgcalls.GroupCall, is_connected: bool):
            if is_connected:
                await gc.reconnect()
MarshalX commented 3 years ago

And yes, the status change is sent twice when connected. I know about it. This is how the telegram library is implemented. Know how to handle such a flow

dashezup commented 3 years ago

To solve ur problem, you need to use callbacks.

How? Show me an example? just like init_client_and_delete_message() in the example? I just tried, it does not work. is_connected reports False.

I tried to add group_call.start("group_username") after group_call.client = client inside the wrapper().

https://github.com/MarshalX/tgcalls/blob/main/examples/player_as_smart_plugin.py#L15

also tried to add to the code, it still output empty audio. is_connected reports False unless after the second time I call the function.

it still looks like the group call could only be properly initialized after pass the whole handler.

MarshalX commented 3 years ago

Example of usage of own callback:

        group_call = pytgcalls.GroupCall(client, output_filename='output.raw')
        await group_call.start(message.chat.id)

        @group_call.on_network_status_changed
        async def network_status_changed_handler(gc: pytgcalls.GroupCall, is_connected: bool):
            if is_connected:
                await gc.reconnect()

@dashezup

dashezup commented 3 years ago

Just for the record, this works

Code ``` import os import asyncio from pyrogram import Client, filters from pyrogram.types import Message from pytgcalls import GroupCall, GroupCallAction recorded = False group_call = GroupCall(None, path_to_log_file='') async def network_status_changed_handler(gc: GroupCall, is_connected: bool): global recorded if is_connected: if recorded: print("Already recorded") recorded = False return recorded = True print("CONNECTED, START RECORDING") gc.output_filename = "output.raw" await asyncio.sleep(15) gc.stop_output() print("FINISHED RECORDING") chat_id = int("-100" + str(gc.full_chat.id)) await gc.client.send_document(chat_id, "output.raw") await gc.stop() else: print("NOT CONNECTED") @Client.on_message(filters.text & filters.outgoing & ~filters.edited & filters.command("record", prefixes="!")) async def test(client, message: Message): group_call.client = client await group_call.start(message.chat.id) print("Joined") group_call.dispatcher.add_handler( network_status_changed_handler, GroupCallAction.NETWORK_STATUS_CHANGED ) ```