Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/faraasaaay/innertube-v2/llms.txt

Use this file to discover all available pages before exploring further.

InnerTube’s player endpoint returns a PlayerResponse that contains streaming formats and all metadata needed to play a track. This guide covers client selection, signature decryption, throttle parameter deobfuscation, and queue management.
Call YouTubeExtractor.ensureInitialized() once at application startup before making any player requests. This pre-fetches the JavaScript needed for signature decryption and n-parameter deobfuscation, avoiding a delay on the first playback.

Overview

suspend fun YouTube.player(
    videoId: String,
    playlistId: String? = null,
    client: YouTubeClient,
    signatureTimestamp: Int? = null,
    poToken: String? = null,
): Result<PlayerResponse>
The response contains:
  • PlayerResponse.streamingData.adaptiveFormats — a list of audio and video formats, each with a direct url or a signatureCipher/cipher that must be decrypted before use.
  • PlayerResponse.videoDetails — title, author, duration, and thumbnail.
  • PlayerResponse.playbackTracking — URLs used to report playback events to YouTube.
  • PlayerResponse.playerConfig.audioConfig — loudness normalisation data.

Choosing a Client

Different YouTubeClient values behave differently with respect to PoToken requirements, signature timestamps, audio quality, and login support. The recommended client for most playback scenarios is ANDROID_VR_1_43_32:
ClientNotes
ANDROID_VR_1_43_32Recommended primary client. Non-adaptive bitrate prevents audio stutter. No PoToken required. Does not support AV1.
WEB_REMIXFull feature set: login, signature timestamps, PoTokens. Required for authenticated history tracking.
TVHTML5_SIMPLY_EMBEDDED_PLAYEREmbedded player that bypasses age-restrictions without login.
TVHTML5Requires login and PoToken. Good fallback for web-style clients.
ANDROID_CREATORPlays kids/children content; requires signature timestamp.
ANDROID_VR_1_61_48Alternative VR client; can only be used logged-out.
ANDROID_VR_NO_AUTHNo auth; no signature timestamp.
IOS / IPADOSApple clients; no login or signature timestamp required.
MOBILEAndroid YouTube client; supports login and signature timestamps.
WEB / WEB_CREATORWeb clients; login required for WEB_CREATOR.
YTPlayerUtils defines the full fallback chain used internally. ANDROID_VR_1_43_32 is the primary client (MAIN_CLIENT). If it fails, the STREAM_FALLBACK_CLIENTS array is tried in order:
MAIN_CLIENT (ANDROID_VR_1_43_32) → ANDROID_VR_1_61_48 → WEB_REMIX → TVHTML5_SIMPLY_EMBEDDED_PLAYER
  → TVHTML5 → ANDROID_CREATOR → IPADOS → ANDROID_VR_NO_AUTH → MOBILE → IOS → WEB → WEB_CREATOR

Signature Timestamps

Clients with useSignatureTimestamp = true (including WEB_REMIX, TVHTML5, and ANDROID_CREATOR) require a signatureTimestamp integer that is embedded in the player request body. It must be fetched from YouTube’s player JavaScript:
val signatureTimestamp: Int = NewPipeExtractor.getSignatureTimestamp(videoId).getOrThrow()
Pass the result as the fourth argument to YouTube.player(). For clients where useSignatureTimestamp = false (such as ANDROID_VR_1_43_32), simply pass null.

PoTokens

WEB_REMIX and TVHTML5 set useWebPoTokens = true. These clients require a Proof-of-Origin Token to be included in the player request body, and a separate streaming token to be appended to each stream URL. See the Bot Detection guide for full PoToken generation details. Pass the player token as the fifth argument:
YouTube.player(
    videoId = videoId,
    playlistId = null,
    client = YouTubeClient.WEB_REMIX,
    signatureTimestamp = signatureTimestamp,
    poToken = poToken?.playerRequestPoToken,
)
After obtaining the stream URL, append the streaming PoToken:
val finalUrl = "${streamUrl}&pot=${poToken?.streamingDataPoToken}"

Decrypting Stream URLs

Formats returned by adaptiveFormats fall into two categories: Direct URL — the url field is present and non-null. Use it as-is (after deobfuscating the n parameter). SignatureCipher — the signatureCipher (or cipher) field is present. This is a URL-encoded string that contains an obfuscated signature. Decrypt it before use:
val streamUrl: String = YouTubeExtractor.decryptUrl(format.signatureCipher!!)
After obtaining any stream URL — whether direct or decrypted — deobfuscate the throttle n parameter to avoid bandwidth throttling by YouTube’s CDN:
val finalUrl: String = YouTubeExtractor.deobfuscateUrlNParam(streamUrl)

Basic Playback Example

// Step 1: Get the player response using the fast ANDROID_VR client
val response = YouTube.player(
    videoId = "dQw4w9WgXcQ",
    playlistId = null,
    client = YouTubeClient.ANDROID_VR_1_43_32
).getOrThrow()

// Step 2: Pick the best audio format
val audioFormat = response.streamingData?.adaptiveFormats
    ?.filter { it.isAudio && it.isOriginal }
    ?.maxByOrNull { it.bitrate }
    ?: error("No audio format found")

// Step 3: Resolve the stream URL
val rawUrl: String = audioFormat.url
    ?: audioFormat.signatureCipher?.let { YouTubeExtractor.decryptUrl(it) }
    ?: error("Cannot resolve stream URL")

// Step 4: Deobfuscate the throttle parameter
val finalUrl: String = YouTubeExtractor.deobfuscateUrlNParam(rawUrl)

// finalUrl is now ready for ExoPlayer / Media3

The next() Method

YouTube.next() returns the playback queue for a given video or playlist endpoint. It is the correct way to find the current track’s position in a queue and to load related tracks for continuous playback:
val result: NextResult = YouTube.next(
    WatchEndpoint(videoId = "dQw4w9WgXcQ", playlistId = "PLxxx")
).getOrThrow()

// result.items          — list of SongItem in the queue
// result.currentIndex   — index of the currently playing track
// result.lyricsEndpoint — browse endpoint to load lyrics
// result.relatedEndpoint — browse endpoint to load related songs
// result.continuation   — token to load more queue items
The library automatically follows automix continuations, enabling infinite radio-style playback. Pass continuation back to YouTube.next() to page through the queue. To bulk-fetch metadata for a set of video IDs (for example, to pre-populate a queue):
val songs: List<SongItem> = YouTube.queue(
    videoIds = listOf("id1", "id2", "id3"),
    playlistId = null
).getOrThrow()

Playback Tracking

YouTube expects clients to report playback events so that listening history and recommendations are updated correctly. After playback begins, call:
// Pass the videostatsPlaybackUrl base URL string from the player response
val trackingUrl = response.playbackTracking?.videostatsPlaybackUrl?.baseUrl
if (trackingUrl != null) {
    YouTube.registerPlayback(
        playlistId = currentPlaylistId,
        playbackTracking = trackingUrl
    )
}
registerPlayback(playlistId: String?, playbackTracking: String) takes the tracking base URL string directly (not the PlaybackTracking object). The library appends a random client playback nonce (cpn) and rewrites the host to music.youtube.com automatically. Both the ANDROID_VR and WEB_REMIX player responses include playbackTracking. When both are fetched in parallel (as YTPlayerUtils does), prefer the WEB_REMIX tracking URLs for authenticated sessions to ensure history is recorded server-side.

Build docs developers (and LLMs) love