Skip to content

UMP y decodificación

Parte de SABR en el extractor. Aquí: el sobre UMP a nivel de byte, cada tipo de part que el decodificador conoce, y la respuesta decodificada.

El framing UMP

El cuerpo de la respuesta es UMP (Ultra-Minimal Playback), el contenedor propio de YouTube, no protobuf a nivel del sobre (los payloads dentro de las parts son protobuf). UmpReader.readAll(byte[]) recorre un único array en memoria como una secuencia de parts:

Layout de una part UMP

Cada part es [type: varint-UMP][size: varint-UMP][payload: size bytes], repetido hasta EOF. No hay reensamblado cross-buffer en esta capa, el body entero ya debe ser un solo array; un buffer truncado simplemente throw SabrProtocolException.

El varint UMP

El varint de UMP no es el de protobuf. El número total de bytes lo decide los bits altos del primer byte:

Primer byteTotal bytesValor
0x00–0x7F (< 128)1el byte mismo
0x80–0xBF2(b0 & 0x3f) + 64·b1
0xC0–0xDF3(b0 & 0x1f) + 32·(b1 + 256·b2)
0xE0–0xEF4(b0 & 0x0f) + 16·(b1 + 256·(b2 + 256·b3))
0xF0–0xFF5los siguientes 4 bytes, little-endian (los bits de valor del primer byte se descartan)

El resultado es un int con signo de 32-bit; el caso de 5 bytes puede overflow a negativo, lo que el guardia type < 0 || size < 0 rechaza, ese es el tope práctico del tamaño de part.

UmpPart sostiene type, size, y data. getData() devuelve un clon defensivo; un getRawData() interno devuelve el array backing (usado en los hot paths de decodificación/stitch para evitar copiar segmentos de varios MB).

Los tipos de part

SabrResponseDecoder despacha según el id de tipo de part. El conjunto completo que reconoce:

IDConstanteDecodificado en
10ONESIE_HEADERSabrOnesieHeader (llevado a las parts onesie siguientes)
11ONESIE_DATASabrOnesieData
12ONESIE_ENCRYPTED_MEDIASabrOnesieData (cifrado)
20MEDIA_HEADERSabrMediaHeader
21MEDIAbytes de medio (1er byte del payload = header id)
22MEDIA_ENDcierra un header id
31LIVE_METADATASabrLiveMetadata
35NEXT_REQUEST_POLICYSabrNextRequestPolicy (pone el backoff)
37FORMAT_SELECTION_CONFIGSabrFormatSelectionConfig
42FORMAT_INITIALIZATION_METADATASabrFormatInitializationMetadata
43SABR_REDIRECTSabrRedirect
44SABR_ERRORSabrError
45SABR_SEEKSabrSeek
46RELOAD_PLAYER_RESPONSESabrReloadPlayerResponse (pone reloadRequested)
47PLAYBACK_START_POLICYSabrPlaybackStartPolicy
51SELECTABLE_FORMATSSabrSelectableFormats
52REQUEST_IDENTIFIERSabrRequestIdentifier
53REQUEST_CANCELLATION_POLICYSabrRequestCancellationPolicy
57SABR_CONTEXT_UPDATESabrContextUpdate
58STREAM_PROTECTION_STATUSSabrStreamProtectionStatus (pone status + maxRetries)
59SABR_CONTEXT_SENDING_POLICYSabrContextSendingPolicy
65PREWARM_CONNECTIONSabrPrewarmConnection
67SNACKBAR_MESSAGESabrSnackbarMessage

Más ids conocidos-pero-no-parseados (30 CONFIG, 32–34 hints de live-metadata, 36/38 metadata ustreamer, 48–50 hints de caché/ancho de banda, 54–56, 60–64, 66) solo resumidos, y cualquier id desconocido registrado vía addUnknownPartType. Ver la referencia de control parts para qué hacen los ricos.

El bucle de decodificación

Pipeline de decodificación

SabrResponseDecoder.decode lee todas las parts primero, luego itera. El único estado llevado entre parts es el header onesie actual (un ONESIE_HEADER aplica a las parts de datos onesie que le siguen). Cada part se registra en orden, luego se despacha:

  • MEDIA_HEADER (20) → decodifica y registra el header.
  • MEDIA (21) → el 1er byte del payload es el header id; la longitud restante se acumula como conteo de bytes de medio de ese id.
  • MEDIA_END (22) → 1er byte = header id, marca el id completo.
  • control parts ricas → decodifica en el campo tipado de SabrDecodedResponse + un resumen humano.
  • NEXT_REQUEST_POLICY tiene un trato especial: tras decodificar, el backoff se relee directamente del campo 4 proto (varint) como backoffTimeMs autoritativo.

La respuesta decodificada

SabrDecodedResponse sostiene cada part en orden más accesores tipados: headers de medio, conteos de bytes de medio por header-id (LinkedHashMap, sumados), ids media-end, metadata format-init, live metadata, datos onesie, y las control parts únicas (redirect, seek, error, reload, next-request policy, status de protección + max retries, backoff ms, …).

Los helpers de análisis son sobre lo que la sesión ramifica:

  • hasMedia() — algún header o byte de medio.
  • isNoMediaResponse() — el inverso.
  • isPolicyOnlyResponse() — sin medio pero con una next-request policy (un round de puro ritmo).
  • isProtectedNoMediaResponse() — sin medio y streamProtectionStatus >= 3: la frontera de PO token (status 3 + sin medio = mint un token).
  • getIntegrityIssues() — chequeos cruzados: duplicate-media-header, missing-media, length-mismatch:expected=…:actual=…, missing-media-end, media-without-header, media-end-without-header. La sesión trata cualquier resultado no vacío como fatal.

Siguiente: Medio, segmentos e índice.

Creado por Priveetee para el proyecto de código abierto PipePipe