Skip to main content

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
    lounge.play_next(screen, "aqz-KE-bpKQ") # jump the queue, behind what plays
    lounge.queue(screen)                    # what is queued right now
    lounge.remove(screen, "jNQXAC9IVRw")    # drop one video
    lounge.clear(screen)                    # empty 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, insert one behind the current video, remove one, empty the queue, and read what the screen has queued.
  • ytlounge.video.parse_video turns an id or any of the usual YouTube URL forms into a Video with 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 command
  • Pair: POST /api/lounge/pairing/get_screen with pairing_code=. The expiration here is a string, on the token endpoint a number.
  • Refresh: POST /api/lounge/pairing/get_lounge_token_batch with screen_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 a Content-Length header 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 body count=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.
  • Play next, remove, clear: same query, req0__sc=insertVideo&req0_videoId=…, req0__sc=removeVideo&req0_videoId=… and req0__sc=clearPlaylist with no fields of its own. insertVideo puts the video behind the one currently playing, not at the end: a queue of [zoo, bunny] with zoo playing became [zoo, rick, bunny]. All three answer with the same ack as play, 8\n[0,-1,0].
  • Reading the queue: no request of its own. The bind reply already carries a playlistModified event whose videoIds is the queue as one comma-separated string, so Session.queue is filled while the session is opened. It is a snapshot of that moment and does not update afterwards: the commands answer with a bare ack and carry no new queue, so a fresh bind is the way to see the result of one.
  • Sessions do not go stale quickly. casttube rebinds before every queue action, saying a session drifts out of step after about 30 seconds. Not reproduced here: a session left idle for 35 seconds still executed an insert that took effect on the screen. Opening one session per command, as the Lounge methods do, sidesteps the question anyway.

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 — for the names of the three queue actions — from reading casttube. Protocol knowledge, no code: nothing here is a translation of either. Through ytcast this client also stands on the sources it credits:

The YouTube URL forms in the tests come from this gist.

Metadata

Release files for ytlounge 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ytlounge 0.2.0
File Size Uploaded Provenance Actions
ytlounge-0.2.0.tar.gz 19.5 kB With provenance Details for ytlounge-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for ytlounge 0.2.0
File Interpreter ABI Platform Actions
ytlounge-0.2.0-py3-none-any.whl Python 3 none any Details for ytlounge-0.2.0-py3-none-any.whl

Total release size: 32.6 kB

Release files / ytlounge-0.2.0.tar.gz

Download URL ytlounge-0.2.0.tar.gz
Size 19.5 kB
Tags Source
SHA-256 checksum
How to use checksums
ffb31dd3a50cbf7ef55b24d2d5907254b0fb1534665e3592db35d38f2c0b95ad
BLAKE2b-256 checksum
How to use checksums
af6f4ccbf6fd31ee4148f7980aec486ef864b58e845321dd6333d42345e132ab
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 13, 2026.

Transparency log

Release files / ytlounge-0.2.0-py3-none-any.whl

Download URL ytlounge-0.2.0-py3-none-any.whl
Size 13.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e0a3825456a4d047f91f1def3dc45c5eb418b9ad514d773d13d9c09c0104c453
BLAKE2b-256 checksum
How to use checksums
30391d6e15f3ac713205efd2f75e3860cd54e26c4de82acaf9603a9506678bf4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 13, 2026.

Transparency log

Security for ytlounge 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for ytlounge-0.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 13, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page