Handle YouTube Kids (TVHTML5_FOR_KIDS)

Should fix #84
This commit is contained in:
dmunozv04
2023-11-05 17:36:39 +01:00
parent 58ee703501
commit db647362c6
3 changed files with 74 additions and 15 deletions

View File

@@ -17,3 +17,5 @@ skip_categories = (
('Preview', 'preview'),
('Filler', 'filler'),
)
youtube_client_blacklist = ["TVHTML5_FOR_KIDS"]

View File

@@ -3,6 +3,7 @@ import aiohttp
import time
import logging
from . import api_helpers, ytlounge
from .constants import youtube_client_blacklist
import traceback
@@ -40,7 +41,6 @@ class DeviceListener:
except:
# traceback.print_exc()
await asyncio.sleep(10)
while not self.cancelled:
while not (await self.is_available()) and not self.cancelled:
await asyncio.sleep(10)
@@ -49,17 +49,17 @@ class DeviceListener:
except:
pass
while not lounge_controller.connected() and not self.cancelled:
# Doesn't connect to the device if it's a kids profile (it's broken)
await asyncio.sleep(10)
try:
await lounge_controller.connect()
except:
pass
print(f"Connected to device {lounge_controller.screen_name}")
try:
#print("Subscribing to lounge")
# print("Subscribing to lounge")
sub = await lounge_controller.subscribe_monitored(self)
await sub
#print("Subscription ended")
await asyncio.sleep(10)
except:
pass
@@ -110,7 +110,6 @@ class DeviceListener:
self.api_helper.mark_viewed_segments(UUID)
) # Don't wait for this to finish
# Stops the connection to the device
async def cancel(self):
self.cancelled = True

View File

@@ -1,7 +1,13 @@
import asyncio
import json
import aiohttp
import pyytlounge
from .constants import youtube_client_blacklist
# Temporary imports
from pyytlounge.api import api_base
from pyytlounge.wrapper import NotLinkedException, desync
create_task = asyncio.create_task
@@ -40,7 +46,7 @@ class YtLoungeApi(pyytlounge.YtLoungeApi):
# Process a lounge subscription event
def _process_event(self, event_id: int, event_type: str, args):
# print(f"YtLoungeApi.__process_event({event_id}, {event_type}, {args})")
print(f"YtLoungeApi.__process_event({event_id}, {event_type}, {args})")
# (Re)start the watchdog
try:
self.subscribe_task_watchdog.cancel()
@@ -51,8 +57,6 @@ class YtLoungeApi(pyytlounge.YtLoungeApi):
# A bunch of events useful to detect ads playing, and the next video before it starts playing (that way we can get the segments)
if event_type == "onStateChange":
data = args[0]
self.state.apply_state(data)
self._update_state()
# print(data)
# Unmute when the video starts playing
if self.mute_ads and data["state"] == "1":
@@ -63,12 +67,12 @@ class YtLoungeApi(pyytlounge.YtLoungeApi):
self._update_state()
# Unmute when the video starts playing
if self.mute_ads and data.get("state", "0") == "1":
#print("Ad has ended, unmuting")
# print("Ad has ended, unmuting")
create_task(self.mute(False, override=True))
elif self.mute_ads and event_type == "onAdStateChange":
data = args[0]
if data["adState"] == '0': # Ad is not playing
#print("Ad has ended, unmuting")
# print("Ad has ended, unmuting")
create_task(self.mute(False, override=True))
else: # Seen multiple other adStates, assuming they are all ads
print("Ad has started, muting")
@@ -98,11 +102,20 @@ class YtLoungeApi(pyytlounge.YtLoungeApi):
create_task(self.mute(False, override=True))
elif self.mute_ads:
create_task(self.mute(True, override=True))
elif event_type == "onAutoplayModeChanged":
elif event_type == "loungeStatus":
data = args[0]
create_task(self.set_auto_play_mode(data["autoplayMode"] == "ENABLED"))
else:
super()._process_event(event_id, event_type, args)
devices = json.loads(data["devices"])
for device in devices:
if device["type"] == "LOUNGE_SCREEN":
device_info = json.loads(device.get("deviceInfo", ""))
if device_info.get("clientName", "") in youtube_client_blacklist:
self._sid = None
self._gsession = None # Force disconnect
# elif event_type == "onAutoplayModeChanged":
# data = args[0]
# create_task(self.set_auto_play_mode(data["autoplayMode"] == "ENABLED"))
super()._process_event(event_id, event_type, args)
# Set the volume to a specific value (0-100)
async def set_volume(self, volume: int) -> None:
@@ -123,4 +136,49 @@ class YtLoungeApi(pyytlounge.YtLoungeApi):
await super()._command("setVolume", {"volume": self.volume_state.get("volume", 100), "muted": mute_str})
async def set_auto_play_mode(self, enabled: bool):
await super()._command("setAutoplayMode", {"autoplayMode": "ENABLED" if enabled else "DISABLED"})
await super()._command("setAutoplayMode", {"autoplayMode": "ENABLED" if enabled else "DISABLED"})
# Here just temporarily, will be removed once the PR is merged on YTlounge
async def connect(self) -> bool:
"""Attempt to connect using the previously set tokens"""
if not self.linked():
raise NotLinkedException("Not linked")
connect_body = {
"app": "web",
"mdx-version": "3",
"name": self.device_name,
"id": self.auth.screen_id,
"device": "REMOTE_CONTROL",
"capabilities": "que,dsdtr,atp",
"method": "setPlaylist",
"magnaKey": "cloudPairedDevice",
"ui": "false",
"deviceContext": "user_agent=dunno&window_width_points=&window_height_points=&os_name=android&ms=",
"theme": "cl",
"loungeIdToken": self.auth.lounge_id_token,
}
connect_url = (
f"{api_base}/bc/bind?RID=1&VER=8&CVER=1&auth_failure_option=send_error"
)
async with aiohttp.ClientSession() as session:
async with session.post(url=connect_url, data=connect_body) as resp:
try:
text = await resp.text()
if resp.status == 401:
self.auth.lounge_id_token = None
return False
if resp.status != 200:
self._logger.warning(
"Unknown reply to connect %i %s", resp.status, resp.reason
)
return False
lines = text.splitlines()
async for events in self._parse_event_chunks(desync(lines)):
self._process_events(events)
self._command_offset = 1
return self.connected()
except Exception as ex:
self._logger.exception(ex, resp.status, resp.reason)
return False