3 of 61 min

How it works inside: indexer → playlist → stream

The project is built as a pipeline: Telegram channel → catalogue → broadcast.

1) The indexer runs through the channel, finds audio and GIF/MP4, reads tags and builds playlist.json.

2) /playlist?mix=&energy= returns JSON with tracks and visuals already in the right order for the current knobs.

3) /audio/{id} and /gif/{id} request media from Telegram and serve it to the browser.

The key principle: audio is not saved to disk. The server streams the data (chunked) as it arrives.

Visuals are cached to disk because they’re short and repeat, and the cache noticeably speeds up switching.

The most important part (the thing that turns a channel into a broadcast):

@app.get("/audio/{message_id}")
async def audio(message_id: int):
    async def gen():
        async for chunk in telethon_stream_bytes(message_id):
            yield chunk

    return StreamingResponse(
        gen(),
        media_type="audio/mpeg",
        headers={"Cache-Control": "no-store", "Accept-Ranges": "none"},
    )

— @app.get(“/audio/{message_id}”) The route: GET /audio/12345 lands here, {message_id} is the ID of the message with the track in Telegram.

— async def audio(message_id: int): An asynchronous handler, so the server doesn’t block during loading and serving. int fixes the parameter type.

— async def gen(): … yield chunk An asynchronous generator: it reads the track from Telegram in portions (async for) and immediately serves each piece (yield).

— StreamingResponse(gen(), …) Wraps the flow in a proper HTTP stream: playback can start before the file has fully loaded.

— media_type=“audio/mpeg” Explicitly sets Content-Type so the browser plays the audio correctly.

— headers no-store: don’t cache the audio. Accept-Ranges: none: no Range requests (seeking or loading “from the middle” isn’t provided for).

Series contents
  1. Radio from Telegram in one click
  2. Two mood knobs: style and energy
  3. How it works inside: indexer → playlist → stream
  4. Engineering reality: the player, background and PWA
  5. The project moves slowly
  6. Without Streaming stopped being a player and became a radio station