FabioGNR / pyytlounge

Python youtube lounge API
https://readthedocs.org/projects/pyytlounge/
GNU General Public License v3.0
11 stars 3 forks source link

Connection eventually gets detached from screen #9

Open FabioGNR opened 6 months ago

FabioGNR commented 6 months ago

Using code that continually reconnects and refreshes authorization, eventually the real connection to the lounge session is lost. Instead a sort of phantom session becomes active, where no events are being sent, but there's no errors from the API. Calling refresh_auth usually fixes this situation, however it should automatically be detected and prevented.

The following sample code can be used to observe this:

import asyncio
from pyytlounge import YtLoungeApi, PlaybackState, State
from ast import literal_eval
import os

AUTH_STATE_FILE = "auth_state"
CONNECT_RETRY_INTERVAL = 10
ERROR_RETRY_INTERVAL = 30
SUBSCRIBE_RETRY_INTERVAL = 1

async def go():
    api = YtLoungeApi("Test")
    if os.path.exists(AUTH_STATE_FILE):
        with open(AUTH_STATE_FILE, "r") as f:
            content = f.read()
            api.load_auth_state(literal_eval(content))
            print("Loaded from file")
    else:
        pairing_code = input("Enter pairing code: ")
        print(f"Pairing with code {pairing_code}...")
        paired = await api.pair(pairing_code)
        print(paired and "success" or "failed")
        if not paired:
            exit()
        auth_state = api.auth.serialize()
        with open(AUTH_STATE_FILE, "w") as f:
            f.write(str(auth_state))
    print("Connecting...")
    connected = await api.connect()
    print(connected and "success" or "failed")
    if not connected:
        exit()

    async def receive_state(state: PlaybackState):
        print(f"New state: {state}")
        if state.videoId:
            print(
                f"Image should be at: https://img.youtube.com/vi/{state.videoId}/0.jpg"
            )

    async def subscribe_and_keep_alive():
        if not api.connected():
            await api.connect()

        while True:
            while not api.connected():
                print("subscribe_and_keep_alive: reconnecting")
                await asyncio.sleep(CONNECT_RETRY_INTERVAL)
                if not api.linked():
                    await api.refresh_auth()
                await api.connect()
            print("subscribe_and_keep_alive: subscribing")
            await api.subscribe(receive_state)
            await asyncio.sleep(SUBSCRIBE_RETRY_INTERVAL)

    while True:
        try:
            print("Starting subscribe and keep alive")
            await subscribe_and_keep_alive()
        except asyncio.CancelledError:
            break
        except:
            print("Subscribe and keep alive encountered error, waiting seconds: ", ERROR_RETRY_INTERVAL)
            await asyncio.sleep(ERROR_RETRY_INTERVAL)

asyncio.run(go())

In my experience it can take a few days or up to a week before this problem occurs, in this time the screen (and thus youtube app) is turned on/off a few times.

dmunozv04 commented 6 months ago

I faced this issue and sort of sorted it by calling refresh_auth every 24 hours. Implementation can be seen here: https://github.com/dmunozv04/iSponsorBlockTV/blob/80196b19aade6041ddf75c46f97ff885715b55fc/src/iSponsorBlockTV/main.py#L35

bertybuttface commented 5 months ago

Is this a race condition with command_offset?

FabioGNR commented 5 months ago

Thank you for your comment and helping!

Sadly I believe the reproduction described in this issue should not be related to this; it occurs even when no commands are issued and there's only one active coroutine.

I think the self._command_offset variable is safely updated and read; there's no await in between. But since the self.session.post call does await when entering the context manager it's possible that the commands are issued out of order. I will experiment with this and see if the lock effectively solves it.

As a general note: with multiple threads this will also cause issues. At this time it's not something I consider as supported by the library though.