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.
PlayerResponse is the raw deserialized response from the InnerTube /player endpoint. It is returned by YouTube.player() and is the primary source of stream URLs, video metadata, and playability information. All fields are modeled with kotlinx.serialization and map directly to the YouTube InnerTube API JSON schema.
PlayerResponse
The top-level player response object.
| Field | Type | Description |
|---|
playabilityStatus | PlayabilityStatus | Whether the video can be played and why it cannot if not |
streamingData | StreamingData? | Available stream formats; null when the video is unplayable |
videoDetails | VideoDetails? | Metadata about the video (title, author, duration) |
playerConfig | PlayerConfig? | Audio normalization settings |
playbackTracking | PlaybackTracking? | URLs for reporting playback statistics to YouTube |
PlayabilityStatus
Indicates whether the video is playable in the current context.
| Field | Type | Description |
|---|
status | String | Playability status code (see table below) |
reason | String? | Human-readable explanation when the video is not playable |
Status Values
| Status | Meaning | Recommended Action |
|---|
OK | Stream is available and ready to play | Proceed with playback using streamingData |
LOGIN_REQUIRED | Bot detection triggered or authentication required | Rotate visitorData, use a different client, or authenticate |
AGE_CHECK_REQUIRED | Content is age-restricted | Retry with WEB_CREATOR client and a logged-in session |
AGE_VERIFICATION_REQUIRED | Additional age verification needed | Same as above |
CONTENT_CHECK_REQUIRED | Content warning requires user acknowledgement | Use a logged-in session with content check accepted |
UNPLAYABLE | Video is unavailable, private, paid, or region-locked | Check region settings; try a different client such as TVHTML5_SIMPLY_EMBEDDED_PLAYER |
StreamingData
Contains all available audio and video stream formats for the video.
| Field | Type | Description |
|---|
adaptiveFormats | List<Format> | Separate audio-only and video-only streams (DASH adaptive bitrate) |
formats | List<Format>? | Combined audio+video streams at standard qualities; null for most music content |
expiresInSeconds | Int | Number of seconds until the stream URLs in this response expire |
Stream URLs expire. Store expiresInSeconds and re-fetch the player response before the URLs go stale. YouTube typically returns 6-hour expiries.
A single stream format entry within adaptiveFormats or formats.
| Field | Type | Description |
|---|
itag | Int | YouTube format identifier. Determines codec, container, and quality tier |
url | String? | Direct stream URL. May be null for cipher-protected formats |
signatureCipher | String? | Encoded cipher string requiring decryption via YouTubeExtractor.decryptUrl() |
cipher | String? | Older name for signatureCipher — present in some client responses |
mimeType | String | MIME type with codec parameter, e.g. "audio/webm; codecs=\"opus\"" |
bitrate | Int | Nominal bitrate in bits per second |
averageBitrate | Int? | Average bitrate in bits per second |
width | Int? | Video width in pixels; null for audio-only formats |
height | Int? | Video height in pixels; null for audio-only formats |
contentLength | Long? | Total byte length of the stream, if known |
quality | String | Quality label, e.g. "tiny", "small", "medium", "hd720" |
qualityLabel | String? | Human-readable quality label for video, e.g. "720p" |
fps | Int? | Frames per second; null for audio-only formats |
audioQuality | String? | Audio quality tier: "AUDIO_QUALITY_LOW", "AUDIO_QUALITY_MEDIUM", or "AUDIO_QUALITY_HIGH" |
audioSampleRate | Int? | Audio sample rate in Hz (e.g. 48000) |
audioChannels | Int? | Number of audio channels (e.g. 2 for stereo) |
loudnessDb | Double? | Per-format loudness value for normalization |
approxDurationMs | String? | Approximate stream duration in milliseconds as a string |
lastModified | Long? | Unix timestamp (microseconds) when this format was last modified |
audioTrack | AudioTrack? | Audio track metadata; present only for multi-audio formats |
Computed Properties
| Property | Type | Description |
|---|
isAudio | Boolean | true when width == null — i.e. this is an audio-only stream |
isOriginal | Boolean | true when audioTrack?.isAutoDubbed is null — filters out auto-dubbed alternative audio tracks |
AudioTrack
Present on formats that are part of a multi-language or auto-dubbed track set.
| Field | Type | Description |
|---|
displayName | String? | Language display name (e.g. "English") |
id | String? | Audio track identifier |
isAutoDubbed | Boolean? | true if this track was auto-generated by YouTube’s dubbing system |
Resolving Stream URLs
When url is null, the format uses a signature cipher. Decrypt it using YouTubeExtractor:
val format: PlayerResponse.StreamingData.Format = ...
val streamUrl = when {
format.url != null -> {
// Direct URL — still deobfuscate the throttle parameter
YouTubeExtractor.deobfuscateUrlNParam(format.url)
}
format.signatureCipher != null -> {
// Ciphered URL — decrypt signature and deobfuscate n param
YouTubeExtractor.decryptUrl(format.signatureCipher)
}
format.cipher != null -> {
YouTubeExtractor.decryptUrl(format.cipher)
}
else -> null
}
VideoDetails
Metadata about the video itself, independent of stream availability.
| Field | Type | Description |
|---|
videoId | String | YouTube video ID |
title | String? | Video title |
author | String? | Channel/artist name |
channelId | String | Channel ID of the uploader |
lengthSeconds | String | Video duration in seconds as a string |
musicVideoType | String? | Music video type string (e.g. "MUSIC_VIDEO_TYPE_ATV") |
viewCount | String? | Total view count as a string |
thumbnail | Thumbnails | Thumbnail images at multiple resolutions |
PlayerConfig
Audio configuration from the player, used for loudness normalization.
| Field | Type | Description |
|---|
audioConfig | AudioConfig | Audio normalization settings |
AudioConfig
| Field | Type | Description |
|---|
loudnessDb | Double? | Loudness adjustment value in decibels for this video |
perceptualLoudnessDb | Double? | Perceptual loudness normalization value |
Use loudnessDb from PlayerConfig.audioConfig (or per-format loudnessDb) to implement loudness normalization in your player. Negative values mean the track is louder than the target and should be attenuated.
PlaybackTracking
URLs used to report playback events back to YouTube. Calling these URLs keeps play count and history accurate.
| Field | Type | Description |
|---|
videostatsPlaybackUrl | VideostatsPlaybackUrl? | Base URL for reporting that playback started |
videostatsWatchtimeUrl | VideostatsWatchtimeUrl? | Base URL for reporting ongoing watch time |
atrUrl | AtrUrl? | Base URL for ATR (Adaptive Transport Rate) reporting |
Each nested type exposes a single baseUrl: String? field containing the URL.
Usage with YouTube.registerPlayback()
val playerResponse = YouTube.player(
videoId = "dQw4w9WgXcQ",
playlistId = null
).getOrNull() ?: return
// Report that playback started
playerResponse.playbackTracking?.videostatsPlaybackUrl?.baseUrl?.let { url ->
YouTube.registerPlayback(url)
}
Complete Example
val result = YouTube.player(videoId = "dQw4w9WgXcQ", playlistId = null)
result.onSuccess { playerResponse ->
when (playerResponse.playabilityStatus.status) {
"OK" -> {
val streaming = playerResponse.streamingData ?: return@onSuccess
// Pick the best audio-only format
val audioFormat = streaming.adaptiveFormats
.filter { it.isAudio && it.isOriginal }
.maxByOrNull { it.bitrate }
?: return@onSuccess
// Resolve the stream URL
val streamUrl = if (audioFormat.url != null) {
YouTubeExtractor.deobfuscateUrlNParam(audioFormat.url)
} else {
YouTubeExtractor.decryptUrl(
audioFormat.signatureCipher ?: audioFormat.cipher ?: return@onSuccess
)
}
println("Stream URL: $streamUrl")
println("Format: ${audioFormat.mimeType}, ${audioFormat.bitrate} bps")
println("Expires in: ${streaming.expiresInSeconds}s")
}
"LOGIN_REQUIRED" -> println("Login required: ${playerResponse.playabilityStatus.reason}")
"UNPLAYABLE" -> println("Unplayable: ${playerResponse.playabilityStatus.reason}")
}
}