ytlounge
Client for the YouTube Lounge API: the protocol behind the "Play on TV" button. Pair with a screen once, then play videos on it and manage its queue from Python. Synchronous, one dependency (httpx), no device code.
from ytlounge import Lounge
with Lounge(name="my-remote") as lounge:
screen = lounge.pair("123 456 789") # code from Settings › Link with TV code
lounge.play(screen, ["dQw4w9WgXcQ"]) # play now
lounge.add(screen, ["jNQXAC9IVRw"]) # append to the queue
Screen is a small frozen dataclass; keep screen_id and you can always
get a fresh token with lounge.refresh(screen). Tokens live about two
weeks, Screen.is_expired() tells you when.
Install
pip install ytlounge
Python 3.11 or newer.
What it does and does not do
- Pair with a TV code, refresh a token from a screen id, open a session, play a list of videos (with a start index and start time), append videos to the queue.
ytlounge.video.parse_videoturns an id or any of the usual YouTube URL forms into aVideowith an optional start time.- It does not find TVs, launch apps or store anything. That is the job of a tool built on top, such as yttv, which adds Cast, Apple TV and DIAL backends, a cache and a command line.
The Lounge API is not documented by Google and can change at any time. Everything below was verified on the wire in September 2026 and is kept as assertions in the tests; when something breaks, that is where to look.
The protocol, as verified
| Field | Lives | Comes from |
|---|---|---|
screen_id |
for good, survives a cold start of the TV | one pairing with a TV code |
lounge_token |
about 13 days | get_lounge_token_batch from the screen id |
SID, gsessionid |
one session | a bind at the start of every play/add |
- Pair:
POST /api/lounge/pairing/get_screenwithpairing_code=. Theexpirationhere is a string, on the token endpoint a number. - Refresh:
POST /api/lounge/pairing/get_lounge_token_batchwithscreen_ids=. A refresh does not revoke earlier tokens. - Session:
POST /api/lounge/bc/bind?CVER=1&RID=1&VER=8&app=youtube-desktop&device=REMOTE_CONTROL&id=remote&loungeIdToken=…&name=…with an empty body. The endpoint insists on aContent-Lengthheader even then (httpx always sends one). The reply is framed: a decimal length on its own line, then that many characters of a JSON array of numbered messages;["c", SID, …]and["S", gsessionid]are in the first frame, followed by the screen's status and current queue. - Play:
POST bind?CVER=1&RID=2&SID=…&VER=8&gsessionid=…&loungeIdToken=…, form bodycount=1&req0__sc=setPlaylist&req0_currentIndex=0&req0_currentTime=0&req0_videoId=…&req0_videoIds=a,b,c. - Add: same query,
req0__sc=addVideo&req0_videoId=…, one request per video with a random 2–5 s pause before each. The pause is not cosmetic: without it consecutive adds race on the screen and the queue comes out incomplete or reordered.
Development
pixi run test # fixture tests
YTLOUNGE_SCREEN_ID=… pixi run test-device # against the real API, no TV changes
pixi run check # build and validate the artifacts
Origins
The Lounge protocol was reverse-engineered independently by several people. This client contains none of their code, but learned the protocol from Marco Lucidi's ytcast, whose requests were the reference for verifying this implementation on the wire, and through it from the sources ytcast credits:
- https://0x41.cf/automation/2021/03/02/google-assistant-youtube-smart-tvs.html
- https://github.com/thedroidgeek/youtube-cast-automation-api
- https://github.com/mutantmonkey/youtube-remote
- https://bugs.xdavidhu.me/google/2021/04/05/i-built-a-tv-that-plays-all-of-your-private-youtube-videos
- https://github.com/aykevl/plaincast
- https://github.com/ur1katz/casttube
The YouTube URL forms in the tests come from this gist.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ytlounge-0.1.0.tar.gz.
File metadata
- Download URL: ytlounge-0.1.0.tar.gz
- Upload date:
- Size: 16.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bf38392f8d8691d242249357bcf110a36de050b242023ce9d533e04a884c56e7
|
|
| MD5 |
cbaff68a85963036f2847696dea4c7f8
|
|
| BLAKE2b-256 |
d8db41f1b44c871bdcafc430c0f32eea07f8cf448c20ccaa1bdb63af72d41746
|
File details
Details for the file ytlounge-0.1.0-py3-none-any.whl.
File metadata
- Download URL: ytlounge-0.1.0-py3-none-any.whl
- Upload date:
- Size: 11.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e385e67e53ec2fb764eb9fc104c3c1aad814d4fd2daf3aaa26d80bc0755b7196
|
|
| MD5 |
627e01a4ea70c57c7be253391cf7ac9e
|
|
| BLAKE2b-256 |
48dc95ee1c5409e0edc911f0fe1b749f7600104762fc62577515af40196d2a21
|