diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml
index 7fe0c42..615d0ac 100644
--- a/.github/workflows/release-please.yml
+++ b/.github/workflows/release-please.yml
@@ -55,6 +55,6 @@ jobs:
- name: Publish to JSR
run: |
- npm run build --if-present
+ npm run build
npx jsr publish
if: ${{ steps.release.outputs.release_created }}
\ No newline at end of file
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..7e5b88d
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,39 @@
+name: Run Tests
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ node-version: [20.x]
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Use Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ matrix.node-version }}
+ cache: 'npm'
+
+ - name: Install protoc
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y unzip
+ curl -OL https://github.com/protocolbuffers/protobuf/releases/download/v21.12/protoc-21.12-linux-x86_64.zip
+ unzip protoc-21.12-linux-x86_64.zip -d protoc21
+ sudo mv protoc21/bin/protoc /usr/local/bin/
+
+ - name: Install dependencies
+ run: npm install
+
+ - name: Run tests
+ run: npm test
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..a55e7a1
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..8ca546d
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml
new file mode 100644
index 0000000..68cd9d6
--- /dev/null
+++ b/.idea/dataSources.xml
@@ -0,0 +1,12 @@
+
+
+
+
+ mongo.4
+ true
+ com.dbschema.MongoJdbcDriver
+ mongodb://localhost:27017
+ $ProjectFileDir$
+
+
+
\ No newline at end of file
diff --git a/.idea/dictionaries/luanl.xml b/.idea/dictionaries/luanl.xml
new file mode 100644
index 0000000..16f8440
--- /dev/null
+++ b/.idea/dictionaries/luanl.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/.idea/googlevideo.iml b/.idea/googlevideo.iml
new file mode 100644
index 0000000..24643cc
--- /dev/null
+++ b/.idea/googlevideo.iml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..03d9549
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml
new file mode 100644
index 0000000..d23208f
--- /dev/null
+++ b/.idea/jsLibraryMappings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/material_theme_project_new.xml b/.idea/material_theme_project_new.xml
new file mode 100644
index 0000000..39e3327
--- /dev/null
+++ b/.idea/material_theme_project_new.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..8fda519
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.npmignore b/.npmignore
index a3a68df..be7572e 100644
--- a/.npmignore
+++ b/.npmignore
@@ -1,6 +1,8 @@
**
src/
+docs/
!dist/**
!README.md
+!LICENSE
!bundle/**
\ No newline at end of file
diff --git a/README.md b/README.md
index 3f9aa48..0e84155 100644
--- a/README.md
+++ b/README.md
@@ -1,19 +1,11 @@
-# What Is This?
+# GoogleVideo
[](https://jsr.io/@luanrt/googlevideo)
[](https://www.npmjs.com/package/googlevideo)
[](./LICENSE)
-This is a collection of utilities for working with Google Video APIs, with a primary focus on UMP.
+A collection of modules for working with YouTube's proprietary video streaming protocols (UMP/SABR). It can be used to build clients or to integrate with existing media players (e.g., [Shaka Player](https://shaka-player-demo.appspot.com/docs/api/index.html)).
-
-* [Video Streaming Protos](./protos/video_streaming/)
-* [UMP (Parser)](./src/core/UMP.ts)
-* [ServerAbrStream (SABR Client)](./src/core/ServerAbrStream.ts)
-* [ChunkedDataBuffer (Buffer Manager)](./src/core/ChunkedDataBuffer.ts)
-
-The protobuf definitions were extracted from YouTube's Android and iOS clients, and the UMP parser and buffer manager are based on the implementation currently found on youtube.com.
-
-Usage examples can be found [here](./examples/).
+[API Reference →](https://ytjs.dev/googlevideo/api)
## Installation
@@ -30,46 +22,102 @@ npm install LuanRT/googlevideo
```
## Basic Usage
+Below is a basic example using the UMP modules to create a buffer, write parts, and process them:
+
+> [!NOTE]
+> More advanced usage examples can be found in the [/examples](./examples/) directory.
```typescript
-import GoogleVideo, { PART, Protos } from 'googlevideo';
+import { CompositeBuffer, UmpReader, UmpWriter } from 'googlevideo/ump';
+import { MediaHeader, UMPPartId } from 'googlevideo/protos';
+import { concatenateChunks } from 'googlevideo/utils';
+import { Part } from 'googlevideo/shared-types';
-const streamingUrl = 'https://abcd--a.googlevideo.com/videoplayback?...';
+function handleMediaHeader(part: Part) {
+ const mediaHeader = MediaHeader.decode(concatenateChunks(part.data.chunks));
+ console.log('Media Header:', mediaHeader);
+}
-const response = await fetch(streamingUrl, { method: 'POST' });
+function handleMedia(part: Part) {
+ const headerId = part.data.getUint8(0);
+ console.log(`Media Part (Associated Header ID: ${headerId}):`, part.data.split(1).remainingBuffer.getLength(), 'bytes');
+}
-const arrayBuffer = await response.arrayBuffer();
+function handleMediaEnd(part: Part) {
+ const headerId = part.data.getUint8(0);
+ console.log(`Media End Part (Associated Header ID: ${headerId}):`, part.data.split(1).remainingBuffer.getLength(), 'bytes');
+}
-const dataBuffer = new GoogleVideo.ChunkedDataBuffer();
-dataBuffer.append(new Uint8Array(arrayBuffer));
+const umpPartHandlers = new Map void>([
+ [ UMPPartId.MEDIA_HEADER, handleMediaHeader ],
+ [ UMPPartId.MEDIA, handleMedia ],
+ [ UMPPartId.MEDIA_END, handleMediaEnd ]
+]);
-const googUmp = new GoogleVideo.UMP(dataBuffer);
+const buffer = mockUmpData();
+const reader = new UmpReader(buffer);
-googUmp.parse((part) => {
- switch (part.type) {
- case PART.MEDIA_HEADER: {
- console.log('[MediaHeader]:', Protos.MediaHeader.decode(part.data.chunks[0]));
- break;
- }
- case PART.MEDIA: {
- const headerId = part.data.getUint8(0);
- const streamData = part.data.split(1).remainingBuffer;
- console.log('[Media]:', `Header ID: ${headerId}`, `length: ${streamData.getLength()}`);
- break;
- }
- case PART.MEDIA_END: {
- const headerId = part.data.getUint8(0);
- console.log('[MediaEnd]:', `Header ID: ${headerId}`);
- break;
- }
- default:
- console.log('Unhandled part:', part.type);
- break;
+reader.read((part) => {
+ const handler = umpPartHandlers.get(part.type);
+ if (handler) {
+ handler(part);
+ } else {
+ console.warn(`No handler for part type: ${part.type}`);
}
});
+
+/**
+ * Generates a mock UMP data buffer containing a MEDIA_HEADER, and respective MEDIA and MEDIA_END parts.
+ * This group represents a single audio segment, which is what you would typically see
+ * in a real UMP stream.
+ */
+function mockUmpData(): CompositeBuffer {
+ const buffer = new CompositeBuffer();
+ const writer = new UmpWriter(buffer);
+
+ const audioHeaderId = 0;
+
+ const partsToWrite: [UMPPartId, Uint8Array][] = [
+ [
+ UMPPartId.MEDIA_HEADER,
+ MediaHeader.encode({
+ headerId: audioHeaderId,
+ videoId: "sOa4VVlI9tE",
+ itag: 141,
+ lmt: 1645502668395260,
+ xtags: "",
+ startRange: 5463800,
+ isInitSeg: false,
+ sequenceNumber: 0,
+ durationMs: 0,
+ formatId: {
+ itag: 141,
+ lastModified: 1645502668395260,
+ xtags: ""
+ },
+ contentLength: 963966,
+ }).finish()
+ ],
+ [ UMPPartId.MEDIA, new Uint8Array([ audioHeaderId, ...new Uint8Array(827609).fill(0) ]) ],
+ [ UMPPartId.MEDIA, new Uint8Array([ audioHeaderId, ...new Uint8Array(136357).fill(0) ]) ],
+ [ UMPPartId.MEDIA_END, new Uint8Array([ audioHeaderId ]) ]
+ ];
+
+ for (const [type, data] of partsToWrite) {
+ writer.write(type, data);
+ }
+
+ return buffer;
+}
```
-For more advanced examples, including scenarios beyond just parsing responses, check out the [examples](./examples/).
+Expected output:
+```
+Media Header: { ... }
+Media Part (Associated Header ID: 0): 827609 bytes
+Media Part (Associated Header ID: 0): 136357 bytes
+Media End Part (Associated Header ID: 0): 0 bytes
+```
## License
Distributed under the [MIT](./LICENSE) License.
diff --git a/docs/api/README.md b/docs/api/README.md
new file mode 100644
index 0000000..1616610
--- /dev/null
+++ b/docs/api/README.md
@@ -0,0 +1,10 @@
+# googlevideo
+
+## Modules
+
+- [exports/protos](exports/protos/README.md)
+- [exports/sabr-stream](exports/sabr-stream/README.md)
+- [exports/sabr-streaming-adapter](exports/sabr-streaming-adapter/README.md)
+- [exports/ump](exports/ump/README.md)
+- [exports/utils](exports/utils/README.md)
+- [types/shared](types/shared/README.md)
diff --git a/docs/api/exports/protos/README.md b/docs/api/exports/protos/README.md
new file mode 100644
index 0000000..e0846dc
--- /dev/null
+++ b/docs/api/exports/protos/README.md
@@ -0,0 +1,99 @@
+[googlevideo](../../README.md) / exports/protos
+
+# exports/protos
+
+## Enumerations
+
+- [AudioQuality](enumerations/AudioQuality.md)
+- [CompressionType](enumerations/CompressionType.md)
+- [NetworkMeteredState](enumerations/NetworkMeteredState.md)
+- [OnesieHeaderType](enumerations/OnesieHeaderType.md)
+- [OnesieProxyStatus](enumerations/OnesieProxyStatus.md)
+- [OnesieRequestTarget](enumerations/OnesieRequestTarget.md)
+- [PlaybackAudioRouteOutputType](enumerations/PlaybackAudioRouteOutputType.md)
+- [SabrContextWritePolicy](enumerations/SabrContextWritePolicy.md)
+- [SeekSource](enumerations/SeekSource.md)
+- [UMPPartId](enumerations/UMPPartId.md)
+- [VideoQualitySetting](enumerations/VideoQualitySetting.md)
+
+## Interfaces
+
+- [AuthorizedFormat](interfaces/AuthorizedFormat.md)
+- [BufferedRange](interfaces/BufferedRange.md)
+- [ClientAbrState](interfaces/ClientAbrState.md)
+- [ClientInfo](interfaces/ClientInfo.md)
+- [CryptoParams](interfaces/CryptoParams.md)
+- [FormatId](interfaces/FormatId.md)
+- [FormatInitializationMetadata](interfaces/FormatInitializationMetadata.md)
+- [FormatSelectionConfig](interfaces/FormatSelectionConfig.md)
+- [HttpHeader](interfaces/HttpHeader.md)
+- [IdentifierToken](interfaces/IdentifierToken.md)
+- [InnertubeRequest](interfaces/InnertubeRequest.md)
+- [KeyValuePair](interfaces/KeyValuePair.md)
+- [LiveMetadata](interfaces/LiveMetadata.md)
+- [MediaCapabilities](interfaces/MediaCapabilities.md)
+- [MediaHeader](interfaces/MediaHeader.md)
+- [NextRequestPolicy](interfaces/NextRequestPolicy.md)
+- [OnesieHeader](interfaces/OnesieHeader.md)
+- [OnesieInnertubeRequest](interfaces/OnesieInnertubeRequest.md)
+- [OnesieInnertubeResponse](interfaces/OnesieInnertubeResponse.md)
+- [OnesieRequest](interfaces/OnesieRequest.md)
+- [PlaybackAuthorization](interfaces/PlaybackAuthorization.md)
+- [PlaybackCookie](interfaces/PlaybackCookie.md)
+- [PlaybackStartPolicy](interfaces/PlaybackStartPolicy.md)
+- [Range](interfaces/Range.md)
+- [ReloadPlaybackContext](interfaces/ReloadPlaybackContext.md)
+- [ReloadPlaybackParams](interfaces/ReloadPlaybackParams.md)
+- [RequestCancellationPolicy](interfaces/RequestCancellationPolicy.md)
+- [RequestIdentifier](interfaces/RequestIdentifier.md)
+- [SabrContextSendingPolicy](interfaces/SabrContextSendingPolicy.md)
+- [SabrContextUpdate](interfaces/SabrContextUpdate.md)
+- [SabrContextValue](interfaces/SabrContextValue.md)
+- [SabrError](interfaces/SabrError.md)
+- [SabrRedirect](interfaces/SabrRedirect.md)
+- [SnackbarMessage](interfaces/SnackbarMessage.md)
+- [StreamerContext](interfaces/StreamerContext.md)
+- [StreamProtectionStatus](interfaces/StreamProtectionStatus.md)
+- [UstreamerFlags](interfaces/UstreamerFlags.md)
+- [VideoPlaybackAbrRequest](interfaces/VideoPlaybackAbrRequest.md)
+
+## Variables
+
+- [AuthorizedFormat](variables/AuthorizedFormat.md)
+- [BufferedRange](variables/BufferedRange.md)
+- [ClientAbrState](variables/ClientAbrState.md)
+- [ClientInfo](variables/ClientInfo.md)
+- [CryptoParams](variables/CryptoParams.md)
+- [FormatId](variables/FormatId.md)
+- [FormatInitializationMetadata](variables/FormatInitializationMetadata.md)
+- [FormatSelectionConfig](variables/FormatSelectionConfig.md)
+- [HttpHeader](variables/HttpHeader.md)
+- [IdentifierToken](variables/IdentifierToken.md)
+- [InnertubeRequest](variables/InnertubeRequest.md)
+- [KeyValuePair](variables/KeyValuePair.md)
+- [LiveMetadata](variables/LiveMetadata.md)
+- [MediaCapabilities](variables/MediaCapabilities.md)
+- [MediaHeader](variables/MediaHeader.md)
+- [NextRequestPolicy](variables/NextRequestPolicy.md)
+- [OnesieHeader](variables/OnesieHeader.md)
+- [OnesieInnertubeRequest](variables/OnesieInnertubeRequest.md)
+- [OnesieInnertubeResponse](variables/OnesieInnertubeResponse.md)
+- [OnesieRequest](variables/OnesieRequest.md)
+- [PlaybackAuthorization](variables/PlaybackAuthorization.md)
+- [PlaybackCookie](variables/PlaybackCookie.md)
+- [PlaybackStartPolicy](variables/PlaybackStartPolicy.md)
+- [Range](variables/Range.md)
+- [ReloadPlaybackContext](variables/ReloadPlaybackContext.md)
+- [ReloadPlaybackParams](variables/ReloadPlaybackParams.md)
+- [RequestCancellationPolicy](variables/RequestCancellationPolicy.md)
+- [RequestIdentifier](variables/RequestIdentifier.md)
+- [SabrContextSendingPolicy](variables/SabrContextSendingPolicy.md)
+- [SabrContextUpdate](variables/SabrContextUpdate.md)
+- [SabrContextValue](variables/SabrContextValue.md)
+- [SabrError](variables/SabrError.md)
+- [SabrRedirect](variables/SabrRedirect.md)
+- [SnackbarMessage](variables/SnackbarMessage.md)
+- [StreamerContext](variables/StreamerContext.md)
+- [StreamProtectionStatus](variables/StreamProtectionStatus.md)
+- [UstreamerFlags](variables/UstreamerFlags.md)
+- [VideoPlaybackAbrRequest](variables/VideoPlaybackAbrRequest.md)
diff --git a/docs/api/exports/protos/enumerations/AudioQuality.md b/docs/api/exports/protos/enumerations/AudioQuality.md
new file mode 100644
index 0000000..afaae49
--- /dev/null
+++ b/docs/api/exports/protos/enumerations/AudioQuality.md
@@ -0,0 +1,53 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / AudioQuality
+
+# Enumeration: AudioQuality
+
+Defined in: [protos/generated/misc/common.ts:19](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L19)
+
+## Enumeration Members
+
+### HIGH
+
+> **HIGH**: `30`
+
+Defined in: [protos/generated/misc/common.ts:24](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L24)
+
+***
+
+### LOW
+
+> **LOW**: `10`
+
+Defined in: [protos/generated/misc/common.ts:22](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L22)
+
+***
+
+### MEDIUM
+
+> **MEDIUM**: `20`
+
+Defined in: [protos/generated/misc/common.ts:23](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L23)
+
+***
+
+### ULTRALOW
+
+> **ULTRALOW**: `5`
+
+Defined in: [protos/generated/misc/common.ts:21](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L21)
+
+***
+
+### UNKNOWN
+
+> **UNKNOWN**: `0`
+
+Defined in: [protos/generated/misc/common.ts:20](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L20)
+
+***
+
+### UNRECOGNIZED
+
+> **UNRECOGNIZED**: `-1`
+
+Defined in: [protos/generated/misc/common.ts:25](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L25)
diff --git a/docs/api/exports/protos/enumerations/CompressionType.md b/docs/api/exports/protos/enumerations/CompressionType.md
new file mode 100644
index 0000000..23faf5b
--- /dev/null
+++ b/docs/api/exports/protos/enumerations/CompressionType.md
@@ -0,0 +1,37 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / CompressionType
+
+# Enumeration: CompressionType
+
+Defined in: [protos/generated/misc/common.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L12)
+
+## Enumeration Members
+
+### BROTLI
+
+> **BROTLI**: `2`
+
+Defined in: [protos/generated/misc/common.ts:15](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L15)
+
+***
+
+### GZIP
+
+> **GZIP**: `1`
+
+Defined in: [protos/generated/misc/common.ts:14](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L14)
+
+***
+
+### UNKNOWN
+
+> **UNKNOWN**: `0`
+
+Defined in: [protos/generated/misc/common.ts:13](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L13)
+
+***
+
+### UNRECOGNIZED
+
+> **UNRECOGNIZED**: `-1`
+
+Defined in: [protos/generated/misc/common.ts:16](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L16)
diff --git a/docs/api/exports/protos/enumerations/NetworkMeteredState.md b/docs/api/exports/protos/enumerations/NetworkMeteredState.md
new file mode 100644
index 0000000..c867a9e
--- /dev/null
+++ b/docs/api/exports/protos/enumerations/NetworkMeteredState.md
@@ -0,0 +1,37 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / NetworkMeteredState
+
+# Enumeration: NetworkMeteredState
+
+Defined in: [protos/generated/misc/common.ts:53](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L53)
+
+## Enumeration Members
+
+### METERED
+
+> **METERED**: `2`
+
+Defined in: [protos/generated/misc/common.ts:56](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L56)
+
+***
+
+### UNKNOWN
+
+> **UNKNOWN**: `0`
+
+Defined in: [protos/generated/misc/common.ts:54](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L54)
+
+***
+
+### UNMETERED
+
+> **UNMETERED**: `1`
+
+Defined in: [protos/generated/misc/common.ts:55](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L55)
+
+***
+
+### UNRECOGNIZED
+
+> **UNRECOGNIZED**: `-1`
+
+Defined in: [protos/generated/misc/common.ts:57](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L57)
diff --git a/docs/api/exports/protos/enumerations/OnesieHeaderType.md b/docs/api/exports/protos/enumerations/OnesieHeaderType.md
new file mode 100644
index 0000000..4665ca5
--- /dev/null
+++ b/docs/api/exports/protos/enumerations/OnesieHeaderType.md
@@ -0,0 +1,109 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / OnesieHeaderType
+
+# Enumeration: OnesieHeaderType
+
+Defined in: [protos/generated/video\_streaming/onesie\_header\_type.ts:11](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_header_type.ts#L11)
+
+## Enumeration Members
+
+### ACK
+
+> **ACK**: `5`
+
+Defined in: [protos/generated/video\_streaming/onesie\_header\_type.ts:17](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_header_type.ts#L17)
+
+***
+
+### CLEAR\_INIT\_SEGMENT
+
+> **CLEAR\_INIT\_SEGMENT**: `4`
+
+Defined in: [protos/generated/video\_streaming/onesie\_header\_type.ts:16](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_header_type.ts#L16)
+
+***
+
+### CLEAR\_MEDIA
+
+> **CLEAR\_MEDIA**: `3`
+
+Defined in: [protos/generated/video\_streaming/onesie\_header\_type.ts:15](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_header_type.ts#L15)
+
+***
+
+### ENCRYPTED\_INNERTUBE\_RESPONSE\_PART
+
+> **ENCRYPTED\_INNERTUBE\_RESPONSE\_PART**: `25`
+
+Defined in: [protos/generated/video\_streaming/onesie\_header\_type.ts:23](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_header_type.ts#L23)
+
+***
+
+### LAST\_HIGH\_PRIORITY\_HINT
+
+> **LAST\_HIGH\_PRIORITY\_HINT**: `9`
+
+Defined in: [protos/generated/video\_streaming/onesie\_header\_type.ts:21](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_header_type.ts#L21)
+
+***
+
+### MEDIA
+
+> **MEDIA**: `1`
+
+Defined in: [protos/generated/video\_streaming/onesie\_header\_type.ts:13](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_header_type.ts#L13)
+
+***
+
+### MEDIA\_DECRYPTION\_KEY
+
+> **MEDIA\_DECRYPTION\_KEY**: `2`
+
+Defined in: [protos/generated/video\_streaming/onesie\_header\_type.ts:14](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_header_type.ts#L14)
+
+***
+
+### MEDIA\_SIZE\_HINT
+
+> **MEDIA\_SIZE\_HINT**: `7`
+
+Defined in: [protos/generated/video\_streaming/onesie\_header\_type.ts:19](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_header_type.ts#L19)
+
+***
+
+### MEDIA\_STREAMER\_HOSTNAME
+
+> **MEDIA\_STREAMER\_HOSTNAME**: `6`
+
+Defined in: [protos/generated/video\_streaming/onesie\_header\_type.ts:18](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_header_type.ts#L18)
+
+***
+
+### ONESIE\_PLAYER\_RESPONSE
+
+> **ONESIE\_PLAYER\_RESPONSE**: `0`
+
+Defined in: [protos/generated/video\_streaming/onesie\_header\_type.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_header_type.ts#L12)
+
+***
+
+### PLAYER\_SERVICE\_RESPONSE\_PUSH\_URL
+
+> **PLAYER\_SERVICE\_RESPONSE\_PUSH\_URL**: `8`
+
+Defined in: [protos/generated/video\_streaming/onesie\_header\_type.ts:20](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_header_type.ts#L20)
+
+***
+
+### STREAM\_METADATA
+
+> **STREAM\_METADATA**: `16`
+
+Defined in: [protos/generated/video\_streaming/onesie\_header\_type.ts:22](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_header_type.ts#L22)
+
+***
+
+### UNRECOGNIZED
+
+> **UNRECOGNIZED**: `-1`
+
+Defined in: [protos/generated/video\_streaming/onesie\_header\_type.ts:24](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_header_type.ts#L24)
diff --git a/docs/api/exports/protos/enumerations/OnesieProxyStatus.md b/docs/api/exports/protos/enumerations/OnesieProxyStatus.md
new file mode 100644
index 0000000..315ace6
--- /dev/null
+++ b/docs/api/exports/protos/enumerations/OnesieProxyStatus.md
@@ -0,0 +1,125 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / OnesieProxyStatus
+
+# Enumeration: OnesieProxyStatus
+
+Defined in: [protos/generated/video\_streaming/onesie\_proxy\_status.ts:11](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_proxy_status.ts#L11)
+
+## Enumeration Members
+
+### BACKEND\_ERROR
+
+> **BACKEND\_ERROR**: `7`
+
+Defined in: [protos/generated/video\_streaming/onesie\_proxy\_status.ts:19](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_proxy_status.ts#L19)
+
+***
+
+### CLIENT\_ERROR
+
+> **CLIENT\_ERROR**: `8`
+
+Defined in: [protos/generated/video\_streaming/onesie\_proxy\_status.ts:20](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_proxy_status.ts#L20)
+
+***
+
+### DECOMPRESSION\_FAILED
+
+> **DECOMPRESSION\_FAILED**: `11`
+
+Defined in: [protos/generated/video\_streaming/onesie\_proxy\_status.ts:23](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_proxy_status.ts#L23)
+
+***
+
+### DECRYPTION\_FAILED
+
+> **DECRYPTION\_FAILED**: `2`
+
+Defined in: [protos/generated/video\_streaming/onesie\_proxy\_status.ts:14](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_proxy_status.ts#L14)
+
+***
+
+### INVALID\_CONTENT\_TYPE
+
+> **INVALID\_CONTENT\_TYPE**: `6`
+
+Defined in: [protos/generated/video\_streaming/onesie\_proxy\_status.ts:18](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_proxy_status.ts#L18)
+
+***
+
+### INVALID\_X\_FORWARDED\_FOR
+
+> **INVALID\_X\_FORWARDED\_FOR**: `5`
+
+Defined in: [protos/generated/video\_streaming/onesie\_proxy\_status.ts:17](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_proxy_status.ts#L17)
+
+***
+
+### JSON\_PARSING\_FAILED
+
+> **JSON\_PARSING\_FAILED**: `12`
+
+Defined in: [protos/generated/video\_streaming/onesie\_proxy\_status.ts:24](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_proxy_status.ts#L24)
+
+***
+
+### MISSING\_CRYPTER
+
+> **MISSING\_CRYPTER**: `9`
+
+Defined in: [protos/generated/video\_streaming/onesie\_proxy\_status.ts:21](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_proxy_status.ts#L21)
+
+***
+
+### MISSING\_X\_FORWARDED\_FOR
+
+> **MISSING\_X\_FORWARDED\_FOR**: `4`
+
+Defined in: [protos/generated/video\_streaming/onesie\_proxy\_status.ts:16](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_proxy_status.ts#L16)
+
+***
+
+### OK
+
+> **OK**: `1`
+
+Defined in: [protos/generated/video\_streaming/onesie\_proxy\_status.ts:13](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_proxy_status.ts#L13)
+
+***
+
+### PARSING\_FAILED
+
+> **PARSING\_FAILED**: `3`
+
+Defined in: [protos/generated/video\_streaming/onesie\_proxy\_status.ts:15](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_proxy_status.ts#L15)
+
+***
+
+### RESPONSE\_JSON\_SERIALIZATION\_FAILED
+
+> **RESPONSE\_JSON\_SERIALIZATION\_FAILED**: `10`
+
+Defined in: [protos/generated/video\_streaming/onesie\_proxy\_status.ts:22](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_proxy_status.ts#L22)
+
+***
+
+### UNKNOWN
+
+> **UNKNOWN**: `0`
+
+Defined in: [protos/generated/video\_streaming/onesie\_proxy\_status.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_proxy_status.ts#L12)
+
+***
+
+### UNKNOWN\_COMPRESSION\_TYPE
+
+> **UNKNOWN\_COMPRESSION\_TYPE**: `13`
+
+Defined in: [protos/generated/video\_streaming/onesie\_proxy\_status.ts:25](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_proxy_status.ts#L25)
+
+***
+
+### UNRECOGNIZED
+
+> **UNRECOGNIZED**: `-1`
+
+Defined in: [protos/generated/video\_streaming/onesie\_proxy\_status.ts:26](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_proxy_status.ts#L26)
diff --git a/docs/api/exports/protos/enumerations/OnesieRequestTarget.md b/docs/api/exports/protos/enumerations/OnesieRequestTarget.md
new file mode 100644
index 0000000..10dfe1a
--- /dev/null
+++ b/docs/api/exports/protos/enumerations/OnesieRequestTarget.md
@@ -0,0 +1,53 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / OnesieRequestTarget
+
+# Enumeration: OnesieRequestTarget
+
+Defined in: [protos/generated/misc/common.ts:172](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L172)
+
+## Enumeration Members
+
+### ENCRYPTED\_PLAYER\_SERVICE
+
+> **ENCRYPTED\_PLAYER\_SERVICE**: `1`
+
+Defined in: [protos/generated/misc/common.ts:174](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L174)
+
+***
+
+### ENCRYPTED\_WATCH\_SERVICE
+
+> **ENCRYPTED\_WATCH\_SERVICE**: `3`
+
+Defined in: [protos/generated/misc/common.ts:176](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L176)
+
+***
+
+### ENCRYPTED\_WATCH\_SERVICE\_DEPRECATED
+
+> **ENCRYPTED\_WATCH\_SERVICE\_DEPRECATED**: `2`
+
+Defined in: [protos/generated/misc/common.ts:175](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L175)
+
+***
+
+### INNERTUBE\_ENCRYPTED\_SERVICE
+
+> **INNERTUBE\_ENCRYPTED\_SERVICE**: `4`
+
+Defined in: [protos/generated/misc/common.ts:177](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L177)
+
+***
+
+### UNKNOWN
+
+> **UNKNOWN**: `0`
+
+Defined in: [protos/generated/misc/common.ts:173](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L173)
+
+***
+
+### UNRECOGNIZED
+
+> **UNRECOGNIZED**: `-1`
+
+Defined in: [protos/generated/misc/common.ts:178](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L178)
diff --git a/docs/api/exports/protos/enumerations/PlaybackAudioRouteOutputType.md b/docs/api/exports/protos/enumerations/PlaybackAudioRouteOutputType.md
new file mode 100644
index 0000000..d873203
--- /dev/null
+++ b/docs/api/exports/protos/enumerations/PlaybackAudioRouteOutputType.md
@@ -0,0 +1,117 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / PlaybackAudioRouteOutputType
+
+# Enumeration: PlaybackAudioRouteOutputType
+
+Defined in: [protos/generated/misc/common.ts:36](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L36)
+
+## Enumeration Members
+
+### AIR\_PLAY
+
+> **AIR\_PLAY**: `7`
+
+Defined in: [protos/generated/misc/common.ts:44](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L44)
+
+***
+
+### ANDROID\_AUDIO
+
+> **ANDROID\_AUDIO**: `12`
+
+Defined in: [protos/generated/misc/common.ts:49](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L49)
+
+***
+
+### BLUETOOTH\_A2DP
+
+> **BLUETOOTH\_A2DP**: `3`
+
+Defined in: [protos/generated/misc/common.ts:40](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L40)
+
+***
+
+### BLUETOOTH\_HFP
+
+> **BLUETOOTH\_HFP**: `9`
+
+Defined in: [protos/generated/misc/common.ts:46](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L46)
+
+***
+
+### BLUETOOTH\_LE
+
+> **BLUETOOTH\_LE**: `8`
+
+Defined in: [protos/generated/misc/common.ts:45](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L45)
+
+***
+
+### BUILT\_IN\_RECEIVER
+
+> **BUILT\_IN\_RECEIVER**: `4`
+
+Defined in: [protos/generated/misc/common.ts:41](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L41)
+
+***
+
+### BUILT\_IN\_SPEAKER
+
+> **BUILT\_IN\_SPEAKER**: `5`
+
+Defined in: [protos/generated/misc/common.ts:42](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L42)
+
+***
+
+### CAR\_PLAY
+
+> **CAR\_PLAY**: `11`
+
+Defined in: [protos/generated/misc/common.ts:48](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L48)
+
+***
+
+### HDMI
+
+> **HDMI**: `6`
+
+Defined in: [protos/generated/misc/common.ts:43](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L43)
+
+***
+
+### HEADPHONES
+
+> **HEADPHONES**: `2`
+
+Defined in: [protos/generated/misc/common.ts:39](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L39)
+
+***
+
+### LINE\_OUT
+
+> **LINE\_OUT**: `1`
+
+Defined in: [protos/generated/misc/common.ts:38](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L38)
+
+***
+
+### UNKNOWN
+
+> **UNKNOWN**: `0`
+
+Defined in: [protos/generated/misc/common.ts:37](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L37)
+
+***
+
+### UNRECOGNIZED
+
+> **UNRECOGNIZED**: `-1`
+
+Defined in: [protos/generated/misc/common.ts:50](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L50)
+
+***
+
+### USB\_AUDIO
+
+> **USB\_AUDIO**: `10`
+
+Defined in: [protos/generated/misc/common.ts:47](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L47)
diff --git a/docs/api/exports/protos/enumerations/SabrContextWritePolicy.md b/docs/api/exports/protos/enumerations/SabrContextWritePolicy.md
new file mode 100644
index 0000000..f2a1348
--- /dev/null
+++ b/docs/api/exports/protos/enumerations/SabrContextWritePolicy.md
@@ -0,0 +1,37 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / SabrContextWritePolicy
+
+# Enumeration: SabrContextWritePolicy
+
+Defined in: [protos/generated/video\_streaming/sabr\_context\_update.ts:29](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/sabr_context_update.ts#L29)
+
+## Enumeration Members
+
+### KEEP\_EXISTING
+
+> **KEEP\_EXISTING**: `2`
+
+Defined in: [protos/generated/video\_streaming/sabr\_context\_update.ts:32](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/sabr_context_update.ts#L32)
+
+***
+
+### OVERWRITE
+
+> **OVERWRITE**: `1`
+
+Defined in: [protos/generated/video\_streaming/sabr\_context\_update.ts:31](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/sabr_context_update.ts#L31)
+
+***
+
+### UNRECOGNIZED
+
+> **UNRECOGNIZED**: `-1`
+
+Defined in: [protos/generated/video\_streaming/sabr\_context\_update.ts:33](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/sabr_context_update.ts#L33)
+
+***
+
+### UNSPECIFIED
+
+> **UNSPECIFIED**: `0`
+
+Defined in: [protos/generated/video\_streaming/sabr\_context\_update.ts:30](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/sabr_context_update.ts#L30)
diff --git a/docs/api/exports/protos/enumerations/SeekSource.md b/docs/api/exports/protos/enumerations/SeekSource.md
new file mode 100644
index 0000000..abf0817
--- /dev/null
+++ b/docs/api/exports/protos/enumerations/SeekSource.md
@@ -0,0 +1,877 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / SeekSource
+
+# Enumeration: SeekSource
+
+Defined in: [protos/generated/misc/common.ts:60](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L60)
+
+## Enumeration Members
+
+### ANDROID\_CLEAR\_BUFFER
+
+> **ANDROID\_CLEAR\_BUFFER**: `110`
+
+Defined in: [protos/generated/misc/common.ts:168](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L168)
+
+***
+
+### ANDROID\_MEDIA\_SESSION
+
+> **ANDROID\_MEDIA\_SESSION**: `35`
+
+Defined in: [protos/generated/misc/common.ts:95](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L95)
+
+***
+
+### AUTOMATIC\_PREVIEW\_REPLAY\_ACTION
+
+> **AUTOMATIC\_PREVIEW\_REPLAY\_ACTION**: `103`
+
+Defined in: [protos/generated/misc/common.ts:161](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L161)
+
+***
+
+### AUTOMATIC\_REPLAY\_ACTION
+
+> **AUTOMATIC\_REPLAY\_ACTION**: `37`
+
+Defined in: [protos/generated/misc/common.ts:97](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L97)
+
+***
+
+### CLIP\_SLIDE\_ON\_FLIMSTRIP
+
+> **CLIP\_SLIDE\_ON\_FLIMSTRIP**: `61`
+
+Defined in: [protos/generated/misc/common.ts:122](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L122)
+
+***
+
+### DOUBLE\_TAP\_TO\_SEEK
+
+> **DOUBLE\_TAP\_TO\_SEEK**: `4`
+
+Defined in: [protos/generated/misc/common.ts:65](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L65)
+
+***
+
+### DOUBLE\_TAP\_TO\_SKIP\_CHAPTER
+
+> **DOUBLE\_TAP\_TO\_SKIP\_CHAPTER**: `5`
+
+Defined in: [protos/generated/misc/common.ts:66](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L66)
+
+***
+
+### FINE\_SCRUBBER\_CANCELLED
+
+> **FINE\_SCRUBBER\_CANCELLED**: `63`
+
+Defined in: [protos/generated/misc/common.ts:124](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L124)
+
+***
+
+### FINE\_SCRUBBER\_SLIDE\_ON\_FILMSTRIP
+
+> **FINE\_SCRUBBER\_SLIDE\_ON\_FILMSTRIP**: `25`
+
+Defined in: [protos/generated/misc/common.ts:85](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L85)
+
+***
+
+### FINE\_SCRUBBER\_SLIDE\_ON\_SCRUBBER\_BAR
+
+> **FINE\_SCRUBBER\_SLIDE\_ON\_SCRUBBER\_BAR**: `27`
+
+Defined in: [protos/generated/misc/common.ts:87](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L87)
+
+***
+
+### FINE\_SCRUBBER\_TAP\_ON\_FILMSTRIP
+
+> **FINE\_SCRUBBER\_TAP\_ON\_FILMSTRIP**: `26`
+
+Defined in: [protos/generated/misc/common.ts:86](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L86)
+
+***
+
+### H5\_MEDIA\_ELEMENT\_EVENT
+
+> **H5\_MEDIA\_ELEMENT\_EVENT**: `104`
+
+Defined in: [protos/generated/misc/common.ts:162](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L162)
+
+***
+
+### H5\_WORKAROUND\_SEEK
+
+> **H5\_WORKAROUND\_SEEK**: `105`
+
+Defined in: [protos/generated/misc/common.ts:163](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L163)
+
+***
+
+### HIDDEN\_FAST\_FORWARD\_BUTTON
+
+> **HIDDEN\_FAST\_FORWARD\_BUTTON**: `82`
+
+Defined in: [protos/generated/misc/common.ts:142](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L142)
+
+***
+
+### HIDDEN\_REWIND\_BUTTON
+
+> **HIDDEN\_REWIND\_BUTTON**: `83`
+
+Defined in: [protos/generated/misc/common.ts:143](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L143)
+
+***
+
+### HIGHLIGHTS\_AUTOMATIC\_NEXT\_PLAY
+
+> **HIGHLIGHTS\_AUTOMATIC\_NEXT\_PLAY**: `43`
+
+Defined in: [protos/generated/misc/common.ts:104](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L104)
+
+***
+
+### HIGHLIGHTS\_PLAYER\_EXIT\_FULLSCREEN
+
+> **HIGHLIGHTS\_PLAYER\_EXIT\_FULLSCREEN**: `67`
+
+Defined in: [protos/generated/misc/common.ts:127](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L127)
+
+***
+
+### HIGHLIGHTS\_SEEK\_TO\_END
+
+> **HIGHLIGHTS\_SEEK\_TO\_END**: `45`
+
+Defined in: [protos/generated/misc/common.ts:106](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L106)
+
+***
+
+### HIGHLIGHTS\_SEEK\_TO\_FIRST\_PLAY
+
+> **HIGHLIGHTS\_SEEK\_TO\_FIRST\_PLAY**: `44`
+
+Defined in: [protos/generated/misc/common.ts:105](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L105)
+
+***
+
+### HIGHLIGHTS\_TAP\_HIDDEN\_NEXT\_PLAY
+
+> **HIGHLIGHTS\_TAP\_HIDDEN\_NEXT\_PLAY**: `41`
+
+Defined in: [protos/generated/misc/common.ts:102](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L102)
+
+***
+
+### HIGHLIGHTS\_TAP\_LIST\_ITEM
+
+> **HIGHLIGHTS\_TAP\_LIST\_ITEM**: `42`
+
+Defined in: [protos/generated/misc/common.ts:103](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L103)
+
+***
+
+### HIGHLIGHTS\_TAP\_NEXT\_PLAY
+
+> **HIGHLIGHTS\_TAP\_NEXT\_PLAY**: `40`
+
+Defined in: [protos/generated/misc/common.ts:101](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L101)
+
+***
+
+### HIGHLIGHTS\_TAP\_PREVIOUS\_PLAY
+
+> **HIGHLIGHTS\_TAP\_PREVIOUS\_PLAY**: `66`
+
+Defined in: [protos/generated/misc/common.ts:100](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L100)
+
+***
+
+### INLINE\_PLAYER\_SEEK\_CHAPTER
+
+> **INLINE\_PLAYER\_SEEK\_CHAPTER**: `64`
+
+Defined in: [protos/generated/misc/common.ts:125](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L125)
+
+***
+
+### INLINE\_PLAYER\_SEEK\_SECONDS
+
+> **INLINE\_PLAYER\_SEEK\_SECONDS**: `65`
+
+Defined in: [protos/generated/misc/common.ts:126](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L126)
+
+***
+
+### IOS\_PLAYER\_ITEM\_SEEK
+
+> **IOS\_PLAYER\_ITEM\_SEEK**: `21`
+
+Defined in: [protos/generated/misc/common.ts:81](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L81)
+
+***
+
+### IOS\_PLAYER\_ITEM\_SEEK\_TO\_END
+
+> **IOS\_PLAYER\_ITEM\_SEEK\_TO\_END**: `22`
+
+Defined in: [protos/generated/misc/common.ts:82](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L82)
+
+***
+
+### IOS\_PLAYER\_REMOVED\_SEGMENTS
+
+> **IOS\_PLAYER\_REMOVED\_SEGMENTS**: `19`
+
+Defined in: [protos/generated/misc/common.ts:79](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L79)
+
+***
+
+### IOS\_PLAYER\_SEEK\_TO\_END\_TO\_RESYNC
+
+> **IOS\_PLAYER\_SEEK\_TO\_END\_TO\_RESYNC**: `23`
+
+Defined in: [protos/generated/misc/common.ts:83](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L83)
+
+***
+
+### IOS\_PLAYER\_SEGMENT\_LIST
+
+> **IOS\_PLAYER\_SEGMENT\_LIST**: `20`
+
+Defined in: [protos/generated/misc/common.ts:80](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L80)
+
+***
+
+### IOS\_SEEK\_ACCESSIBILITY\_BUTTON
+
+> **IOS\_SEEK\_ACCESSIBILITY\_BUTTON**: `24`
+
+Defined in: [protos/generated/misc/common.ts:84](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L84)
+
+***
+
+### IOS\_SHAREPLAY\_PAUSE
+
+> **IOS\_SHAREPLAY\_PAUSE**: `54`
+
+Defined in: [protos/generated/misc/common.ts:115](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L115)
+
+***
+
+### IOS\_SHAREPLAY\_SEEK
+
+> **IOS\_SHAREPLAY\_SEEK**: `55`
+
+Defined in: [protos/generated/misc/common.ts:116](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L116)
+
+***
+
+### IOS\_SHAREPLAY\_SYNC\_RESPONSE
+
+> **IOS\_SHAREPLAY\_SYNC\_RESPONSE**: `56`
+
+Defined in: [protos/generated/misc/common.ts:117](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L117)
+
+***
+
+### KEYBOARD\_SEEK\_TO\_BEGINNING
+
+> **KEYBOARD\_SEEK\_TO\_BEGINNING**: `79`
+
+Defined in: [protos/generated/misc/common.ts:139](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L139)
+
+***
+
+### KEYBOARD\_SEEK\_TO\_END
+
+> **KEYBOARD\_SEEK\_TO\_END**: `80`
+
+Defined in: [protos/generated/misc/common.ts:140](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L140)
+
+***
+
+### LARGE\_CONTROLS\_FORWARD\_BUTTON
+
+> **LARGE\_CONTROLS\_FORWARD\_BUTTON**: `68`
+
+Defined in: [protos/generated/misc/common.ts:128](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L128)
+
+***
+
+### LARGE\_CONTROLS\_REWIND\_BUTTON
+
+> **LARGE\_CONTROLS\_REWIND\_BUTTON**: `69`
+
+Defined in: [protos/generated/misc/common.ts:129](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L129)
+
+***
+
+### LARGE\_CONTROLS\_SCRUBBER\_BAR
+
+> **LARGE\_CONTROLS\_SCRUBBER\_BAR**: `70`
+
+Defined in: [protos/generated/misc/common.ts:130](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L130)
+
+***
+
+### LR\_KEY\_PLAYS
+
+> **LR\_KEY\_PLAYS**: `96`
+
+Defined in: [protos/generated/misc/common.ts:154](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L154)
+
+***
+
+### LR\_MEDIA\_SESSION\_SEEK
+
+> **LR\_MEDIA\_SESSION\_SEEK**: `87`
+
+Defined in: [protos/generated/misc/common.ts:145](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L145)
+
+***
+
+### LR\_PLAYER\_CONTROL\_ACTION
+
+> **LR\_PLAYER\_CONTROL\_ACTION**: `94`
+
+Defined in: [protos/generated/misc/common.ts:152](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L152)
+
+***
+
+### LR\_QUICK\_SEEK
+
+> **LR\_QUICK\_SEEK**: `92`
+
+Defined in: [protos/generated/misc/common.ts:150](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L150)
+
+***
+
+### MACRO\_MARKER\_LIST\_ITEM
+
+> **MACRO\_MARKER\_LIST\_ITEM**: `3`
+
+Defined in: [protos/generated/misc/common.ts:64](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L64)
+
+***
+
+### MIDROLLS\_WITH\_TIME\_RANGE
+
+> **MIDROLLS\_WITH\_TIME\_RANGE**: `88`
+
+Defined in: [protos/generated/misc/common.ts:146](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L146)
+
+***
+
+### MINIPLAYER\_FAST\_FORWARD\_BUTTON
+
+> **MINIPLAYER\_FAST\_FORWARD\_BUTTON**: `107`
+
+Defined in: [protos/generated/misc/common.ts:165](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L165)
+
+***
+
+### MINIPLAYER\_REWIND\_BUTTON
+
+> **MINIPLAYER\_REWIND\_BUTTON**: `106`
+
+Defined in: [protos/generated/misc/common.ts:164](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L164)
+
+***
+
+### MOVING\_CLIP\_FRAME
+
+> **MOVING\_CLIP\_FRAME**: `50`
+
+Defined in: [protos/generated/misc/common.ts:111](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L111)
+
+***
+
+### NON\_USER\_SEEK\_TO\_NEXT
+
+> **NON\_USER\_SEEK\_TO\_NEXT**: `39`
+
+Defined in: [protos/generated/misc/common.ts:99](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L99)
+
+***
+
+### NON\_USER\_SEEK\_TO\_PREVIOUS
+
+> **NON\_USER\_SEEK\_TO\_PREVIOUS**: `38`
+
+Defined in: [protos/generated/misc/common.ts:98](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L98)
+
+***
+
+### ONESIE\_LIVE
+
+> **ONESIE\_LIVE**: `93`
+
+Defined in: [protos/generated/misc/common.ts:151](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L151)
+
+***
+
+### PEG\_TO\_LIVE
+
+> **PEG\_TO\_LIVE**: `34`
+
+Defined in: [protos/generated/misc/common.ts:94](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L94)
+
+***
+
+### PICK\_UP\_CLIP\_SLIDER
+
+> **PICK\_UP\_CLIP\_SLIDER**: `62`
+
+Defined in: [protos/generated/misc/common.ts:123](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L123)
+
+***
+
+### PICK\_UP\_PLAY\_HEAD
+
+> **PICK\_UP\_PLAY\_HEAD**: `6`
+
+Defined in: [protos/generated/misc/common.ts:67](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L67)
+
+***
+
+### PIP\_FAST\_FORWARD\_BUTTON
+
+> **PIP\_FAST\_FORWARD\_BUTTON**: `47`
+
+Defined in: [protos/generated/misc/common.ts:108](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L108)
+
+***
+
+### PIP\_RESUME\_ON\_HEAD
+
+> **PIP\_RESUME\_ON\_HEAD**: `49`
+
+Defined in: [protos/generated/misc/common.ts:110](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L110)
+
+***
+
+### PIP\_REWIND\_BUTTON
+
+> **PIP\_REWIND\_BUTTON**: `48`
+
+Defined in: [protos/generated/misc/common.ts:109](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L109)
+
+***
+
+### PLAYER\_VIEW\_REPARENT\_INTERNAL
+
+> **PLAYER\_VIEW\_REPARENT\_INTERNAL**: `30`
+
+Defined in: [protos/generated/misc/common.ts:90](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L90)
+
+***
+
+### PRESS\_FAST\_FORWARD\_PLAY\_BACK\_CONTROL
+
+> **PRESS\_FAST\_FORWARD\_PLAY\_BACK\_CONTROL**: `32`
+
+Defined in: [protos/generated/misc/common.ts:92](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L92)
+
+***
+
+### PRESS\_LIVE\_SYNC\_ICON
+
+> **PRESS\_LIVE\_SYNC\_ICON**: `33`
+
+Defined in: [protos/generated/misc/common.ts:93](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L93)
+
+***
+
+### PRESS\_REWIND\_PLAY\_BACK\_CONTROL
+
+> **PRESS\_REWIND\_PLAY\_BACK\_CONTROL**: `31`
+
+Defined in: [protos/generated/misc/common.ts:91](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L91)
+
+***
+
+### RESUME\_CLIP\_PREVIOUS\_POSITION
+
+> **RESUME\_CLIP\_PREVIOUS\_POSITION**: `51`
+
+Defined in: [protos/generated/misc/common.ts:112](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L112)
+
+***
+
+### SABR\_ACCURATE\_SEEK
+
+> **SABR\_ACCURATE\_SEEK**: `17`
+
+Defined in: [protos/generated/misc/common.ts:77](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L77)
+
+***
+
+### SABR\_INGESTION\_WALL\_TIME\_SEEK
+
+> **SABR\_INGESTION\_WALL\_TIME\_SEEK**: `29`
+
+Defined in: [protos/generated/misc/common.ts:89](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L89)
+
+***
+
+### SABR\_LIVE\_DVR\_USER\_SEEK
+
+> **SABR\_LIVE\_DVR\_USER\_SEEK**: `11`
+
+Defined in: [protos/generated/misc/common.ts:72](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L72)
+
+***
+
+### SABR\_PARTIAL\_CHUNK
+
+> **SABR\_PARTIAL\_CHUNK**: `9`
+
+Defined in: [protos/generated/misc/common.ts:70](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L70)
+
+***
+
+### SABR\_RELOAD\_PLAYER\_RESPONSE\_TOKEN\_SEEK
+
+> **SABR\_RELOAD\_PLAYER\_RESPONSE\_TOKEN\_SEEK**: `108`
+
+Defined in: [protos/generated/misc/common.ts:166](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L166)
+
+***
+
+### SABR\_SEEK\_TO\_CLOSEST\_KEYFRAME
+
+> **SABR\_SEEK\_TO\_CLOSEST\_KEYFRAME**: `59`
+
+Defined in: [protos/generated/misc/common.ts:120](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L120)
+
+***
+
+### SABR\_SEEK\_TO\_DVR\_LOWER\_BOUND
+
+> **SABR\_SEEK\_TO\_DVR\_LOWER\_BOUND**: `12`
+
+Defined in: [protos/generated/misc/common.ts:73](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L73)
+
+***
+
+### SABR\_SEEK\_TO\_DVR\_UPPER\_BOUND
+
+> **SABR\_SEEK\_TO\_DVR\_UPPER\_BOUND**: `13`
+
+Defined in: [protos/generated/misc/common.ts:74](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L74)
+
+***
+
+### SABR\_SEEK\_TO\_HEAD
+
+> **SABR\_SEEK\_TO\_HEAD**: `10`
+
+Defined in: [protos/generated/misc/common.ts:71](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L71)
+
+***
+
+### SEEK\_BACKWARD\_10S
+
+> **SEEK\_BACKWARD\_10S**: `73`
+
+Defined in: [protos/generated/misc/common.ts:133](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L133)
+
+***
+
+### SEEK\_BACKWARD\_5S
+
+> **SEEK\_BACKWARD\_5S**: `71`
+
+Defined in: [protos/generated/misc/common.ts:131](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L131)
+
+***
+
+### SEEK\_BACKWARD\_60S
+
+> **SEEK\_BACKWARD\_60S**: `76`
+
+Defined in: [protos/generated/misc/common.ts:136](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L136)
+
+***
+
+### SEEK\_BUTTON\_ON\_PLAYER\_CONTROL
+
+> **SEEK\_BUTTON\_ON\_PLAYER\_CONTROL**: `28`
+
+Defined in: [protos/generated/misc/common.ts:88](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L88)
+
+***
+
+### SEEK\_FORWARD\_10S
+
+> **SEEK\_FORWARD\_10S**: `74`
+
+Defined in: [protos/generated/misc/common.ts:134](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L134)
+
+***
+
+### SEEK\_FORWARD\_5S
+
+> **SEEK\_FORWARD\_5S**: `72`
+
+Defined in: [protos/generated/misc/common.ts:132](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L132)
+
+***
+
+### SEEK\_FORWARD\_60S
+
+> **SEEK\_FORWARD\_60S**: `75`
+
+Defined in: [protos/generated/misc/common.ts:135](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L135)
+
+***
+
+### SEEK\_PERCENT\_OF\_VIDEO
+
+> **SEEK\_PERCENT\_OF\_VIDEO**: `81`
+
+Defined in: [protos/generated/misc/common.ts:141](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L141)
+
+***
+
+### SEEK\_TO\_END\_OF\_LOOPING\_RANGE\_OF\_SHORTS
+
+> **SEEK\_TO\_END\_OF\_LOOPING\_RANGE\_OF\_SHORTS**: `60`
+
+Defined in: [protos/generated/misc/common.ts:121](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L121)
+
+***
+
+### SEEK\_TO\_HEAD
+
+> **SEEK\_TO\_HEAD**: `102`
+
+Defined in: [protos/generated/misc/common.ts:160](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L160)
+
+***
+
+### SEEK\_TO\_HEAD\_IMMERSIVE\_LIVE\_VIDEO
+
+> **SEEK\_TO\_HEAD\_IMMERSIVE\_LIVE\_VIDEO**: `57`
+
+Defined in: [protos/generated/misc/common.ts:118](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L118)
+
+***
+
+### SEEK\_TO\_NEXT
+
+> **SEEK\_TO\_NEXT**: `91`
+
+Defined in: [protos/generated/misc/common.ts:149](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L149)
+
+***
+
+### SEEK\_TO\_NEXT\_CHAPTER
+
+> **SEEK\_TO\_NEXT\_CHAPTER**: `52`
+
+Defined in: [protos/generated/misc/common.ts:113](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L113)
+
+***
+
+### SEEK\_TO\_NEXT\_FRAME
+
+> **SEEK\_TO\_NEXT\_FRAME**: `77`
+
+Defined in: [protos/generated/misc/common.ts:137](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L137)
+
+***
+
+### SEEK\_TO\_PREV\_FRAME
+
+> **SEEK\_TO\_PREV\_FRAME**: `78`
+
+Defined in: [protos/generated/misc/common.ts:138](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L138)
+
+***
+
+### SEEK\_TO\_PREVIOUS
+
+> **SEEK\_TO\_PREVIOUS**: `90`
+
+Defined in: [protos/generated/misc/common.ts:148](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L148)
+
+***
+
+### SEEK\_TO\_PREVIOUS\_CHAPTER
+
+> **SEEK\_TO\_PREVIOUS\_CHAPTER**: `53`
+
+Defined in: [protos/generated/misc/common.ts:114](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L114)
+
+***
+
+### SEEK\_TO\_START\_OF\_LOOPING\_RANGE\_OF\_SHORTS
+
+> **SEEK\_TO\_START\_OF\_LOOPING\_RANGE\_OF\_SHORTS**: `58`
+
+Defined in: [protos/generated/misc/common.ts:119](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L119)
+
+***
+
+### SEGMENTS\_TAP\_LIST\_ITEM
+
+> **SEGMENTS\_TAP\_LIST\_ITEM**: `46`
+
+Defined in: [protos/generated/misc/common.ts:107](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L107)
+
+***
+
+### SKIP\_AD
+
+> **SKIP\_AD**: `89`
+
+Defined in: [protos/generated/misc/common.ts:147](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L147)
+
+***
+
+### SLIDE\_ON\_PLAYER
+
+> **SLIDE\_ON\_PLAYER**: `8`
+
+Defined in: [protos/generated/misc/common.ts:69](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L69)
+
+***
+
+### SLIDE\_ON\_SCRUBBER\_BAR
+
+> **SLIDE\_ON\_SCRUBBER\_BAR**: `7`
+
+Defined in: [protos/generated/misc/common.ts:68](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L68)
+
+***
+
+### SLIDE\_ON\_SCRUBBER\_BAR\_CHAPTER
+
+> **SLIDE\_ON\_SCRUBBER\_BAR\_CHAPTER**: `109`
+
+Defined in: [protos/generated/misc/common.ts:167](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L167)
+
+***
+
+### SSAP\_AD\_FMT\_FATAL
+
+> **SSAP\_AD\_FMT\_FATAL**: `97`
+
+Defined in: [protos/generated/misc/common.ts:155](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L155)
+
+***
+
+### SSDAI\_INTERNAL
+
+> **SSDAI\_INTERNAL**: `14`
+
+Defined in: [protos/generated/misc/common.ts:75](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L75)
+
+***
+
+### START\_PLAYBACK
+
+> **START\_PLAYBACK**: `15`
+
+Defined in: [protos/generated/misc/common.ts:76](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L76)
+
+***
+
+### START\_PLAYBACK\_SEEK\_TO\_END
+
+> **START\_PLAYBACK\_SEEK\_TO\_END**: `18`
+
+Defined in: [protos/generated/misc/common.ts:78](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L78)
+
+***
+
+### TAP\_ON\_REPLAY\_ACTION
+
+> **TAP\_ON\_REPLAY\_ACTION**: `36`
+
+Defined in: [protos/generated/misc/common.ts:96](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L96)
+
+***
+
+### TIMESTAMP
+
+> **TIMESTAMP**: `84`
+
+Defined in: [protos/generated/misc/common.ts:144](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L144)
+
+***
+
+### TIMESTAMP\_IN\_COMMENTS
+
+> **TIMESTAMP\_IN\_COMMENTS**: `1`
+
+Defined in: [protos/generated/misc/common.ts:62](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L62)
+
+***
+
+### TIMESTAMP\_IN\_DESCRIPTION
+
+> **TIMESTAMP\_IN\_DESCRIPTION**: `2`
+
+Defined in: [protos/generated/misc/common.ts:63](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L63)
+
+***
+
+### TVHTML5\_INPUT\_SOURCE\_CONTROLS
+
+> **TVHTML5\_INPUT\_SOURCE\_CONTROLS**: `99`
+
+Defined in: [protos/generated/misc/common.ts:157](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L157)
+
+***
+
+### TVHTML5\_INPUT\_SOURCE\_KEY\_EVENT
+
+> **TVHTML5\_INPUT\_SOURCE\_KEY\_EVENT**: `98`
+
+Defined in: [protos/generated/misc/common.ts:156](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L156)
+
+***
+
+### TVHTML5\_INPUT\_SOURCE\_TOUCH
+
+> **TVHTML5\_INPUT\_SOURCE\_TOUCH**: `100`
+
+Defined in: [protos/generated/misc/common.ts:158](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L158)
+
+***
+
+### TVHTML5\_INPUT\_SOURCE\_TOUCHPAD
+
+> **TVHTML5\_INPUT\_SOURCE\_TOUCHPAD**: `101`
+
+Defined in: [protos/generated/misc/common.ts:159](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L159)
+
+***
+
+### UNKNOWN
+
+> **UNKNOWN**: `0`
+
+Defined in: [protos/generated/misc/common.ts:61](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L61)
+
+***
+
+### UNPLUGGED\_LENS\_START\_CLIP
+
+> **UNPLUGGED\_LENS\_START\_CLIP**: `95`
+
+Defined in: [protos/generated/misc/common.ts:153](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L153)
+
+***
+
+### UNRECOGNIZED
+
+> **UNRECOGNIZED**: `-1`
+
+Defined in: [protos/generated/misc/common.ts:169](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L169)
diff --git a/docs/api/exports/protos/enumerations/UMPPartId.md b/docs/api/exports/protos/enumerations/UMPPartId.md
new file mode 100644
index 0000000..6a2f780
--- /dev/null
+++ b/docs/api/exports/protos/enumerations/UMPPartId.md
@@ -0,0 +1,373 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / UMPPartId
+
+# Enumeration: UMPPartId
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:11](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L11)
+
+## Enumeration Members
+
+### ALLOWED\_CACHED\_FORMATS
+
+> **ALLOWED\_CACHED\_FORMATS**: `48`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:42](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L42)
+
+***
+
+### CACHE\_LOAD\_POLICY
+
+> **CACHE\_LOAD\_POLICY**: `63`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:60](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L60)
+
+***
+
+### CONFIG
+
+> **CONFIG**: `30`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:22](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L22)
+
+***
+
+### END\_OF\_TRACK
+
+> **END\_OF\_TRACK**: `62`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:59](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L59)
+
+***
+
+### FORMAT\_INITIALIZATION\_METADATA
+
+> **FORMAT\_INITIALIZATION\_METADATA**: `42`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:33](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L33)
+
+FORMAT_INITIALIZATION_METADATA - Metadata for format initialization; contains total number of segments, duration, etc.
+
+***
+
+### FORMAT\_SELECTION\_CONFIG
+
+> **FORMAT\_SELECTION\_CONFIG**: `37`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:30](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L30)
+
+***
+
+### HOSTNAME\_CHANGE\_HINT\_DEPRECATED
+
+> **HOSTNAME\_CHANGE\_HINT\_DEPRECATED**: `32`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:24](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L24)
+
+***
+
+### LAWNMOWER\_MESSAGING\_POLICY
+
+> **LAWNMOWER\_MESSAGING\_POLICY**: `64`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:61](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L61)
+
+***
+
+### LAWNMOWER\_POLICY
+
+> **LAWNMOWER\_POLICY**: `60`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:57](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L57)
+
+***
+
+### LIVE\_METADATA
+
+> **LIVE\_METADATA**: `31`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:23](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L23)
+
+***
+
+### LIVE\_METADATA\_PROMISE
+
+> **LIVE\_METADATA\_PROMISE**: `33`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:25](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L25)
+
+***
+
+### LIVE\_METADATA\_PROMISE\_CANCELLATION
+
+> **LIVE\_METADATA\_PROMISE\_CANCELLATION**: `34`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:26](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L26)
+
+***
+
+### MEDIA
+
+> **MEDIA**: `21`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:19](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L19)
+
+MEDIA - Chunk of media segment data.
+
+***
+
+### MEDIA\_END
+
+> **MEDIA\_END**: `22`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:21](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L21)
+
+MEDIA_END - Indicates end of media segment; finalizes segment processing.
+
+***
+
+### MEDIA\_HEADER
+
+> **MEDIA\_HEADER**: `20`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:17](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L17)
+
+MEDIA_HEADER - Header for a media segment; includes sequence and timing information.
+
+***
+
+### NEXT\_REQUEST\_POLICY
+
+> **NEXT\_REQUEST\_POLICY**: `35`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:28](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L28)
+
+NEXT_REQUEST_POLICY - Server's policy for the next request; includes backoff time and playback cookie.
+
+***
+
+### ONESIE\_DATA
+
+> **ONESIE\_DATA**: `11`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:14](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L14)
+
+***
+
+### ONESIE\_ENCRYPTED\_MEDIA
+
+> **ONESIE\_ENCRYPTED\_MEDIA**: `12`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:15](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L15)
+
+***
+
+### ONESIE\_HEADER
+
+> **ONESIE\_HEADER**: `10`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:13](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L13)
+
+***
+
+### ONESIE\_PREFETCH\_REJECTION
+
+> **ONESIE\_PREFETCH\_REJECTION**: `54`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:48](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L48)
+
+***
+
+### PAUSE\_BW\_SAMPLING\_HINT
+
+> **PAUSE\_BW\_SAMPLING\_HINT**: `50`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:44](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L44)
+
+***
+
+### PLAYBACK\_DEBUG\_INFO
+
+> **PLAYBACK\_DEBUG\_INFO**: `66`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:63](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L63)
+
+***
+
+### PLAYBACK\_START\_POLICY
+
+> **PLAYBACK\_START\_POLICY**: `47`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:41](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L41)
+
+***
+
+### PREWARM\_CONNECTION
+
+> **PREWARM\_CONNECTION**: `65`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:62](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L62)
+
+***
+
+### RELOAD\_PLAYER\_RESPONSE
+
+> **RELOAD\_PLAYER\_RESPONSE**: `46`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:40](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L40)
+
+RELOAD_PLAYER_RESPONSE - Directive to reload the player with new parameters.
+
+***
+
+### REQUEST\_CANCELLATION\_POLICY
+
+> **REQUEST\_CANCELLATION\_POLICY**: `53`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:47](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L47)
+
+***
+
+### REQUEST\_IDENTIFIER
+
+> **REQUEST\_IDENTIFIER**: `52`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:46](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L46)
+
+***
+
+### REQUEST\_PIPELINING
+
+> **REQUEST\_PIPELINING**: `56`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:50](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L50)
+
+***
+
+### SABR\_ACK
+
+> **SABR\_ACK**: `61`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:58](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L58)
+
+***
+
+### SABR\_CONTEXT\_SENDING\_POLICY
+
+> **SABR\_CONTEXT\_SENDING\_POLICY**: `59`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:56](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L56)
+
+SABR_CONTEXT_SENDING_POLICY - Policy indicating which SABR contexts to send or discard in future requests.
+
+***
+
+### SABR\_CONTEXT\_UPDATE
+
+> **SABR\_CONTEXT\_UPDATE**: `57`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:52](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L52)
+
+SABR_CONTEXT_UPDATE - Updates SABR context data; usually used for ads.
+
+***
+
+### SABR\_ERROR
+
+> **SABR\_ERROR**: `44`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:37](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L37)
+
+SABR_ERROR - Indicates a SABR error; happens when the payload is invalid or the server cannot process the request.
+
+***
+
+### SABR\_REDIRECT
+
+> **SABR\_REDIRECT**: `43`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:35](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L35)
+
+SABR_REDIRECT - Indicates a redirect to a different streaming URL.
+
+***
+
+### SABR\_SEEK
+
+> **SABR\_SEEK**: `45`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:38](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L38)
+
+***
+
+### SELECTABLE\_FORMATS
+
+> **SELECTABLE\_FORMATS**: `51`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:45](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L45)
+
+***
+
+### SNACKBAR\_MESSAGE
+
+> **SNACKBAR\_MESSAGE**: `67`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:65](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L65)
+
+SNACKBAR_MESSAGE - Directive to show the user a notification message.
+
+***
+
+### START\_BW\_SAMPLING\_HINT
+
+> **START\_BW\_SAMPLING\_HINT**: `49`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:43](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L43)
+
+***
+
+### STREAM\_PROTECTION\_STATUS
+
+> **STREAM\_PROTECTION\_STATUS**: `58`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:54](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L54)
+
+STREAM_PROTECTION_STATUS - Status of stream protection; indicates whether attestation is required.
+
+***
+
+### TIMELINE\_CONTEXT
+
+> **TIMELINE\_CONTEXT**: `55`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:49](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L49)
+
+***
+
+### UNKNOWN
+
+> **UNKNOWN**: `0`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L12)
+
+***
+
+### UNRECOGNIZED
+
+> **UNRECOGNIZED**: `-1`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:66](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L66)
+
+***
+
+### USTREAMER\_SELECTED\_MEDIA\_STREAM
+
+> **USTREAMER\_SELECTED\_MEDIA\_STREAM**: `38`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:31](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L31)
+
+***
+
+### USTREAMER\_VIDEO\_AND\_FORMAT\_METADATA
+
+> **USTREAMER\_VIDEO\_AND\_FORMAT\_METADATA**: `36`
+
+Defined in: [protos/generated/video\_streaming/ump\_part\_id.ts:29](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/ump_part_id.ts#L29)
diff --git a/docs/api/exports/protos/enumerations/VideoQualitySetting.md b/docs/api/exports/protos/enumerations/VideoQualitySetting.md
new file mode 100644
index 0000000..4e6162f
--- /dev/null
+++ b/docs/api/exports/protos/enumerations/VideoQualitySetting.md
@@ -0,0 +1,45 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / VideoQualitySetting
+
+# Enumeration: VideoQualitySetting
+
+Defined in: [protos/generated/misc/common.ts:28](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L28)
+
+## Enumeration Members
+
+### ADVANCED\_MENU
+
+> **ADVANCED\_MENU**: `3`
+
+Defined in: [protos/generated/misc/common.ts:32](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L32)
+
+***
+
+### DATA\_SAVER
+
+> **DATA\_SAVER**: `2`
+
+Defined in: [protos/generated/misc/common.ts:31](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L31)
+
+***
+
+### HIGHER\_QUALITY
+
+> **HIGHER\_QUALITY**: `1`
+
+Defined in: [protos/generated/misc/common.ts:30](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L30)
+
+***
+
+### UNKNOWN
+
+> **UNKNOWN**: `0`
+
+Defined in: [protos/generated/misc/common.ts:29](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L29)
+
+***
+
+### UNRECOGNIZED
+
+> **UNRECOGNIZED**: `-1`
+
+Defined in: [protos/generated/misc/common.ts:33](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L33)
diff --git a/docs/api/exports/protos/interfaces/AuthorizedFormat.md b/docs/api/exports/protos/interfaces/AuthorizedFormat.md
new file mode 100644
index 0000000..71bc5c3
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/AuthorizedFormat.md
@@ -0,0 +1,21 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / AuthorizedFormat
+
+# Interface: AuthorizedFormat
+
+Defined in: [protos/generated/misc/common.ts:209](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L209)
+
+## Properties
+
+### isHdr?
+
+> `optional` **isHdr**: `boolean`
+
+Defined in: [protos/generated/misc/common.ts:211](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L211)
+
+***
+
+### trackType?
+
+> `optional` **trackType**: `number`
+
+Defined in: [protos/generated/misc/common.ts:210](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L210)
diff --git a/docs/api/exports/protos/interfaces/BufferedRange.md b/docs/api/exports/protos/interfaces/BufferedRange.md
new file mode 100644
index 0000000..734b309
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/BufferedRange.md
@@ -0,0 +1,77 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / BufferedRange
+
+# Interface: BufferedRange
+
+Defined in: [protos/generated/video\_streaming/buffered\_range.ts:14](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/buffered_range.ts#L14)
+
+## Properties
+
+### durationMs
+
+> **durationMs**: `number`
+
+Defined in: [protos/generated/video\_streaming/buffered\_range.ts:17](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/buffered_range.ts#L17)
+
+***
+
+### endSegmentIndex
+
+> **endSegmentIndex**: `number`
+
+Defined in: [protos/generated/video\_streaming/buffered\_range.ts:19](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/buffered_range.ts#L19)
+
+***
+
+### field11?
+
+> `optional` **field11**: `BufferedRange_UnknownMessage2`
+
+Defined in: [protos/generated/video\_streaming/buffered\_range.ts:22](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/buffered_range.ts#L22)
+
+***
+
+### field12?
+
+> `optional` **field12**: `BufferedRange_UnknownMessage2`
+
+Defined in: [protos/generated/video\_streaming/buffered\_range.ts:23](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/buffered_range.ts#L23)
+
+***
+
+### field9?
+
+> `optional` **field9**: `BufferedRange_UnknownMessage1`
+
+Defined in: [protos/generated/video\_streaming/buffered\_range.ts:21](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/buffered_range.ts#L21)
+
+***
+
+### formatId
+
+> **formatId**: `undefined` \| [`FormatId`](FormatId.md)
+
+Defined in: [protos/generated/video\_streaming/buffered\_range.ts:15](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/buffered_range.ts#L15)
+
+***
+
+### startSegmentIndex
+
+> **startSegmentIndex**: `number`
+
+Defined in: [protos/generated/video\_streaming/buffered\_range.ts:18](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/buffered_range.ts#L18)
+
+***
+
+### startTimeMs
+
+> **startTimeMs**: `number`
+
+Defined in: [protos/generated/video\_streaming/buffered\_range.ts:16](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/buffered_range.ts#L16)
+
+***
+
+### timeRange?
+
+> `optional` **timeRange**: `TimeRange`
+
+Defined in: [protos/generated/video\_streaming/buffered\_range.ts:20](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/buffered_range.ts#L20)
diff --git a/docs/api/exports/protos/interfaces/ClientAbrState.md b/docs/api/exports/protos/interfaces/ClientAbrState.md
new file mode 100644
index 0000000..1e3b601
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/ClientAbrState.md
@@ -0,0 +1,375 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / ClientAbrState
+
+# Interface: ClientAbrState
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:20](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L20)
+
+## Properties
+
+### allowProximaLiveLatency?
+
+> `optional` **allowProximaLiveLatency**: `number`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:63](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L63)
+
+***
+
+### audioRoute?
+
+> `optional` **audioRoute**: [`PlaybackAudioRouteOutputType`](../enumerations/PlaybackAudioRouteOutputType.md)
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:34](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L34)
+
+***
+
+### audioTrackId?
+
+> `optional` **audioTrackId**: `string`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:67](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L67)
+
+***
+
+### av1QualityThreshold?
+
+> `optional` **av1QualityThreshold**: `number`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:58](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L58)
+
+2160
+
+***
+
+### bandwidthEstimate?
+
+> `optional` **bandwidthEstimate**: `number`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:30](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L30)
+
+***
+
+### clientBitrateCapBytesPerSec?
+
+> `optional` **clientBitrateCapBytesPerSec**: `number`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:27](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L27)
+
+***
+
+### clientViewportHeight?
+
+> `optional` **clientViewportHeight**: `number`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:26](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L26)
+
+***
+
+### clientViewportIsFlexible?
+
+> `optional` **clientViewportIsFlexible**: `boolean`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:29](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L29)
+
+***
+
+### clientViewportWidth?
+
+> `optional` **clientViewportWidth**: `number`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:25](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L25)
+
+***
+
+### dataSaverMode?
+
+> `optional` **dataSaverMode**: `boolean`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:37](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L37)
+
+***
+
+### detailedNetworkType?
+
+> `optional` **detailedNetworkType**: `number`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:24](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L24)
+
+***
+
+### disableStreamingXhr?
+
+> `optional` **disableStreamingXhr**: `boolean`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:52](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L52)
+
+***
+
+### drcEnabled?
+
+> `optional` **drcEnabled**: `boolean`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:47](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L47)
+
+***
+
+### elapsedWallTimeMs?
+
+> `optional` **elapsedWallTimeMs**: `number`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:41](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L41)
+
+***
+
+### enabledTrackTypesBitfield?
+
+> `optional` **enabledTrackTypesBitfield**: `number`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:44](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L44)
+
+***
+
+### enableVoiceBoost?
+
+> `optional` **enableVoiceBoost**: `boolean`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:68](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L68)
+
+***
+
+### field48?
+
+> `optional` **field48**: `number`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:48](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L48)
+
+***
+
+### field50?
+
+> `optional` **field50**: `number`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:49](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L49)
+
+***
+
+### field51?
+
+> `optional` **field51**: `number`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:50](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L50)
+
+***
+
+### field57?
+
+> `optional` **field57**: `number`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:53](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L53)
+
+***
+
+### field60?
+
+> `optional` **field60**: `number`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:59](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L59)
+
+***
+
+### field67?
+
+> `optional` **field67**: `number`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:65](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L65)
+
+***
+
+### isPrefetch?
+
+> `optional` **isPrefetch**: `boolean`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:60](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L60)
+
+***
+
+### lastManualDirection?
+
+> `optional` **lastManualDirection**: `number`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:22](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L22)
+
+***
+
+### lastManualSelectedResolution?
+
+> `optional` **lastManualSelectedResolution**: `number`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:23](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L23)
+
+***
+
+### maxAudioQuality?
+
+> `optional` **maxAudioQuality**: [`AudioQuality`](../enumerations/AudioQuality.md)
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:32](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L32)
+
+***
+
+### maxPacingRate?
+
+> `optional` **maxPacingRate**: `number`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:45](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L45)
+
+***
+
+### mediaCapabilities?
+
+> `optional` **mediaCapabilities**: [`MediaCapabilities`](MediaCapabilities.md)
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:42](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L42)
+
+***
+
+### minAudioQuality?
+
+> `optional` **minAudioQuality**: [`AudioQuality`](../enumerations/AudioQuality.md)
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:31](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L31)
+
+***
+
+### networkMeteredState?
+
+> `optional` **networkMeteredState**: [`NetworkMeteredState`](../enumerations/NetworkMeteredState.md)
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:38](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L38)
+
+***
+
+### playbackAuthorization?
+
+> `optional` **playbackAuthorization**: [`PlaybackAuthorization`](PlaybackAuthorization.md)
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:69](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L69)
+
+***
+
+### playbackRate?
+
+> `optional` **playbackRate**: `number`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:40](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L40)
+
+***
+
+### playerState?
+
+> `optional` **playerState**: `number`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:46](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L46)
+
+***
+
+### playerTimeMs?
+
+> `optional` **playerTimeMs**: `number`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:35](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L35)
+
+***
+
+### preferVp9?
+
+> `optional` **preferVp9**: `boolean`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:54](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L54)
+
+***
+
+### sabrForceMaxNetworkInterruptionDurationMs?
+
+> `optional` **sabrForceMaxNetworkInterruptionDurationMs**: `number`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:66](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L66)
+
+***
+
+### sabrForceProxima?
+
+> `optional` **sabrForceProxima**: `number`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:64](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L64)
+
+***
+
+### sabrLicenseConstraint?
+
+> `optional` **sabrLicenseConstraint**: `Uint8Array`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:62](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L62)
+
+***
+
+### sabrReportRequestCancellationInfo?
+
+> `optional` **sabrReportRequestCancellationInfo**: `number`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:51](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L51)
+
+***
+
+### sabrSupportQualityConstraints?
+
+> `optional` **sabrSupportQualityConstraints**: `boolean`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:61](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L61)
+
+***
+
+### stickyResolution?
+
+> `optional` **stickyResolution**: `number`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:28](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L28)
+
+***
+
+### timeSinceLastActionMs?
+
+> `optional` **timeSinceLastActionMs**: `number`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:43](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L43)
+
+***
+
+### timeSinceLastManualFormatSelectionMs?
+
+> `optional` **timeSinceLastManualFormatSelectionMs**: `number`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:21](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L21)
+
+***
+
+### timeSinceLastSeek?
+
+> `optional` **timeSinceLastSeek**: `number`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:36](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L36)
+
+***
+
+### videoQualitySetting?
+
+> `optional` **videoQualitySetting**: [`VideoQualitySetting`](../enumerations/VideoQualitySetting.md)
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:33](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L33)
+
+***
+
+### visibility?
+
+> `optional` **visibility**: `number`
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:39](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L39)
diff --git a/docs/api/exports/protos/interfaces/ClientInfo.md b/docs/api/exports/protos/interfaces/ClientInfo.md
new file mode 100644
index 0000000..77c5100
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/ClientInfo.md
@@ -0,0 +1,193 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / ClientInfo
+
+# Interface: ClientInfo
+
+Defined in: [protos/generated/video\_streaming/streamer\_context.ts:30](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/streamer_context.ts#L30)
+
+## Properties
+
+### acceptLanguage?
+
+> `optional` **acceptLanguage**: `string`
+
+Defined in: [protos/generated/video\_streaming/streamer\_context.ts:37](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/streamer_context.ts#L37)
+
+***
+
+### acceptRegion?
+
+> `optional` **acceptRegion**: `string`
+
+Defined in: [protos/generated/video\_streaming/streamer\_context.ts:38](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/streamer_context.ts#L38)
+
+***
+
+### androidSdkVersion?
+
+> `optional` **androidSdkVersion**: `number`
+
+Defined in: [protos/generated/video\_streaming/streamer\_context.ts:51](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/streamer_context.ts#L51)
+
+***
+
+### chipset?
+
+> `optional` **chipset**: `string`
+
+Defined in: [protos/generated/video\_streaming/streamer\_context.ts:58](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/streamer_context.ts#L58)
+
+e.g. "qcom;taro"
+
+***
+
+### clientFormFactor?
+
+> `optional` **clientFormFactor**: `StreamerContext_ClientFormFactor`
+
+Defined in: [protos/generated/video\_streaming/streamer\_context.ts:44](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/streamer_context.ts#L44)
+
+***
+
+### clientName?
+
+> `optional` **clientName**: `number`
+
+Defined in: [protos/generated/video\_streaming/streamer\_context.ts:33](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/streamer_context.ts#L33)
+
+***
+
+### clientVersion?
+
+> `optional` **clientVersion**: `string`
+
+Defined in: [protos/generated/video\_streaming/streamer\_context.ts:34](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/streamer_context.ts#L34)
+
+***
+
+### deviceMake?
+
+> `optional` **deviceMake**: `string`
+
+Defined in: [protos/generated/video\_streaming/streamer\_context.ts:31](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/streamer_context.ts#L31)
+
+***
+
+### deviceModel?
+
+> `optional` **deviceModel**: `string`
+
+Defined in: [protos/generated/video\_streaming/streamer\_context.ts:32](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/streamer_context.ts#L32)
+
+***
+
+### glDeviceInfo?
+
+> `optional` **glDeviceInfo**: `StreamerContext_GLDeviceInfo`
+
+Defined in: [protos/generated/video\_streaming/streamer\_context.ts:59](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/streamer_context.ts#L59)
+
+***
+
+### gmscoreVersionCode?
+
+> `optional` **gmscoreVersionCode**: `number`
+
+Defined in: [protos/generated/video\_streaming/streamer\_context.ts:48](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/streamer_context.ts#L48)
+
+e.g. 243731017
+
+***
+
+### osName?
+
+> `optional` **osName**: `string`
+
+Defined in: [protos/generated/video\_streaming/streamer\_context.ts:35](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/streamer_context.ts#L35)
+
+***
+
+### osVersion?
+
+> `optional` **osVersion**: `string`
+
+Defined in: [protos/generated/video\_streaming/streamer\_context.ts:36](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/streamer_context.ts#L36)
+
+***
+
+### screenDensityFloat?
+
+> `optional` **screenDensityFloat**: `number`
+
+Defined in: [protos/generated/video\_streaming/streamer\_context.ts:52](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/streamer_context.ts#L52)
+
+***
+
+### screenHeightInches?
+
+> `optional` **screenHeightInches**: `number`
+
+Defined in: [protos/generated/video\_streaming/streamer\_context.ts:42](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/streamer_context.ts#L42)
+
+***
+
+### screenHeightPoints?
+
+> `optional` **screenHeightPoints**: `number`
+
+Defined in: [protos/generated/video\_streaming/streamer\_context.ts:40](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/streamer_context.ts#L40)
+
+***
+
+### screenPixelDensity?
+
+> `optional` **screenPixelDensity**: `number`
+
+Defined in: [protos/generated/video\_streaming/streamer\_context.ts:43](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/streamer_context.ts#L43)
+
+***
+
+### screenWidthInches?
+
+> `optional` **screenWidthInches**: `number`
+
+Defined in: [protos/generated/video\_streaming/streamer\_context.ts:41](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/streamer_context.ts#L41)
+
+***
+
+### screenWidthPoints?
+
+> `optional` **screenWidthPoints**: `number`
+
+Defined in: [protos/generated/video\_streaming/streamer\_context.ts:39](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/streamer_context.ts#L39)
+
+***
+
+### timeZone?
+
+> `optional` **timeZone**: `string`
+
+Defined in: [protos/generated/video\_streaming/streamer\_context.ts:54](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/streamer_context.ts#L54)
+
+***
+
+### utcOffsetMinutes?
+
+> `optional` **utcOffsetMinutes**: `number`
+
+Defined in: [protos/generated/video\_streaming/streamer\_context.ts:53](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/streamer_context.ts#L53)
+
+***
+
+### windowHeightPoints?
+
+> `optional` **windowHeightPoints**: `number`
+
+Defined in: [protos/generated/video\_streaming/streamer\_context.ts:50](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/streamer_context.ts#L50)
+
+***
+
+### windowWidthPoints?
+
+> `optional` **windowWidthPoints**: `number`
+
+Defined in: [protos/generated/video\_streaming/streamer\_context.ts:49](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/streamer_context.ts#L49)
diff --git a/docs/api/exports/protos/interfaces/CryptoParams.md b/docs/api/exports/protos/interfaces/CryptoParams.md
new file mode 100644
index 0000000..440fe64
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/CryptoParams.md
@@ -0,0 +1,29 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / CryptoParams
+
+# Interface: CryptoParams
+
+Defined in: [protos/generated/video\_streaming/crypto\_params.ts:13](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/crypto_params.ts#L13)
+
+## Properties
+
+### compressionType?
+
+> `optional` **compressionType**: [`CompressionType`](../enumerations/CompressionType.md)
+
+Defined in: [protos/generated/video\_streaming/crypto\_params.ts:16](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/crypto_params.ts#L16)
+
+***
+
+### hmac?
+
+> `optional` **hmac**: `Uint8Array`
+
+Defined in: [protos/generated/video\_streaming/crypto\_params.ts:14](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/crypto_params.ts#L14)
+
+***
+
+### iv?
+
+> `optional` **iv**: `Uint8Array`
+
+Defined in: [protos/generated/video\_streaming/crypto\_params.ts:15](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/crypto_params.ts#L15)
diff --git a/docs/api/exports/protos/interfaces/FormatId.md b/docs/api/exports/protos/interfaces/FormatId.md
new file mode 100644
index 0000000..e3de842
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/FormatId.md
@@ -0,0 +1,29 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / FormatId
+
+# Interface: FormatId
+
+Defined in: [protos/generated/misc/common.ts:186](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L186)
+
+## Properties
+
+### itag?
+
+> `optional` **itag**: `number`
+
+Defined in: [protos/generated/misc/common.ts:187](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L187)
+
+***
+
+### lastModified?
+
+> `optional` **lastModified**: `number`
+
+Defined in: [protos/generated/misc/common.ts:188](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L188)
+
+***
+
+### xtags?
+
+> `optional` **xtags**: `string`
+
+Defined in: [protos/generated/misc/common.ts:189](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L189)
diff --git a/docs/api/exports/protos/interfaces/FormatInitializationMetadata.md b/docs/api/exports/protos/interfaces/FormatInitializationMetadata.md
new file mode 100644
index 0000000..edfba3f
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/FormatInitializationMetadata.md
@@ -0,0 +1,85 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / FormatInitializationMetadata
+
+# Interface: FormatInitializationMetadata
+
+Defined in: [protos/generated/video\_streaming/format\_initialization\_metadata.ts:13](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/format_initialization_metadata.ts#L13)
+
+## Properties
+
+### durationTimescale?
+
+> `optional` **durationTimescale**: `number`
+
+Defined in: [protos/generated/video\_streaming/format\_initialization\_metadata.ts:23](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/format_initialization_metadata.ts#L23)
+
+***
+
+### durationUnits?
+
+> `optional` **durationUnits**: `number`
+
+Defined in: [protos/generated/video\_streaming/format\_initialization\_metadata.ts:22](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/format_initialization_metadata.ts#L22)
+
+***
+
+### endSegmentNumber?
+
+> `optional` **endSegmentNumber**: `number`
+
+Defined in: [protos/generated/video\_streaming/format\_initialization\_metadata.ts:17](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/format_initialization_metadata.ts#L17)
+
+***
+
+### endTimeMs?
+
+> `optional` **endTimeMs**: `number`
+
+Defined in: [protos/generated/video\_streaming/format\_initialization\_metadata.ts:16](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/format_initialization_metadata.ts#L16)
+
+***
+
+### field8?
+
+> `optional` **field8**: `number`
+
+Defined in: [protos/generated/video\_streaming/format\_initialization\_metadata.ts:21](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/format_initialization_metadata.ts#L21)
+
+***
+
+### formatId?
+
+> `optional` **formatId**: [`FormatId`](FormatId.md)
+
+Defined in: [protos/generated/video\_streaming/format\_initialization\_metadata.ts:15](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/format_initialization_metadata.ts#L15)
+
+***
+
+### indexRange?
+
+> `optional` **indexRange**: [`Range`](Range.md)
+
+Defined in: [protos/generated/video\_streaming/format\_initialization\_metadata.ts:20](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/format_initialization_metadata.ts#L20)
+
+***
+
+### initRange?
+
+> `optional` **initRange**: [`Range`](Range.md)
+
+Defined in: [protos/generated/video\_streaming/format\_initialization\_metadata.ts:19](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/format_initialization_metadata.ts#L19)
+
+***
+
+### mimeType?
+
+> `optional` **mimeType**: `string`
+
+Defined in: [protos/generated/video\_streaming/format\_initialization\_metadata.ts:18](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/format_initialization_metadata.ts#L18)
+
+***
+
+### videoId?
+
+> `optional` **videoId**: `string`
+
+Defined in: [protos/generated/video\_streaming/format\_initialization\_metadata.ts:14](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/format_initialization_metadata.ts#L14)
diff --git a/docs/api/exports/protos/interfaces/FormatSelectionConfig.md b/docs/api/exports/protos/interfaces/FormatSelectionConfig.md
new file mode 100644
index 0000000..46b5764
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/FormatSelectionConfig.md
@@ -0,0 +1,29 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / FormatSelectionConfig
+
+# Interface: FormatSelectionConfig
+
+Defined in: [protos/generated/video\_streaming/format\_selection\_config.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/format_selection_config.ts#L12)
+
+## Properties
+
+### itags
+
+> **itags**: `number`[]
+
+Defined in: [protos/generated/video\_streaming/format\_selection\_config.ts:13](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/format_selection_config.ts#L13)
+
+***
+
+### resolution?
+
+> `optional` **resolution**: `number`
+
+Defined in: [protos/generated/video\_streaming/format\_selection\_config.ts:15](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/format_selection_config.ts#L15)
+
+***
+
+### videoId?
+
+> `optional` **videoId**: `string`
+
+Defined in: [protos/generated/video\_streaming/format\_selection\_config.ts:14](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/format_selection_config.ts#L14)
diff --git a/docs/api/exports/protos/interfaces/HttpHeader.md b/docs/api/exports/protos/interfaces/HttpHeader.md
new file mode 100644
index 0000000..651c2d2
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/HttpHeader.md
@@ -0,0 +1,21 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / HttpHeader
+
+# Interface: HttpHeader
+
+Defined in: [protos/generated/misc/common.ts:181](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L181)
+
+## Properties
+
+### name?
+
+> `optional` **name**: `string`
+
+Defined in: [protos/generated/misc/common.ts:182](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L182)
+
+***
+
+### value?
+
+> `optional` **value**: `string`
+
+Defined in: [protos/generated/misc/common.ts:183](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L183)
diff --git a/docs/api/exports/protos/interfaces/IdentifierToken.md b/docs/api/exports/protos/interfaces/IdentifierToken.md
new file mode 100644
index 0000000..4fde147
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/IdentifierToken.md
@@ -0,0 +1,21 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / IdentifierToken
+
+# Interface: IdentifierToken
+
+Defined in: [protos/generated/misc/common.ts:199](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L199)
+
+## Properties
+
+### field5?
+
+> `optional` **field5**: `number`
+
+Defined in: [protos/generated/misc/common.ts:201](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L201)
+
+***
+
+### requestNumber?
+
+> `optional` **requestNumber**: `number`
+
+Defined in: [protos/generated/misc/common.ts:200](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L200)
diff --git a/docs/api/exports/protos/interfaces/InnertubeRequest.md b/docs/api/exports/protos/interfaces/InnertubeRequest.md
new file mode 100644
index 0000000..228c0a9
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/InnertubeRequest.md
@@ -0,0 +1,101 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / InnertubeRequest
+
+# Interface: InnertubeRequest
+
+Defined in: [protos/generated/video\_streaming/innertube\_request.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/innertube_request.ts#L12)
+
+## Properties
+
+### context?
+
+> `optional` **context**: `Uint8Array`
+
+Defined in: [protos/generated/video\_streaming/innertube\_request.ts:13](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/innertube_request.ts#L13)
+
+***
+
+### enableAdPlacementsPreroll?
+
+> `optional` **enableAdPlacementsPreroll**: `boolean`
+
+Defined in: [protos/generated/video\_streaming/innertube\_request.ts:20](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/innertube_request.ts#L20)
+
+***
+
+### enableCompression?
+
+> `optional` **enableCompression**: `boolean`
+
+Defined in: [protos/generated/video\_streaming/innertube\_request.ts:21](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/innertube_request.ts#L21)
+
+***
+
+### encryptedClientKey?
+
+> `optional` **encryptedClientKey**: `Uint8Array`
+
+Defined in: [protos/generated/video\_streaming/innertube\_request.ts:15](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/innertube_request.ts#L15)
+
+***
+
+### encryptedOnesieInnertubeRequest?
+
+> `optional` **encryptedOnesieInnertubeRequest**: `Uint8Array`
+
+Defined in: [protos/generated/video\_streaming/innertube\_request.ts:14](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/innertube_request.ts#L14)
+
+***
+
+### hmac?
+
+> `optional` **hmac**: `Uint8Array`
+
+Defined in: [protos/generated/video\_streaming/innertube\_request.ts:17](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/innertube_request.ts#L17)
+
+***
+
+### iv?
+
+> `optional` **iv**: `Uint8Array`
+
+Defined in: [protos/generated/video\_streaming/innertube\_request.ts:16](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/innertube_request.ts#L16)
+
+***
+
+### reverseProxyConfig?
+
+> `optional` **reverseProxyConfig**: `string`
+
+Defined in: [protos/generated/video\_streaming/innertube\_request.ts:18](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/innertube_request.ts#L18)
+
+***
+
+### serializeResponseAsJson?
+
+> `optional` **serializeResponseAsJson**: `boolean`
+
+Defined in: [protos/generated/video\_streaming/innertube\_request.ts:19](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/innertube_request.ts#L19)
+
+***
+
+### unencryptedOnesieInnertubeRequest?
+
+> `optional` **unencryptedOnesieInnertubeRequest**: `Uint8Array`
+
+Defined in: [protos/generated/video\_streaming/innertube\_request.ts:23](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/innertube_request.ts#L23)
+
+***
+
+### useJsonformatterToParsePlayerResponse?
+
+> `optional` **useJsonformatterToParsePlayerResponse**: `boolean`
+
+Defined in: [protos/generated/video\_streaming/innertube\_request.ts:24](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/innertube_request.ts#L24)
+
+***
+
+### ustreamerFlags?
+
+> `optional` **ustreamerFlags**: [`UstreamerFlags`](UstreamerFlags.md)
+
+Defined in: [protos/generated/video\_streaming/innertube\_request.ts:22](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/innertube_request.ts#L22)
diff --git a/docs/api/exports/protos/interfaces/KeyValuePair.md b/docs/api/exports/protos/interfaces/KeyValuePair.md
new file mode 100644
index 0000000..79ae7bf
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/KeyValuePair.md
@@ -0,0 +1,21 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / KeyValuePair
+
+# Interface: KeyValuePair
+
+Defined in: [protos/generated/misc/common.ts:204](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L204)
+
+## Properties
+
+### key?
+
+> `optional` **key**: `string`
+
+Defined in: [protos/generated/misc/common.ts:205](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L205)
+
+***
+
+### value?
+
+> `optional` **value**: `string`
+
+Defined in: [protos/generated/misc/common.ts:206](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L206)
diff --git a/docs/api/exports/protos/interfaces/LiveMetadata.md b/docs/api/exports/protos/interfaces/LiveMetadata.md
new file mode 100644
index 0000000..939278c
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/LiveMetadata.md
@@ -0,0 +1,93 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / LiveMetadata
+
+# Interface: LiveMetadata
+
+Defined in: [protos/generated/video\_streaming/live\_metadata.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/live_metadata.ts#L12)
+
+## Properties
+
+### broadcastId?
+
+> `optional` **broadcastId**: `string`
+
+Defined in: [protos/generated/video\_streaming/live\_metadata.ts:13](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/live_metadata.ts#L13)
+
+***
+
+### headm?
+
+> `optional` **headm**: `number`
+
+Defined in: [protos/generated/video\_streaming/live\_metadata.ts:19](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/live_metadata.ts#L19)
+
+***
+
+### headSequenceNumber?
+
+> `optional` **headSequenceNumber**: `number`
+
+Defined in: [protos/generated/video\_streaming/live\_metadata.ts:14](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/live_metadata.ts#L14)
+
+***
+
+### headTimeMs?
+
+> `optional` **headTimeMs**: `number`
+
+Defined in: [protos/generated/video\_streaming/live\_metadata.ts:15](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/live_metadata.ts#L15)
+
+***
+
+### maxSeekableTimescale?
+
+> `optional` **maxSeekableTimescale**: `number`
+
+Defined in: [protos/generated/video\_streaming/live\_metadata.ts:23](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/live_metadata.ts#L23)
+
+***
+
+### maxSeekableTimeTicks?
+
+> `optional` **maxSeekableTimeTicks**: `number`
+
+Defined in: [protos/generated/video\_streaming/live\_metadata.ts:22](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/live_metadata.ts#L22)
+
+***
+
+### minSeekableTimescale?
+
+> `optional` **minSeekableTimescale**: `number`
+
+Defined in: [protos/generated/video\_streaming/live\_metadata.ts:21](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/live_metadata.ts#L21)
+
+***
+
+### minSeekableTimeTicks?
+
+> `optional` **minSeekableTimeTicks**: `number`
+
+Defined in: [protos/generated/video\_streaming/live\_metadata.ts:20](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/live_metadata.ts#L20)
+
+***
+
+### postLiveDvr?
+
+> `optional` **postLiveDvr**: `boolean`
+
+Defined in: [protos/generated/video\_streaming/live\_metadata.ts:18](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/live_metadata.ts#L18)
+
+***
+
+### videoId?
+
+> `optional` **videoId**: `string`
+
+Defined in: [protos/generated/video\_streaming/live\_metadata.ts:17](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/live_metadata.ts#L17)
+
+***
+
+### wallTimeMs?
+
+> `optional` **wallTimeMs**: `number`
+
+Defined in: [protos/generated/video\_streaming/live\_metadata.ts:16](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/live_metadata.ts#L16)
diff --git a/docs/api/exports/protos/interfaces/MediaCapabilities.md b/docs/api/exports/protos/interfaces/MediaCapabilities.md
new file mode 100644
index 0000000..eae708b
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/MediaCapabilities.md
@@ -0,0 +1,29 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / MediaCapabilities
+
+# Interface: MediaCapabilities
+
+Defined in: [protos/generated/video\_streaming/media\_capabilities.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/media_capabilities.ts#L12)
+
+## Properties
+
+### audioFormatCapabilities
+
+> **audioFormatCapabilities**: `MediaCapabilities_AudioFormatCapability`[]
+
+Defined in: [protos/generated/video\_streaming/media\_capabilities.ts:14](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/media_capabilities.ts#L14)
+
+***
+
+### hdrModeBitmask?
+
+> `optional` **hdrModeBitmask**: `number`
+
+Defined in: [protos/generated/video\_streaming/media\_capabilities.ts:15](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/media_capabilities.ts#L15)
+
+***
+
+### videoFormatCapabilities
+
+> **videoFormatCapabilities**: `MediaCapabilities_VideoFormatCapability`[]
+
+Defined in: [protos/generated/video\_streaming/media\_capabilities.ts:13](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/media_capabilities.ts#L13)
diff --git a/docs/api/exports/protos/interfaces/MediaHeader.md b/docs/api/exports/protos/interfaces/MediaHeader.md
new file mode 100644
index 0000000..b6ffcbb
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/MediaHeader.md
@@ -0,0 +1,133 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / MediaHeader
+
+# Interface: MediaHeader
+
+Defined in: [protos/generated/video\_streaming/media\_header.ts:14](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/media_header.ts#L14)
+
+## Properties
+
+### bitrateBps?
+
+> `optional` **bitrateBps**: `number`
+
+Defined in: [protos/generated/video\_streaming/media\_header.ts:24](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/media_header.ts#L24)
+
+***
+
+### compressionAlgorithm?
+
+> `optional` **compressionAlgorithm**: [`CompressionType`](../enumerations/CompressionType.md)
+
+Defined in: [protos/generated/video\_streaming/media\_header.ts:21](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/media_header.ts#L21)
+
+***
+
+### contentLength?
+
+> `optional` **contentLength**: `number`
+
+Defined in: [protos/generated/video\_streaming/media\_header.ts:28](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/media_header.ts#L28)
+
+***
+
+### durationMs?
+
+> `optional` **durationMs**: `number`
+
+Defined in: [protos/generated/video\_streaming/media\_header.ts:26](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/media_header.ts#L26)
+
+***
+
+### formatId?
+
+> `optional` **formatId**: [`FormatId`](FormatId.md)
+
+Defined in: [protos/generated/video\_streaming/media\_header.ts:27](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/media_header.ts#L27)
+
+***
+
+### headerId?
+
+> `optional` **headerId**: `number`
+
+Defined in: [protos/generated/video\_streaming/media\_header.ts:15](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/media_header.ts#L15)
+
+***
+
+### isInitSeg?
+
+> `optional` **isInitSeg**: `boolean`
+
+Defined in: [protos/generated/video\_streaming/media\_header.ts:22](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/media_header.ts#L22)
+
+***
+
+### itag?
+
+> `optional` **itag**: `number`
+
+Defined in: [protos/generated/video\_streaming/media\_header.ts:17](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/media_header.ts#L17)
+
+***
+
+### lmt?
+
+> `optional` **lmt**: `number`
+
+Defined in: [protos/generated/video\_streaming/media\_header.ts:18](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/media_header.ts#L18)
+
+***
+
+### sequenceLmt?
+
+> `optional` **sequenceLmt**: `number`
+
+Defined in: [protos/generated/video\_streaming/media\_header.ts:30](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/media_header.ts#L30)
+
+***
+
+### sequenceNumber?
+
+> `optional` **sequenceNumber**: `number`
+
+Defined in: [protos/generated/video\_streaming/media\_header.ts:23](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/media_header.ts#L23)
+
+***
+
+### startMs?
+
+> `optional` **startMs**: `number`
+
+Defined in: [protos/generated/video\_streaming/media\_header.ts:25](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/media_header.ts#L25)
+
+***
+
+### startRange?
+
+> `optional` **startRange**: `number`
+
+Defined in: [protos/generated/video\_streaming/media\_header.ts:20](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/media_header.ts#L20)
+
+***
+
+### timeRange?
+
+> `optional` **timeRange**: `TimeRange`
+
+Defined in: [protos/generated/video\_streaming/media\_header.ts:29](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/media_header.ts#L29)
+
+***
+
+### videoId?
+
+> `optional` **videoId**: `string`
+
+Defined in: [protos/generated/video\_streaming/media\_header.ts:16](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/media_header.ts#L16)
+
+***
+
+### xtags?
+
+> `optional` **xtags**: `string`
+
+Defined in: [protos/generated/video\_streaming/media\_header.ts:19](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/media_header.ts#L19)
diff --git a/docs/api/exports/protos/interfaces/NextRequestPolicy.md b/docs/api/exports/protos/interfaces/NextRequestPolicy.md
new file mode 100644
index 0000000..ccb0e00
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/NextRequestPolicy.md
@@ -0,0 +1,69 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / NextRequestPolicy
+
+# Interface: NextRequestPolicy
+
+Defined in: [protos/generated/video\_streaming/next\_request\_policy.ts:13](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/next_request_policy.ts#L13)
+
+## Properties
+
+### backoffTimeMs?
+
+> `optional` **backoffTimeMs**: `number`
+
+Defined in: [protos/generated/video\_streaming/next\_request\_policy.ts:17](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/next_request_policy.ts#L17)
+
+***
+
+### maxTimeSinceLastRequestMs?
+
+> `optional` **maxTimeSinceLastRequestMs**: `number`
+
+Defined in: [protos/generated/video\_streaming/next\_request\_policy.ts:16](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/next_request_policy.ts#L16)
+
+***
+
+### minAudioReadaheadMs?
+
+> `optional` **minAudioReadaheadMs**: `number`
+
+Defined in: [protos/generated/video\_streaming/next\_request\_policy.ts:18](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/next_request_policy.ts#L18)
+
+***
+
+### minVideoReadaheadMs?
+
+> `optional` **minVideoReadaheadMs**: `number`
+
+Defined in: [protos/generated/video\_streaming/next\_request\_policy.ts:19](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/next_request_policy.ts#L19)
+
+***
+
+### playbackCookie?
+
+> `optional` **playbackCookie**: [`PlaybackCookie`](PlaybackCookie.md)
+
+Defined in: [protos/generated/video\_streaming/next\_request\_policy.ts:20](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/next_request_policy.ts#L20)
+
+***
+
+### targetAudioReadaheadMs?
+
+> `optional` **targetAudioReadaheadMs**: `number`
+
+Defined in: [protos/generated/video\_streaming/next\_request\_policy.ts:14](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/next_request_policy.ts#L14)
+
+***
+
+### targetVideoReadaheadMs?
+
+> `optional` **targetVideoReadaheadMs**: `number`
+
+Defined in: [protos/generated/video\_streaming/next\_request\_policy.ts:15](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/next_request_policy.ts#L15)
+
+***
+
+### videoId?
+
+> `optional` **videoId**: `string`
+
+Defined in: [protos/generated/video\_streaming/next\_request\_policy.ts:21](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/next_request_policy.ts#L21)
diff --git a/docs/api/exports/protos/interfaces/OnesieHeader.md b/docs/api/exports/protos/interfaces/OnesieHeader.md
new file mode 100644
index 0000000..1789e01
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/OnesieHeader.md
@@ -0,0 +1,93 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / OnesieHeader
+
+# Interface: OnesieHeader
+
+Defined in: [protos/generated/video\_streaming/onesie\_header.ts:14](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_header.ts#L14)
+
+## Properties
+
+### cryptoParams?
+
+> `optional` **cryptoParams**: [`CryptoParams`](CryptoParams.md)
+
+Defined in: [protos/generated/video\_streaming/onesie\_header.ts:18](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_header.ts#L18)
+
+***
+
+### expectedMediaSizeBytes?
+
+> `optional` **expectedMediaSizeBytes**: `number`
+
+Defined in: [protos/generated/video\_streaming/onesie\_header.ts:20](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_header.ts#L20)
+
+***
+
+### field23?
+
+> `optional` **field23**: `OnesieHeader_UnknownMessage1`
+
+Defined in: [protos/generated/video\_streaming/onesie\_header.ts:24](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_header.ts#L24)
+
+***
+
+### field34?
+
+> `optional` **field34**: `OnesieHeader_UnknownMessage2`
+
+Defined in: [protos/generated/video\_streaming/onesie\_header.ts:25](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_header.ts#L25)
+
+***
+
+### itag?
+
+> `optional` **itag**: `string`
+
+Defined in: [protos/generated/video\_streaming/onesie\_header.ts:17](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_header.ts#L17)
+
+***
+
+### lastModified?
+
+> `optional` **lastModified**: `number`
+
+Defined in: [protos/generated/video\_streaming/onesie\_header.ts:19](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_header.ts#L19)
+
+***
+
+### restrictedFormats
+
+> **restrictedFormats**: `string`[]
+
+Defined in: [protos/generated/video\_streaming/onesie\_header.ts:21](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_header.ts#L21)
+
+***
+
+### sequenceNumber?
+
+> `optional` **sequenceNumber**: `number`
+
+Defined in: [protos/generated/video\_streaming/onesie\_header.ts:23](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_header.ts#L23)
+
+***
+
+### type?
+
+> `optional` **type**: [`OnesieHeaderType`](../enumerations/OnesieHeaderType.md)
+
+Defined in: [protos/generated/video\_streaming/onesie\_header.ts:15](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_header.ts#L15)
+
+***
+
+### videoId?
+
+> `optional` **videoId**: `string`
+
+Defined in: [protos/generated/video\_streaming/onesie\_header.ts:16](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_header.ts#L16)
+
+***
+
+### xtags?
+
+> `optional` **xtags**: `string`
+
+Defined in: [protos/generated/video\_streaming/onesie\_header.ts:22](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_header.ts#L22)
diff --git a/docs/api/exports/protos/interfaces/OnesieInnertubeRequest.md b/docs/api/exports/protos/interfaces/OnesieInnertubeRequest.md
new file mode 100644
index 0000000..06df0b6
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/OnesieInnertubeRequest.md
@@ -0,0 +1,45 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / OnesieInnertubeRequest
+
+# Interface: OnesieInnertubeRequest
+
+Defined in: [protos/generated/video\_streaming/onesie\_innertube\_request.ts:13](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_innertube_request.ts#L13)
+
+## Properties
+
+### body?
+
+> `optional` **body**: `string`
+
+Defined in: [protos/generated/video\_streaming/onesie\_innertube\_request.ts:16](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_innertube_request.ts#L16)
+
+***
+
+### headers
+
+> **headers**: [`HttpHeader`](HttpHeader.md)[]
+
+Defined in: [protos/generated/video\_streaming/onesie\_innertube\_request.ts:15](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_innertube_request.ts#L15)
+
+***
+
+### proxiedByTrustedBandaid?
+
+> `optional` **proxiedByTrustedBandaid**: `boolean`
+
+Defined in: [protos/generated/video\_streaming/onesie\_innertube\_request.ts:17](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_innertube_request.ts#L17)
+
+***
+
+### skipResponseEncryption?
+
+> `optional` **skipResponseEncryption**: `boolean`
+
+Defined in: [protos/generated/video\_streaming/onesie\_innertube\_request.ts:18](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_innertube_request.ts#L18)
+
+***
+
+### url?
+
+> `optional` **url**: `string`
+
+Defined in: [protos/generated/video\_streaming/onesie\_innertube\_request.ts:14](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_innertube_request.ts#L14)
diff --git a/docs/api/exports/protos/interfaces/OnesieInnertubeResponse.md b/docs/api/exports/protos/interfaces/OnesieInnertubeResponse.md
new file mode 100644
index 0000000..e749596
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/OnesieInnertubeResponse.md
@@ -0,0 +1,37 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / OnesieInnertubeResponse
+
+# Interface: OnesieInnertubeResponse
+
+Defined in: [protos/generated/video\_streaming/onesie\_innertube\_response.ts:14](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_innertube_response.ts#L14)
+
+## Properties
+
+### body?
+
+> `optional` **body**: `Uint8Array`
+
+Defined in: [protos/generated/video\_streaming/onesie\_innertube\_response.ts:18](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_innertube_response.ts#L18)
+
+***
+
+### headers
+
+> **headers**: [`HttpHeader`](HttpHeader.md)[]
+
+Defined in: [protos/generated/video\_streaming/onesie\_innertube\_response.ts:17](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_innertube_response.ts#L17)
+
+***
+
+### httpStatus?
+
+> `optional` **httpStatus**: `number`
+
+Defined in: [protos/generated/video\_streaming/onesie\_innertube\_response.ts:16](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_innertube_response.ts#L16)
+
+***
+
+### onesieProxyStatus?
+
+> `optional` **onesieProxyStatus**: [`OnesieProxyStatus`](../enumerations/OnesieProxyStatus.md)
+
+Defined in: [protos/generated/video\_streaming/onesie\_innertube\_response.ts:15](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_innertube_response.ts#L15)
diff --git a/docs/api/exports/protos/interfaces/OnesieRequest.md b/docs/api/exports/protos/interfaces/OnesieRequest.md
new file mode 100644
index 0000000..f2617a5
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/OnesieRequest.md
@@ -0,0 +1,87 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / OnesieRequest
+
+# Interface: OnesieRequest
+
+Defined in: [protos/generated/video\_streaming/onesie\_request.ts:18](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_request.ts#L18)
+
+## Properties
+
+### bufferedRanges
+
+> **bufferedRanges**: [`BufferedRange`](BufferedRange.md)[]
+
+Defined in: [protos/generated/video\_streaming/onesie\_request.ts:30](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_request.ts#L30)
+
+***
+
+### clientAbrState?
+
+> `optional` **clientAbrState**: [`ClientAbrState`](ClientAbrState.md)
+
+Defined in: [protos/generated/video\_streaming/onesie\_request.ts:20](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_request.ts#L20)
+
+***
+
+### clientDisplayHeight?
+
+> `optional` **clientDisplayHeight**: `number`
+
+Defined in: [protos/generated/video\_streaming/onesie\_request.ts:24](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_request.ts#L24)
+
+***
+
+### innertubeRequest?
+
+> `optional` **innertubeRequest**: [`InnertubeRequest`](InnertubeRequest.md)
+
+Defined in: [protos/generated/video\_streaming/onesie\_request.ts:21](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_request.ts#L21)
+
+***
+
+### maxVp9Height?
+
+> `optional` **maxVp9Height**: `number`
+
+Defined in: [protos/generated/video\_streaming/onesie\_request.ts:23](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_request.ts#L23)
+
+***
+
+### onesieUstreamerConfig?
+
+> `optional` **onesieUstreamerConfig**: `Uint8Array`
+
+Defined in: [protos/generated/video\_streaming/onesie\_request.ts:22](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_request.ts#L22)
+
+***
+
+### reloadPlaybackParams?
+
+> `optional` **reloadPlaybackParams**: [`ReloadPlaybackParams`](ReloadPlaybackParams.md)
+
+Defined in: [protos/generated/video\_streaming/onesie\_request.ts:31](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_request.ts#L31)
+
+***
+
+### requestTarget?
+
+> `optional` **requestTarget**: [`OnesieRequestTarget`](../enumerations/OnesieRequestTarget.md)
+
+Defined in: [protos/generated/video\_streaming/onesie\_request.ts:29](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_request.ts#L29)
+
+MLOnesieRequestTarget
+
+***
+
+### streamerContext?
+
+> `optional` **streamerContext**: [`StreamerContext`](StreamerContext.md)
+
+Defined in: [protos/generated/video\_streaming/onesie\_request.ts:25](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_request.ts#L25)
+
+***
+
+### urls
+
+> **urls**: `string`[]
+
+Defined in: [protos/generated/video\_streaming/onesie\_request.ts:19](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_request.ts#L19)
diff --git a/docs/api/exports/protos/interfaces/PlaybackAuthorization.md b/docs/api/exports/protos/interfaces/PlaybackAuthorization.md
new file mode 100644
index 0000000..f873c41
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/PlaybackAuthorization.md
@@ -0,0 +1,21 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / PlaybackAuthorization
+
+# Interface: PlaybackAuthorization
+
+Defined in: [protos/generated/misc/common.ts:214](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L214)
+
+## Properties
+
+### authorizedFormats
+
+> **authorizedFormats**: [`AuthorizedFormat`](AuthorizedFormat.md)[]
+
+Defined in: [protos/generated/misc/common.ts:215](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L215)
+
+***
+
+### sabrLicenseConstraint?
+
+> `optional` **sabrLicenseConstraint**: `Uint8Array`
+
+Defined in: [protos/generated/misc/common.ts:216](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L216)
diff --git a/docs/api/exports/protos/interfaces/PlaybackCookie.md b/docs/api/exports/protos/interfaces/PlaybackCookie.md
new file mode 100644
index 0000000..483c566
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/PlaybackCookie.md
@@ -0,0 +1,39 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / PlaybackCookie
+
+# Interface: PlaybackCookie
+
+Defined in: [protos/generated/video\_streaming/playback\_cookie.ts:13](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/playback_cookie.ts#L13)
+
+## Properties
+
+### audioFmt?
+
+> `optional` **audioFmt**: [`FormatId`](FormatId.md)
+
+Defined in: [protos/generated/video\_streaming/playback\_cookie.ts:18](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/playback_cookie.ts#L18)
+
+***
+
+### field2?
+
+> `optional` **field2**: `number`
+
+Defined in: [protos/generated/video\_streaming/playback\_cookie.ts:16](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/playback_cookie.ts#L16)
+
+***
+
+### resolution?
+
+> `optional` **resolution**: `number`
+
+Defined in: [protos/generated/video\_streaming/playback\_cookie.ts:15](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/playback_cookie.ts#L15)
+
+Always 999999 when resolution is set manually, or if the auto selected one is the max available resolution.
+
+***
+
+### videoFmt?
+
+> `optional` **videoFmt**: [`FormatId`](FormatId.md)
+
+Defined in: [protos/generated/video\_streaming/playback\_cookie.ts:17](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/playback_cookie.ts#L17)
diff --git a/docs/api/exports/protos/interfaces/PlaybackStartPolicy.md b/docs/api/exports/protos/interfaces/PlaybackStartPolicy.md
new file mode 100644
index 0000000..18697f7
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/PlaybackStartPolicy.md
@@ -0,0 +1,21 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / PlaybackStartPolicy
+
+# Interface: PlaybackStartPolicy
+
+Defined in: [protos/generated/video\_streaming/playback\_start\_policy.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/playback_start_policy.ts#L12)
+
+## Properties
+
+### resumeMinReadaheadPolicy?
+
+> `optional` **resumeMinReadaheadPolicy**: `PlaybackStartPolicy_ReadaheadPolicy`
+
+Defined in: [protos/generated/video\_streaming/playback\_start\_policy.ts:14](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/playback_start_policy.ts#L14)
+
+***
+
+### startMinReadaheadPolicy?
+
+> `optional` **startMinReadaheadPolicy**: `PlaybackStartPolicy_ReadaheadPolicy`
+
+Defined in: [protos/generated/video\_streaming/playback\_start\_policy.ts:13](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/playback_start_policy.ts#L13)
diff --git a/docs/api/exports/protos/interfaces/Range.md b/docs/api/exports/protos/interfaces/Range.md
new file mode 100644
index 0000000..429e217
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/Range.md
@@ -0,0 +1,37 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / Range
+
+# Interface: Range
+
+Defined in: [protos/generated/misc/common.ts:192](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L192)
+
+## Properties
+
+### end?
+
+> `optional` **end**: `number`
+
+Defined in: [protos/generated/misc/common.ts:196](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L196)
+
+***
+
+### legacyEnd?
+
+> `optional` **legacyEnd**: `number`
+
+Defined in: [protos/generated/misc/common.ts:194](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L194)
+
+***
+
+### legacyStart?
+
+> `optional` **legacyStart**: `number`
+
+Defined in: [protos/generated/misc/common.ts:193](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L193)
+
+***
+
+### start?
+
+> `optional` **start**: `number`
+
+Defined in: [protos/generated/misc/common.ts:195](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L195)
diff --git a/docs/api/exports/protos/interfaces/ReloadPlaybackContext.md b/docs/api/exports/protos/interfaces/ReloadPlaybackContext.md
new file mode 100644
index 0000000..508cdc6
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/ReloadPlaybackContext.md
@@ -0,0 +1,13 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / ReloadPlaybackContext
+
+# Interface: ReloadPlaybackContext
+
+Defined in: [protos/generated/video\_streaming/reload\_player\_response.ts:16](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/reload_player_response.ts#L16)
+
+## Properties
+
+### reloadPlaybackParams?
+
+> `optional` **reloadPlaybackParams**: [`ReloadPlaybackParams`](ReloadPlaybackParams.md)
+
+Defined in: [protos/generated/video\_streaming/reload\_player\_response.ts:17](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/reload_player_response.ts#L17)
diff --git a/docs/api/exports/protos/interfaces/ReloadPlaybackParams.md b/docs/api/exports/protos/interfaces/ReloadPlaybackParams.md
new file mode 100644
index 0000000..b8ae875
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/ReloadPlaybackParams.md
@@ -0,0 +1,13 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / ReloadPlaybackParams
+
+# Interface: ReloadPlaybackParams
+
+Defined in: [protos/generated/video\_streaming/reload\_player\_response.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/reload_player_response.ts#L12)
+
+## Properties
+
+### token?
+
+> `optional` **token**: `string`
+
+Defined in: [protos/generated/video\_streaming/reload\_player\_response.ts:13](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/reload_player_response.ts#L13)
diff --git a/docs/api/exports/protos/interfaces/RequestCancellationPolicy.md b/docs/api/exports/protos/interfaces/RequestCancellationPolicy.md
new file mode 100644
index 0000000..e96aaff
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/RequestCancellationPolicy.md
@@ -0,0 +1,29 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / RequestCancellationPolicy
+
+# Interface: RequestCancellationPolicy
+
+Defined in: [protos/generated/video\_streaming/request\_cancellation\_policy.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/request_cancellation_policy.ts#L12)
+
+## Properties
+
+### items
+
+> **items**: `RequestCancellationPolicy_Item`[]
+
+Defined in: [protos/generated/video\_streaming/request\_cancellation\_policy.ts:14](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/request_cancellation_policy.ts#L14)
+
+***
+
+### jq?
+
+> `optional` **jq**: `number`
+
+Defined in: [protos/generated/video\_streaming/request\_cancellation\_policy.ts:15](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/request_cancellation_policy.ts#L15)
+
+***
+
+### N0?
+
+> `optional` **N0**: `number`
+
+Defined in: [protos/generated/video\_streaming/request\_cancellation\_policy.ts:13](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/request_cancellation_policy.ts#L13)
diff --git a/docs/api/exports/protos/interfaces/RequestIdentifier.md b/docs/api/exports/protos/interfaces/RequestIdentifier.md
new file mode 100644
index 0000000..d854bee
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/RequestIdentifier.md
@@ -0,0 +1,13 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / RequestIdentifier
+
+# Interface: RequestIdentifier
+
+Defined in: [protos/generated/video\_streaming/request\_identifier.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/request_identifier.ts#L12)
+
+## Properties
+
+### token?
+
+> `optional` **token**: `string`
+
+Defined in: [protos/generated/video\_streaming/request\_identifier.ts:13](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/request_identifier.ts#L13)
diff --git a/docs/api/exports/protos/interfaces/SabrContextSendingPolicy.md b/docs/api/exports/protos/interfaces/SabrContextSendingPolicy.md
new file mode 100644
index 0000000..9363d21
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/SabrContextSendingPolicy.md
@@ -0,0 +1,29 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / SabrContextSendingPolicy
+
+# Interface: SabrContextSendingPolicy
+
+Defined in: [protos/generated/video\_streaming/sabr\_context\_sending\_policy.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/sabr_context_sending_policy.ts#L12)
+
+## Properties
+
+### discardPolicy
+
+> **discardPolicy**: `number`[]
+
+Defined in: [protos/generated/video\_streaming/sabr\_context\_sending\_policy.ts:15](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/sabr_context_sending_policy.ts#L15)
+
+***
+
+### startPolicy
+
+> **startPolicy**: `number`[]
+
+Defined in: [protos/generated/video\_streaming/sabr\_context\_sending\_policy.ts:13](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/sabr_context_sending_policy.ts#L13)
+
+***
+
+### stopPolicy
+
+> **stopPolicy**: `number`[]
+
+Defined in: [protos/generated/video\_streaming/sabr\_context\_sending\_policy.ts:14](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/sabr_context_sending_policy.ts#L14)
diff --git a/docs/api/exports/protos/interfaces/SabrContextUpdate.md b/docs/api/exports/protos/interfaces/SabrContextUpdate.md
new file mode 100644
index 0000000..8f29d37
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/SabrContextUpdate.md
@@ -0,0 +1,45 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / SabrContextUpdate
+
+# Interface: SabrContextUpdate
+
+Defined in: [protos/generated/video\_streaming/sabr\_context\_update.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/sabr_context_update.ts#L12)
+
+## Properties
+
+### scope?
+
+> `optional` **scope**: `SabrContextUpdate_SabrContextScope`
+
+Defined in: [protos/generated/video\_streaming/sabr\_context\_update.ts:14](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/sabr_context_update.ts#L14)
+
+***
+
+### sendByDefault?
+
+> `optional` **sendByDefault**: `boolean`
+
+Defined in: [protos/generated/video\_streaming/sabr\_context\_update.ts:16](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/sabr_context_update.ts#L16)
+
+***
+
+### type?
+
+> `optional` **type**: `number`
+
+Defined in: [protos/generated/video\_streaming/sabr\_context\_update.ts:13](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/sabr_context_update.ts#L13)
+
+***
+
+### value?
+
+> `optional` **value**: `Uint8Array`
+
+Defined in: [protos/generated/video\_streaming/sabr\_context\_update.ts:15](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/sabr_context_update.ts#L15)
+
+***
+
+### writePolicy?
+
+> `optional` **writePolicy**: [`SabrContextWritePolicy`](../enumerations/SabrContextWritePolicy.md)
+
+Defined in: [protos/generated/video\_streaming/sabr\_context\_update.ts:17](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/sabr_context_update.ts#L17)
diff --git a/docs/api/exports/protos/interfaces/SabrContextValue.md b/docs/api/exports/protos/interfaces/SabrContextValue.md
new file mode 100644
index 0000000..42ece99
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/SabrContextValue.md
@@ -0,0 +1,31 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / SabrContextValue
+
+# Interface: SabrContextValue
+
+Defined in: [protos/generated/video\_streaming/sabr\_context\_update.ts:37](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/sabr_context_update.ts#L37)
+
+For debugging
+
+## Properties
+
+### field5?
+
+> `optional` **field5**: `number`
+
+Defined in: [protos/generated/video\_streaming/sabr\_context\_update.ts:40](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/sabr_context_update.ts#L40)
+
+***
+
+### signature?
+
+> `optional` **signature**: `Uint8Array`
+
+Defined in: [protos/generated/video\_streaming/sabr\_context\_update.ts:39](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/sabr_context_update.ts#L39)
+
+***
+
+### timing?
+
+> `optional` **timing**: `SabrContextValue_TimingInfo`
+
+Defined in: [protos/generated/video\_streaming/sabr\_context\_update.ts:38](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/sabr_context_update.ts#L38)
diff --git a/docs/api/exports/protos/interfaces/SabrError.md b/docs/api/exports/protos/interfaces/SabrError.md
new file mode 100644
index 0000000..9a74410
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/SabrError.md
@@ -0,0 +1,21 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / SabrError
+
+# Interface: SabrError
+
+Defined in: [protos/generated/video\_streaming/sabr\_error.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/sabr_error.ts#L12)
+
+## Properties
+
+### code?
+
+> `optional` **code**: `number`
+
+Defined in: [protos/generated/video\_streaming/sabr\_error.ts:14](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/sabr_error.ts#L14)
+
+***
+
+### type?
+
+> `optional` **type**: `string`
+
+Defined in: [protos/generated/video\_streaming/sabr\_error.ts:13](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/sabr_error.ts#L13)
diff --git a/docs/api/exports/protos/interfaces/SabrRedirect.md b/docs/api/exports/protos/interfaces/SabrRedirect.md
new file mode 100644
index 0000000..e941cc0
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/SabrRedirect.md
@@ -0,0 +1,13 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / SabrRedirect
+
+# Interface: SabrRedirect
+
+Defined in: [protos/generated/video\_streaming/sabr\_redirect.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/sabr_redirect.ts#L12)
+
+## Properties
+
+### url?
+
+> `optional` **url**: `string`
+
+Defined in: [protos/generated/video\_streaming/sabr\_redirect.ts:13](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/sabr_redirect.ts#L13)
diff --git a/docs/api/exports/protos/interfaces/SnackbarMessage.md b/docs/api/exports/protos/interfaces/SnackbarMessage.md
new file mode 100644
index 0000000..8eb5e44
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/SnackbarMessage.md
@@ -0,0 +1,13 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / SnackbarMessage
+
+# Interface: SnackbarMessage
+
+Defined in: [protos/generated/video\_streaming/snackbar\_message.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/snackbar_message.ts#L12)
+
+## Properties
+
+### id?
+
+> `optional` **id**: `number`
+
+Defined in: [protos/generated/video\_streaming/snackbar\_message.ts:13](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/snackbar_message.ts#L13)
diff --git a/docs/api/exports/protos/interfaces/StreamProtectionStatus.md b/docs/api/exports/protos/interfaces/StreamProtectionStatus.md
new file mode 100644
index 0000000..6e4211a
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/StreamProtectionStatus.md
@@ -0,0 +1,21 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / StreamProtectionStatus
+
+# Interface: StreamProtectionStatus
+
+Defined in: [protos/generated/video\_streaming/stream\_protection\_status.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/stream_protection_status.ts#L12)
+
+## Properties
+
+### maxRetries?
+
+> `optional` **maxRetries**: `number`
+
+Defined in: [protos/generated/video\_streaming/stream\_protection\_status.ts:14](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/stream_protection_status.ts#L14)
+
+***
+
+### status?
+
+> `optional` **status**: `number`
+
+Defined in: [protos/generated/video\_streaming/stream\_protection\_status.ts:13](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/stream_protection_status.ts#L13)
diff --git a/docs/api/exports/protos/interfaces/StreamerContext.md b/docs/api/exports/protos/interfaces/StreamerContext.md
new file mode 100644
index 0000000..54e63c4
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/StreamerContext.md
@@ -0,0 +1,69 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / StreamerContext
+
+# Interface: StreamerContext
+
+Defined in: [protos/generated/video\_streaming/streamer\_context.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/streamer_context.ts#L12)
+
+## Properties
+
+### clientInfo?
+
+> `optional` **clientInfo**: [`ClientInfo`](ClientInfo.md)
+
+Defined in: [protos/generated/video\_streaming/streamer\_context.ts:13](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/streamer_context.ts#L13)
+
+***
+
+### field4?
+
+> `optional` **field4**: `Uint8Array`
+
+Defined in: [protos/generated/video\_streaming/streamer\_context.ts:16](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/streamer_context.ts#L16)
+
+***
+
+### field7?
+
+> `optional` **field7**: `string`
+
+Defined in: [protos/generated/video\_streaming/streamer\_context.ts:19](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/streamer_context.ts#L19)
+
+***
+
+### field8?
+
+> `optional` **field8**: `StreamerContext_UnknownMessage1`
+
+Defined in: [protos/generated/video\_streaming/streamer\_context.ts:20](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/streamer_context.ts#L20)
+
+***
+
+### playbackCookie?
+
+> `optional` **playbackCookie**: `Uint8Array`
+
+Defined in: [protos/generated/video\_streaming/streamer\_context.ts:15](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/streamer_context.ts#L15)
+
+***
+
+### poToken?
+
+> `optional` **poToken**: `Uint8Array`
+
+Defined in: [protos/generated/video\_streaming/streamer\_context.ts:14](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/streamer_context.ts#L14)
+
+***
+
+### sabrContexts
+
+> **sabrContexts**: `StreamerContext_SabrContext`[]
+
+Defined in: [protos/generated/video\_streaming/streamer\_context.ts:17](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/streamer_context.ts#L17)
+
+***
+
+### unsentSabrContexts
+
+> **unsentSabrContexts**: `number`[]
+
+Defined in: [protos/generated/video\_streaming/streamer\_context.ts:18](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/streamer_context.ts#L18)
diff --git a/docs/api/exports/protos/interfaces/UstreamerFlags.md b/docs/api/exports/protos/interfaces/UstreamerFlags.md
new file mode 100644
index 0000000..8d16769
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/UstreamerFlags.md
@@ -0,0 +1,13 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / UstreamerFlags
+
+# Interface: UstreamerFlags
+
+Defined in: [protos/generated/video\_streaming/innertube\_request.ts:27](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/innertube_request.ts#L27)
+
+## Properties
+
+### sendVideoPlaybackConfig?
+
+> `optional` **sendVideoPlaybackConfig**: `boolean`
+
+Defined in: [protos/generated/video\_streaming/innertube\_request.ts:28](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/innertube_request.ts#L28)
diff --git a/docs/api/exports/protos/interfaces/VideoPlaybackAbrRequest.md b/docs/api/exports/protos/interfaces/VideoPlaybackAbrRequest.md
new file mode 100644
index 0000000..9b1eced
--- /dev/null
+++ b/docs/api/exports/protos/interfaces/VideoPlaybackAbrRequest.md
@@ -0,0 +1,123 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / VideoPlaybackAbrRequest
+
+# Interface: VideoPlaybackAbrRequest
+
+Defined in: [protos/generated/video\_streaming/video\_playback\_abr\_request.ts:17](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/video_playback_abr_request.ts#L17)
+
+## Properties
+
+### bufferedRanges
+
+> **bufferedRanges**: [`BufferedRange`](BufferedRange.md)[]
+
+Defined in: [protos/generated/video\_streaming/video\_playback\_abr\_request.ts:20](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/video_playback_abr_request.ts#L20)
+
+***
+
+### clientAbrState?
+
+> `optional` **clientAbrState**: [`ClientAbrState`](ClientAbrState.md)
+
+Defined in: [protos/generated/video\_streaming/video\_playback\_abr\_request.ts:18](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/video_playback_abr_request.ts#L18)
+
+***
+
+### field1000
+
+> **field1000**: `UnknownMessage3`[]
+
+Defined in: [protos/generated/video\_streaming/video\_playback\_abr\_request.ts:36](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/video_playback_abr_request.ts#L36)
+
+***
+
+### field21?
+
+> `optional` **field21**: `UnknownMessage2`
+
+Defined in: [protos/generated/video\_streaming/video\_playback\_abr\_request.ts:33](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/video_playback_abr_request.ts#L33)
+
+***
+
+### field22?
+
+> `optional` **field22**: `number`
+
+Defined in: [protos/generated/video\_streaming/video\_playback\_abr\_request.ts:34](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/video_playback_abr_request.ts#L34)
+
+***
+
+### field23?
+
+> `optional` **field23**: `number`
+
+Defined in: [protos/generated/video\_streaming/video\_playback\_abr\_request.ts:35](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/video_playback_abr_request.ts#L35)
+
+***
+
+### field6?
+
+> `optional` **field6**: `UnknownMessage1`
+
+Defined in: [protos/generated/video\_streaming/video\_playback\_abr\_request.ts:24](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/video_playback_abr_request.ts#L24)
+
+***
+
+### playerTimeMs?
+
+> `optional` **playerTimeMs**: `number`
+
+Defined in: [protos/generated/video\_streaming/video\_playback\_abr\_request.ts:22](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/video_playback_abr_request.ts#L22)
+
+`osts` (Onesie Start Time Seconds) param on Onesie requests.
+
+***
+
+### preferredAudioFormatIds
+
+> **preferredAudioFormatIds**: [`FormatId`](FormatId.md)[]
+
+Defined in: [protos/generated/video\_streaming/video\_playback\_abr\_request.ts:28](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/video_playback_abr_request.ts#L28)
+
+`pai` (Preferred Audio Itags) param on Onesie requests.
+
+***
+
+### preferredSubtitleFormatIds
+
+> **preferredSubtitleFormatIds**: [`FormatId`](FormatId.md)[]
+
+Defined in: [protos/generated/video\_streaming/video\_playback\_abr\_request.ts:31](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/video_playback_abr_request.ts#L31)
+
+***
+
+### preferredVideoFormatIds
+
+> **preferredVideoFormatIds**: [`FormatId`](FormatId.md)[]
+
+Defined in: [protos/generated/video\_streaming/video\_playback\_abr\_request.ts:30](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/video_playback_abr_request.ts#L30)
+
+`pvi` (Preferred Video Itags) param on Onesie requests.
+
+***
+
+### selectedFormatIds
+
+> **selectedFormatIds**: [`FormatId`](FormatId.md)[]
+
+Defined in: [protos/generated/video\_streaming/video\_playback\_abr\_request.ts:19](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/video_playback_abr_request.ts#L19)
+
+***
+
+### streamerContext?
+
+> `optional` **streamerContext**: [`StreamerContext`](StreamerContext.md)
+
+Defined in: [protos/generated/video\_streaming/video\_playback\_abr\_request.ts:32](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/video_playback_abr_request.ts#L32)
+
+***
+
+### videoPlaybackUstreamerConfig?
+
+> `optional` **videoPlaybackUstreamerConfig**: `Uint8Array`
+
+Defined in: [protos/generated/video\_streaming/video\_playback\_abr\_request.ts:23](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/video_playback_abr_request.ts#L23)
diff --git a/docs/api/exports/protos/variables/AuthorizedFormat.md b/docs/api/exports/protos/variables/AuthorizedFormat.md
new file mode 100644
index 0000000..b5b79bf
--- /dev/null
+++ b/docs/api/exports/protos/variables/AuthorizedFormat.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / AuthorizedFormat
+
+# Variable: AuthorizedFormat
+
+> **AuthorizedFormat**: `MessageFns`\<[`AuthorizedFormat`](../interfaces/AuthorizedFormat.md)\>
+
+Defined in: [protos/generated/misc/common.ts:209](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L209)
diff --git a/docs/api/exports/protos/variables/BufferedRange.md b/docs/api/exports/protos/variables/BufferedRange.md
new file mode 100644
index 0000000..4b21c9c
--- /dev/null
+++ b/docs/api/exports/protos/variables/BufferedRange.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / BufferedRange
+
+# Variable: BufferedRange
+
+> **BufferedRange**: `MessageFns`\<[`BufferedRange`](../interfaces/BufferedRange.md)\>
+
+Defined in: [protos/generated/video\_streaming/buffered\_range.ts:14](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/buffered_range.ts#L14)
diff --git a/docs/api/exports/protos/variables/ClientAbrState.md b/docs/api/exports/protos/variables/ClientAbrState.md
new file mode 100644
index 0000000..4522eb8
--- /dev/null
+++ b/docs/api/exports/protos/variables/ClientAbrState.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / ClientAbrState
+
+# Variable: ClientAbrState
+
+> **ClientAbrState**: `MessageFns`\<[`ClientAbrState`](../interfaces/ClientAbrState.md)\>
+
+Defined in: [protos/generated/video\_streaming/client\_abr\_state.ts:20](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/client_abr_state.ts#L20)
diff --git a/docs/api/exports/protos/variables/ClientInfo.md b/docs/api/exports/protos/variables/ClientInfo.md
new file mode 100644
index 0000000..f859f6d
--- /dev/null
+++ b/docs/api/exports/protos/variables/ClientInfo.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / ClientInfo
+
+# Variable: ClientInfo
+
+> **ClientInfo**: `MessageFns`\<[`ClientInfo`](../interfaces/ClientInfo.md)\>
+
+Defined in: [protos/generated/video\_streaming/streamer\_context.ts:30](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/streamer_context.ts#L30)
diff --git a/docs/api/exports/protos/variables/CryptoParams.md b/docs/api/exports/protos/variables/CryptoParams.md
new file mode 100644
index 0000000..5338177
--- /dev/null
+++ b/docs/api/exports/protos/variables/CryptoParams.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / CryptoParams
+
+# Variable: CryptoParams
+
+> **CryptoParams**: `MessageFns`\<[`CryptoParams`](../interfaces/CryptoParams.md)\>
+
+Defined in: [protos/generated/video\_streaming/crypto\_params.ts:13](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/crypto_params.ts#L13)
diff --git a/docs/api/exports/protos/variables/FormatId.md b/docs/api/exports/protos/variables/FormatId.md
new file mode 100644
index 0000000..2653551
--- /dev/null
+++ b/docs/api/exports/protos/variables/FormatId.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / FormatId
+
+# Variable: FormatId
+
+> **FormatId**: `MessageFns`\<[`FormatId`](../interfaces/FormatId.md)\>
+
+Defined in: [protos/generated/misc/common.ts:186](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L186)
diff --git a/docs/api/exports/protos/variables/FormatInitializationMetadata.md b/docs/api/exports/protos/variables/FormatInitializationMetadata.md
new file mode 100644
index 0000000..a84296a
--- /dev/null
+++ b/docs/api/exports/protos/variables/FormatInitializationMetadata.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / FormatInitializationMetadata
+
+# Variable: FormatInitializationMetadata
+
+> **FormatInitializationMetadata**: `MessageFns`\<[`FormatInitializationMetadata`](../interfaces/FormatInitializationMetadata.md)\>
+
+Defined in: [protos/generated/video\_streaming/format\_initialization\_metadata.ts:13](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/format_initialization_metadata.ts#L13)
diff --git a/docs/api/exports/protos/variables/FormatSelectionConfig.md b/docs/api/exports/protos/variables/FormatSelectionConfig.md
new file mode 100644
index 0000000..4352fb8
--- /dev/null
+++ b/docs/api/exports/protos/variables/FormatSelectionConfig.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / FormatSelectionConfig
+
+# Variable: FormatSelectionConfig
+
+> **FormatSelectionConfig**: `MessageFns`\<[`FormatSelectionConfig`](../interfaces/FormatSelectionConfig.md)\>
+
+Defined in: [protos/generated/video\_streaming/format\_selection\_config.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/format_selection_config.ts#L12)
diff --git a/docs/api/exports/protos/variables/HttpHeader.md b/docs/api/exports/protos/variables/HttpHeader.md
new file mode 100644
index 0000000..c64a967
--- /dev/null
+++ b/docs/api/exports/protos/variables/HttpHeader.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / HttpHeader
+
+# Variable: HttpHeader
+
+> **HttpHeader**: `MessageFns`\<[`HttpHeader`](../interfaces/HttpHeader.md)\>
+
+Defined in: [protos/generated/misc/common.ts:181](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L181)
diff --git a/docs/api/exports/protos/variables/IdentifierToken.md b/docs/api/exports/protos/variables/IdentifierToken.md
new file mode 100644
index 0000000..7ee94b3
--- /dev/null
+++ b/docs/api/exports/protos/variables/IdentifierToken.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / IdentifierToken
+
+# Variable: IdentifierToken
+
+> **IdentifierToken**: `MessageFns`\<[`IdentifierToken`](../interfaces/IdentifierToken.md)\>
+
+Defined in: [protos/generated/misc/common.ts:199](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L199)
diff --git a/docs/api/exports/protos/variables/InnertubeRequest.md b/docs/api/exports/protos/variables/InnertubeRequest.md
new file mode 100644
index 0000000..bc37745
--- /dev/null
+++ b/docs/api/exports/protos/variables/InnertubeRequest.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / InnertubeRequest
+
+# Variable: InnertubeRequest
+
+> **InnertubeRequest**: `MessageFns`\<[`InnertubeRequest`](../interfaces/InnertubeRequest.md)\>
+
+Defined in: [protos/generated/video\_streaming/innertube\_request.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/innertube_request.ts#L12)
diff --git a/docs/api/exports/protos/variables/KeyValuePair.md b/docs/api/exports/protos/variables/KeyValuePair.md
new file mode 100644
index 0000000..485e093
--- /dev/null
+++ b/docs/api/exports/protos/variables/KeyValuePair.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / KeyValuePair
+
+# Variable: KeyValuePair
+
+> **KeyValuePair**: `MessageFns`\<[`KeyValuePair`](../interfaces/KeyValuePair.md)\>
+
+Defined in: [protos/generated/misc/common.ts:204](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L204)
diff --git a/docs/api/exports/protos/variables/LiveMetadata.md b/docs/api/exports/protos/variables/LiveMetadata.md
new file mode 100644
index 0000000..ffbd2ee
--- /dev/null
+++ b/docs/api/exports/protos/variables/LiveMetadata.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / LiveMetadata
+
+# Variable: LiveMetadata
+
+> **LiveMetadata**: `MessageFns`\<[`LiveMetadata`](../interfaces/LiveMetadata.md)\>
+
+Defined in: [protos/generated/video\_streaming/live\_metadata.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/live_metadata.ts#L12)
diff --git a/docs/api/exports/protos/variables/MediaCapabilities.md b/docs/api/exports/protos/variables/MediaCapabilities.md
new file mode 100644
index 0000000..a11e4ab
--- /dev/null
+++ b/docs/api/exports/protos/variables/MediaCapabilities.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / MediaCapabilities
+
+# Variable: MediaCapabilities
+
+> **MediaCapabilities**: `MessageFns`\<[`MediaCapabilities`](../interfaces/MediaCapabilities.md)\>
+
+Defined in: [protos/generated/video\_streaming/media\_capabilities.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/media_capabilities.ts#L12)
diff --git a/docs/api/exports/protos/variables/MediaHeader.md b/docs/api/exports/protos/variables/MediaHeader.md
new file mode 100644
index 0000000..e5ebf37
--- /dev/null
+++ b/docs/api/exports/protos/variables/MediaHeader.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / MediaHeader
+
+# Variable: MediaHeader
+
+> **MediaHeader**: `MessageFns`\<[`MediaHeader`](../interfaces/MediaHeader.md)\>
+
+Defined in: [protos/generated/video\_streaming/media\_header.ts:14](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/media_header.ts#L14)
diff --git a/docs/api/exports/protos/variables/NextRequestPolicy.md b/docs/api/exports/protos/variables/NextRequestPolicy.md
new file mode 100644
index 0000000..b347dd6
--- /dev/null
+++ b/docs/api/exports/protos/variables/NextRequestPolicy.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / NextRequestPolicy
+
+# Variable: NextRequestPolicy
+
+> **NextRequestPolicy**: `MessageFns`\<[`NextRequestPolicy`](../interfaces/NextRequestPolicy.md)\>
+
+Defined in: [protos/generated/video\_streaming/next\_request\_policy.ts:13](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/next_request_policy.ts#L13)
diff --git a/docs/api/exports/protos/variables/OnesieHeader.md b/docs/api/exports/protos/variables/OnesieHeader.md
new file mode 100644
index 0000000..7448500
--- /dev/null
+++ b/docs/api/exports/protos/variables/OnesieHeader.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / OnesieHeader
+
+# Variable: OnesieHeader
+
+> **OnesieHeader**: `MessageFns`\<[`OnesieHeader`](../interfaces/OnesieHeader.md)\>
+
+Defined in: [protos/generated/video\_streaming/onesie\_header.ts:14](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_header.ts#L14)
diff --git a/docs/api/exports/protos/variables/OnesieInnertubeRequest.md b/docs/api/exports/protos/variables/OnesieInnertubeRequest.md
new file mode 100644
index 0000000..92ba5ef
--- /dev/null
+++ b/docs/api/exports/protos/variables/OnesieInnertubeRequest.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / OnesieInnertubeRequest
+
+# Variable: OnesieInnertubeRequest
+
+> **OnesieInnertubeRequest**: `MessageFns`\<[`OnesieInnertubeRequest`](../interfaces/OnesieInnertubeRequest.md)\>
+
+Defined in: [protos/generated/video\_streaming/onesie\_innertube\_request.ts:13](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_innertube_request.ts#L13)
diff --git a/docs/api/exports/protos/variables/OnesieInnertubeResponse.md b/docs/api/exports/protos/variables/OnesieInnertubeResponse.md
new file mode 100644
index 0000000..affe258
--- /dev/null
+++ b/docs/api/exports/protos/variables/OnesieInnertubeResponse.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / OnesieInnertubeResponse
+
+# Variable: OnesieInnertubeResponse
+
+> **OnesieInnertubeResponse**: `MessageFns`\<[`OnesieInnertubeResponse`](../interfaces/OnesieInnertubeResponse.md)\>
+
+Defined in: [protos/generated/video\_streaming/onesie\_innertube\_response.ts:14](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_innertube_response.ts#L14)
diff --git a/docs/api/exports/protos/variables/OnesieRequest.md b/docs/api/exports/protos/variables/OnesieRequest.md
new file mode 100644
index 0000000..1296294
--- /dev/null
+++ b/docs/api/exports/protos/variables/OnesieRequest.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / OnesieRequest
+
+# Variable: OnesieRequest
+
+> **OnesieRequest**: `MessageFns`\<[`OnesieRequest`](../interfaces/OnesieRequest.md)\>
+
+Defined in: [protos/generated/video\_streaming/onesie\_request.ts:18](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/onesie_request.ts#L18)
diff --git a/docs/api/exports/protos/variables/PlaybackAuthorization.md b/docs/api/exports/protos/variables/PlaybackAuthorization.md
new file mode 100644
index 0000000..7a20be4
--- /dev/null
+++ b/docs/api/exports/protos/variables/PlaybackAuthorization.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / PlaybackAuthorization
+
+# Variable: PlaybackAuthorization
+
+> **PlaybackAuthorization**: `MessageFns`\<[`PlaybackAuthorization`](../interfaces/PlaybackAuthorization.md)\>
+
+Defined in: [protos/generated/misc/common.ts:214](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L214)
diff --git a/docs/api/exports/protos/variables/PlaybackCookie.md b/docs/api/exports/protos/variables/PlaybackCookie.md
new file mode 100644
index 0000000..be6c75c
--- /dev/null
+++ b/docs/api/exports/protos/variables/PlaybackCookie.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / PlaybackCookie
+
+# Variable: PlaybackCookie
+
+> **PlaybackCookie**: `MessageFns`\<[`PlaybackCookie`](../interfaces/PlaybackCookie.md)\>
+
+Defined in: [protos/generated/video\_streaming/playback\_cookie.ts:13](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/playback_cookie.ts#L13)
diff --git a/docs/api/exports/protos/variables/PlaybackStartPolicy.md b/docs/api/exports/protos/variables/PlaybackStartPolicy.md
new file mode 100644
index 0000000..316eefb
--- /dev/null
+++ b/docs/api/exports/protos/variables/PlaybackStartPolicy.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / PlaybackStartPolicy
+
+# Variable: PlaybackStartPolicy
+
+> **PlaybackStartPolicy**: `MessageFns`\<[`PlaybackStartPolicy`](../interfaces/PlaybackStartPolicy.md)\>
+
+Defined in: [protos/generated/video\_streaming/playback\_start\_policy.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/playback_start_policy.ts#L12)
diff --git a/docs/api/exports/protos/variables/Range.md b/docs/api/exports/protos/variables/Range.md
new file mode 100644
index 0000000..7d69aef
--- /dev/null
+++ b/docs/api/exports/protos/variables/Range.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / Range
+
+# Variable: Range
+
+> **Range**: `MessageFns`\<[`Range`](../interfaces/Range.md)\>
+
+Defined in: [protos/generated/misc/common.ts:192](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/misc/common.ts#L192)
diff --git a/docs/api/exports/protos/variables/ReloadPlaybackContext.md b/docs/api/exports/protos/variables/ReloadPlaybackContext.md
new file mode 100644
index 0000000..94d421a
--- /dev/null
+++ b/docs/api/exports/protos/variables/ReloadPlaybackContext.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / ReloadPlaybackContext
+
+# Variable: ReloadPlaybackContext
+
+> **ReloadPlaybackContext**: `MessageFns`\<[`ReloadPlaybackContext`](../interfaces/ReloadPlaybackContext.md)\>
+
+Defined in: [protos/generated/video\_streaming/reload\_player\_response.ts:16](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/reload_player_response.ts#L16)
diff --git a/docs/api/exports/protos/variables/ReloadPlaybackParams.md b/docs/api/exports/protos/variables/ReloadPlaybackParams.md
new file mode 100644
index 0000000..fbd41dc
--- /dev/null
+++ b/docs/api/exports/protos/variables/ReloadPlaybackParams.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / ReloadPlaybackParams
+
+# Variable: ReloadPlaybackParams
+
+> **ReloadPlaybackParams**: `MessageFns`\<[`ReloadPlaybackParams`](../interfaces/ReloadPlaybackParams.md)\>
+
+Defined in: [protos/generated/video\_streaming/reload\_player\_response.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/reload_player_response.ts#L12)
diff --git a/docs/api/exports/protos/variables/RequestCancellationPolicy.md b/docs/api/exports/protos/variables/RequestCancellationPolicy.md
new file mode 100644
index 0000000..a9e288e
--- /dev/null
+++ b/docs/api/exports/protos/variables/RequestCancellationPolicy.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / RequestCancellationPolicy
+
+# Variable: RequestCancellationPolicy
+
+> **RequestCancellationPolicy**: `MessageFns`\<[`RequestCancellationPolicy`](../interfaces/RequestCancellationPolicy.md)\>
+
+Defined in: [protos/generated/video\_streaming/request\_cancellation\_policy.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/request_cancellation_policy.ts#L12)
diff --git a/docs/api/exports/protos/variables/RequestIdentifier.md b/docs/api/exports/protos/variables/RequestIdentifier.md
new file mode 100644
index 0000000..e2d1b07
--- /dev/null
+++ b/docs/api/exports/protos/variables/RequestIdentifier.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / RequestIdentifier
+
+# Variable: RequestIdentifier
+
+> **RequestIdentifier**: `MessageFns`\<[`RequestIdentifier`](../interfaces/RequestIdentifier.md)\>
+
+Defined in: [protos/generated/video\_streaming/request\_identifier.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/request_identifier.ts#L12)
diff --git a/docs/api/exports/protos/variables/SabrContextSendingPolicy.md b/docs/api/exports/protos/variables/SabrContextSendingPolicy.md
new file mode 100644
index 0000000..9cb9332
--- /dev/null
+++ b/docs/api/exports/protos/variables/SabrContextSendingPolicy.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / SabrContextSendingPolicy
+
+# Variable: SabrContextSendingPolicy
+
+> **SabrContextSendingPolicy**: `MessageFns`\<[`SabrContextSendingPolicy`](../interfaces/SabrContextSendingPolicy.md)\>
+
+Defined in: [protos/generated/video\_streaming/sabr\_context\_sending\_policy.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/sabr_context_sending_policy.ts#L12)
diff --git a/docs/api/exports/protos/variables/SabrContextUpdate.md b/docs/api/exports/protos/variables/SabrContextUpdate.md
new file mode 100644
index 0000000..ba14ea2
--- /dev/null
+++ b/docs/api/exports/protos/variables/SabrContextUpdate.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / SabrContextUpdate
+
+# Variable: SabrContextUpdate
+
+> **SabrContextUpdate**: `MessageFns`\<[`SabrContextUpdate`](../interfaces/SabrContextUpdate.md)\>
+
+Defined in: [protos/generated/video\_streaming/sabr\_context\_update.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/sabr_context_update.ts#L12)
diff --git a/docs/api/exports/protos/variables/SabrContextValue.md b/docs/api/exports/protos/variables/SabrContextValue.md
new file mode 100644
index 0000000..893c3cc
--- /dev/null
+++ b/docs/api/exports/protos/variables/SabrContextValue.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / SabrContextValue
+
+# Variable: SabrContextValue
+
+> **SabrContextValue**: `MessageFns`\<[`SabrContextValue`](../interfaces/SabrContextValue.md)\>
+
+Defined in: [protos/generated/video\_streaming/sabr\_context\_update.ts:37](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/sabr_context_update.ts#L37)
diff --git a/docs/api/exports/protos/variables/SabrError.md b/docs/api/exports/protos/variables/SabrError.md
new file mode 100644
index 0000000..87a267e
--- /dev/null
+++ b/docs/api/exports/protos/variables/SabrError.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / SabrError
+
+# Variable: SabrError
+
+> **SabrError**: `MessageFns`\<[`SabrError`](../interfaces/SabrError.md)\>
+
+Defined in: [protos/generated/video\_streaming/sabr\_error.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/sabr_error.ts#L12)
diff --git a/docs/api/exports/protos/variables/SabrRedirect.md b/docs/api/exports/protos/variables/SabrRedirect.md
new file mode 100644
index 0000000..66b92d2
--- /dev/null
+++ b/docs/api/exports/protos/variables/SabrRedirect.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / SabrRedirect
+
+# Variable: SabrRedirect
+
+> **SabrRedirect**: `MessageFns`\<[`SabrRedirect`](../interfaces/SabrRedirect.md)\>
+
+Defined in: [protos/generated/video\_streaming/sabr\_redirect.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/sabr_redirect.ts#L12)
diff --git a/docs/api/exports/protos/variables/SnackbarMessage.md b/docs/api/exports/protos/variables/SnackbarMessage.md
new file mode 100644
index 0000000..d7d3414
--- /dev/null
+++ b/docs/api/exports/protos/variables/SnackbarMessage.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / SnackbarMessage
+
+# Variable: SnackbarMessage
+
+> **SnackbarMessage**: `MessageFns`\<[`SnackbarMessage`](../interfaces/SnackbarMessage.md)\>
+
+Defined in: [protos/generated/video\_streaming/snackbar\_message.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/snackbar_message.ts#L12)
diff --git a/docs/api/exports/protos/variables/StreamProtectionStatus.md b/docs/api/exports/protos/variables/StreamProtectionStatus.md
new file mode 100644
index 0000000..6293dc1
--- /dev/null
+++ b/docs/api/exports/protos/variables/StreamProtectionStatus.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / StreamProtectionStatus
+
+# Variable: StreamProtectionStatus
+
+> **StreamProtectionStatus**: `MessageFns`\<[`StreamProtectionStatus`](../interfaces/StreamProtectionStatus.md)\>
+
+Defined in: [protos/generated/video\_streaming/stream\_protection\_status.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/stream_protection_status.ts#L12)
diff --git a/docs/api/exports/protos/variables/StreamerContext.md b/docs/api/exports/protos/variables/StreamerContext.md
new file mode 100644
index 0000000..c20112d
--- /dev/null
+++ b/docs/api/exports/protos/variables/StreamerContext.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / StreamerContext
+
+# Variable: StreamerContext
+
+> **StreamerContext**: `MessageFns`\<[`StreamerContext`](../interfaces/StreamerContext.md)\>
+
+Defined in: [protos/generated/video\_streaming/streamer\_context.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/streamer_context.ts#L12)
diff --git a/docs/api/exports/protos/variables/UstreamerFlags.md b/docs/api/exports/protos/variables/UstreamerFlags.md
new file mode 100644
index 0000000..d167da8
--- /dev/null
+++ b/docs/api/exports/protos/variables/UstreamerFlags.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / UstreamerFlags
+
+# Variable: UstreamerFlags
+
+> **UstreamerFlags**: `MessageFns`\<[`UstreamerFlags`](../interfaces/UstreamerFlags.md)\>
+
+Defined in: [protos/generated/video\_streaming/innertube\_request.ts:27](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/innertube_request.ts#L27)
diff --git a/docs/api/exports/protos/variables/VideoPlaybackAbrRequest.md b/docs/api/exports/protos/variables/VideoPlaybackAbrRequest.md
new file mode 100644
index 0000000..29d9ea6
--- /dev/null
+++ b/docs/api/exports/protos/variables/VideoPlaybackAbrRequest.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/protos](../README.md) / VideoPlaybackAbrRequest
+
+# Variable: VideoPlaybackAbrRequest
+
+> **VideoPlaybackAbrRequest**: `MessageFns`\<[`VideoPlaybackAbrRequest`](../interfaces/VideoPlaybackAbrRequest.md)\>
+
+Defined in: [protos/generated/video\_streaming/video\_playback\_abr\_request.ts:17](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/protos/generated/video_streaming/video_playback_abr_request.ts#L17)
diff --git a/docs/api/exports/sabr-stream/README.md b/docs/api/exports/sabr-stream/README.md
new file mode 100644
index 0000000..925d947
--- /dev/null
+++ b/docs/api/exports/sabr-stream/README.md
@@ -0,0 +1,14 @@
+[googlevideo](../../README.md) / exports/sabr-stream
+
+# exports/sabr-stream
+
+## Classes
+
+- [SabrStream](classes/SabrStream.md)
+
+## Interfaces
+
+- [InitializedFormat](interfaces/InitializedFormat.md)
+- [SabrPlaybackOptions](interfaces/SabrPlaybackOptions.md)
+- [SabrStreamConfig](interfaces/SabrStreamConfig.md)
+- [SabrStreamState](interfaces/SabrStreamState.md)
diff --git a/docs/api/exports/sabr-stream/classes/SabrStream.md b/docs/api/exports/sabr-stream/classes/SabrStream.md
new file mode 100644
index 0000000..83b99f2
--- /dev/null
+++ b/docs/api/exports/sabr-stream/classes/SabrStream.md
@@ -0,0 +1,675 @@
+[googlevideo](../../../README.md) / [exports/sabr-stream](../README.md) / SabrStream
+
+# Class: SabrStream
+
+Defined in: [src/core/SabrStream.ts:106](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStream.ts#L106)
+
+Manages the download and processing of YouTube's Server-Adaptive Bitrate (SABR) streams.
+
+This class handles the entire lifecycle of a SABR stream:
+- Selecting appropriate video and audio formats.
+- Making network requests to fetch media segments.
+- Processing UMP parts in real-time.
+- Handling server-side directives like redirects, context updates, and backoff policies.
+- Emitting events for key stream updates, such as format initialization and errors.
+- Providing separate `ReadableStream` instances for video and audio data.
+
+## Extends
+
+- [`EventEmitterLike`](../../utils/classes/EventEmitterLike.md)
+
+## Constructors
+
+### Constructor
+
+> **new SabrStream**(`config`): `SabrStream`
+
+Defined in: [src/core/SabrStream.ts:194](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStream.ts#L194)
+
+#### Parameters
+
+##### config
+
+[`SabrStreamConfig`](../interfaces/SabrStreamConfig.md) = `{}`
+
+#### Returns
+
+`SabrStream`
+
+#### Overrides
+
+[`EventEmitterLike`](../../utils/classes/EventEmitterLike.md).[`constructor`](../../utils/classes/EventEmitterLike.md#constructor)
+
+## Methods
+
+### abort()
+
+> **abort**(): `void`
+
+Defined in: [src/core/SabrStream.ts:270](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStream.ts#L270)
+
+Aborts the download process, closing all streams and cleaning up resources.
+Emits an 'abort' event.
+
+#### Returns
+
+`void`
+
+***
+
+### addEventListener()
+
+> **addEventListener**(`type`, `callback`, `options?`): `void`
+
+Defined in: node\_modules/typescript/lib/lib.dom.d.ts:8303
+
+Appends an event listener for events whose type attribute value is type. The callback argument sets the callback that will be invoked when the event is dispatched.
+
+The options argument sets listener-specific options. For compatibility this can be a boolean, in which case the method behaves exactly as if the value was specified as options's capture.
+
+When set to true, options's capture prevents callback from being invoked when the event's eventPhase attribute value is BUBBLING_PHASE. When false (or not present), callback will not be invoked when event's eventPhase attribute value is CAPTURING_PHASE. Either way, callback will be invoked if event's eventPhase attribute value is AT_TARGET.
+
+When set to true, options's passive indicates that the callback will not cancel the event by invoking preventDefault(). This is used to enable performance optimizations described in § 2.8 Observing event listeners.
+
+When set to true, options's once indicates that the callback will only be invoked once after which the event listener will be removed.
+
+If an AbortSignal is passed for options's signal, then the event listener will be removed when signal is aborted.
+
+The event listener is appended to target's event listener list and is not appended if it has the same type, callback, and capture.
+
+[MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener)
+
+#### Parameters
+
+##### type
+
+`string`
+
+##### callback
+
+`null` | `EventListenerOrEventListenerObject`
+
+##### options?
+
+`boolean` | `AddEventListenerOptions`
+
+#### Returns
+
+`void`
+
+#### Inherited from
+
+[`EventEmitterLike`](../../utils/classes/EventEmitterLike.md).[`addEventListener`](../../utils/classes/EventEmitterLike.md#addeventlistener)
+
+***
+
+### dispatchEvent()
+
+> **dispatchEvent**(`event`): `boolean`
+
+Defined in: node\_modules/typescript/lib/lib.dom.d.ts:8309
+
+Dispatches a synthetic event event to target and returns true if either event's cancelable attribute value is false or its preventDefault() method was not invoked, and false otherwise.
+
+[MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/dispatchEvent)
+
+#### Parameters
+
+##### event
+
+`Event`
+
+#### Returns
+
+`boolean`
+
+#### Inherited from
+
+[`EventEmitterLike`](../../utils/classes/EventEmitterLike.md).[`dispatchEvent`](../../utils/classes/EventEmitterLike.md#dispatchevent)
+
+***
+
+### emit()
+
+> **emit**(`type`, ...`args`): `void`
+
+Defined in: [src/utils/EventEmitterLike.ts:29](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/EventEmitterLike.ts#L29)
+
+#### Parameters
+
+##### type
+
+`string`
+
+##### args
+
+...`any`[]
+
+#### Returns
+
+`void`
+
+#### Inherited from
+
+[`EventEmitterLike`](../../utils/classes/EventEmitterLike.md).[`emit`](../../utils/classes/EventEmitterLike.md#emit)
+
+***
+
+### getState()
+
+> **getState**(): [`SabrStreamState`](../interfaces/SabrStreamState.md)
+
+Defined in: [src/core/SabrStream.ts:292](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStream.ts#L292)
+
+Returns a serializable state object that can be used to restore the stream later.
+
+#### Returns
+
+[`SabrStreamState`](../interfaces/SabrStreamState.md)
+
+The current state of the stream.
+
+#### Throws
+
+If the main format is not initialized.
+
+***
+
+### off()
+
+> **off**(`type`, `listener`): `void`
+
+Defined in: [src/utils/EventEmitterLike.ts:59](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/EventEmitterLike.ts#L59)
+
+#### Parameters
+
+##### type
+
+`string`
+
+##### listener
+
+(...`args`) => `void`
+
+#### Returns
+
+`void`
+
+#### Inherited from
+
+[`EventEmitterLike`](../../utils/classes/EventEmitterLike.md).[`off`](../../utils/classes/EventEmitterLike.md#off)
+
+***
+
+### once()
+
+#### Call Signature
+
+> **once**(`event`, `listener`): `void`
+
+Defined in: [src/core/SabrStream.ts:185](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStream.ts#L185)
+
+##### Parameters
+
+###### event
+
+`"formatInitialization"`
+
+###### listener
+
+(`initializedFormat`) => `void`
+
+##### Returns
+
+`void`
+
+##### Overrides
+
+[`EventEmitterLike`](../../utils/classes/EventEmitterLike.md).[`once`](../../utils/classes/EventEmitterLike.md#once)
+
+#### Call Signature
+
+> **once**(`event`, `listener`): `void`
+
+Defined in: [src/core/SabrStream.ts:186](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStream.ts#L186)
+
+##### Parameters
+
+###### event
+
+`"streamProtectionStatusUpdate"`
+
+###### listener
+
+(`data`) => `void`
+
+##### Returns
+
+`void`
+
+##### Overrides
+
+`EventEmitterLike.once`
+
+#### Call Signature
+
+> **once**(`event`, `listener`): `void`
+
+Defined in: [src/core/SabrStream.ts:187](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStream.ts#L187)
+
+##### Parameters
+
+###### event
+
+`"reloadPlayerResponse"`
+
+###### listener
+
+(`reloadPlaybackContext`) => `void`
+
+##### Returns
+
+`void`
+
+##### Overrides
+
+`EventEmitterLike.once`
+
+#### Call Signature
+
+> **once**(`event`, `listener`): `void`
+
+Defined in: [src/core/SabrStream.ts:188](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStream.ts#L188)
+
+##### Parameters
+
+###### event
+
+`"finish"`
+
+###### listener
+
+() => `void`
+
+##### Returns
+
+`void`
+
+##### Overrides
+
+`EventEmitterLike.once`
+
+#### Call Signature
+
+> **once**(`event`, `listener`): `void`
+
+Defined in: [src/core/SabrStream.ts:189](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStream.ts#L189)
+
+##### Parameters
+
+###### event
+
+`"abort"`
+
+###### listener
+
+() => `void`
+
+##### Returns
+
+`void`
+
+##### Overrides
+
+`EventEmitterLike.once`
+
+***
+
+### removeAllListeners()
+
+> **removeAllListeners**(`type?`): `void`
+
+Defined in: [src/utils/EventEmitterLike.ts:67](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/EventEmitterLike.ts#L67)
+
+#### Parameters
+
+##### type?
+
+`string`
+
+#### Returns
+
+`void`
+
+#### Inherited from
+
+[`EventEmitterLike`](../../utils/classes/EventEmitterLike.md).[`removeAllListeners`](../../utils/classes/EventEmitterLike.md#removealllisteners)
+
+***
+
+### removeEventListener()
+
+> **removeEventListener**(`type`, `callback`, `options?`): `void`
+
+Defined in: node\_modules/typescript/lib/lib.dom.d.ts:8315
+
+Removes the event listener in target's event listener list with the same type, callback, and options.
+
+[MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/removeEventListener)
+
+#### Parameters
+
+##### type
+
+`string`
+
+##### callback
+
+`null` | `EventListenerOrEventListenerObject`
+
+##### options?
+
+`boolean` | `EventListenerOptions`
+
+#### Returns
+
+`void`
+
+#### Inherited from
+
+[`EventEmitterLike`](../../utils/classes/EventEmitterLike.md).[`removeEventListener`](../../utils/classes/EventEmitterLike.md#removeeventlistener)
+
+***
+
+### setClientInfo()
+
+> **setClientInfo**(`clientInfo`): `void`
+
+Defined in: [src/core/SabrStream.ts:262](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStream.ts#L262)
+
+Sets the client information used in SABR requests.
+
+#### Parameters
+
+##### clientInfo
+
+[`ClientInfo`](../../protos/interfaces/ClientInfo.md)
+
+The client information object.
+
+#### Returns
+
+`void`
+
+***
+
+### setDurationMs()
+
+> **setDurationMs**(`durationMs`): `void`
+
+Defined in: [src/core/SabrStream.ts:238](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStream.ts#L238)
+
+Sets the total duration of the stream in milliseconds.
+This is optional as duration is often determined automatically from format metadata.
+
+#### Parameters
+
+##### durationMs
+
+`number`
+
+The duration in milliseconds.
+
+#### Returns
+
+`void`
+
+***
+
+### setPoToken()
+
+> **setPoToken**(`poToken`): `void`
+
+Defined in: [src/core/SabrStream.ts:221](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStream.ts#L221)
+
+Sets Proof of Origin (PO) token.
+
+#### Parameters
+
+##### poToken
+
+`string`
+
+The base64-encoded token string.
+
+#### Returns
+
+`void`
+
+***
+
+### setServerAbrFormats()
+
+> **setServerAbrFormats**(`formats`): `void`
+
+Defined in: [src/core/SabrStream.ts:229](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStream.ts#L229)
+
+Sets the available server ABR formats.
+
+#### Parameters
+
+##### formats
+
+[`SabrFormat`](../../../types/shared/interfaces/SabrFormat.md)[]
+
+An array of available SabrFormat objects.
+
+#### Returns
+
+`void`
+
+***
+
+### setStreamingURL()
+
+> **setStreamingURL**(`url`): `void`
+
+Defined in: [src/core/SabrStream.ts:246](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStream.ts#L246)
+
+Sets the server ABR streaming URL for media requests.
+
+#### Parameters
+
+##### url
+
+`string`
+
+The streaming URL.
+
+#### Returns
+
+`void`
+
+***
+
+### setUstreamerConfig()
+
+> **setUstreamerConfig**(`config`): `void`
+
+Defined in: [src/core/SabrStream.ts:254](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStream.ts#L254)
+
+Sets the Ustreamer configuration string.
+
+#### Parameters
+
+##### config
+
+`string`
+
+The Ustreamer configuration.
+
+#### Returns
+
+`void`
+
+***
+
+### start()
+
+> **start**(`options`): `Promise`\<\{ `audioStream`: `ReadableStream`\<`Uint8Array`\>; `selectedFormats`: `SelectedFormats`; `videoStream`: `ReadableStream`\<`Uint8Array`\>; \}\>
+
+Defined in: [src/core/SabrStream.ts:327](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStream.ts#L327)
+
+Initiates the streaming process for the selected formats.
+
+#### Parameters
+
+##### options
+
+[`SabrPlaybackOptions`](../interfaces/SabrPlaybackOptions.md)
+
+Playback options, including format preferences and initial state.
+
+#### Returns
+
+`Promise`\<\{ `audioStream`: `ReadableStream`\<`Uint8Array`\>; `selectedFormats`: `SelectedFormats`; `videoStream`: `ReadableStream`\<`Uint8Array`\>; \}\>
+
+A promise that resolves with the video/audio streams and selected formats.
+
+#### Throws
+
+If no suitable formats are found or streaming fails.
+
+## Events
+
+### on()
+
+#### Call Signature
+
+> **on**(`event`, `listener`): `void`
+
+Defined in: [src/core/SabrStream.ts:160](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStream.ts#L160)
+
+Fired when the server sends initialization metadata for a media format.
+
+##### Parameters
+
+###### event
+
+`"formatInitialization"`
+
+###### listener
+
+(`initializedFormat`) => `void`
+
+##### Returns
+
+`void`
+
+##### Overrides
+
+[`EventEmitterLike`](../../utils/classes/EventEmitterLike.md).[`on`](../../utils/classes/EventEmitterLike.md#on)
+
+#### Call Signature
+
+> **on**(`event`, `listener`): `void`
+
+Defined in: [src/core/SabrStream.ts:165](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStream.ts#L165)
+
+Fired when the server provides an update on the stream's content protection status.
+
+##### Parameters
+
+###### event
+
+`"streamProtectionStatusUpdate"`
+
+###### listener
+
+(`data`) => `void`
+
+##### Returns
+
+`void`
+
+##### Overrides
+
+`EventEmitterLike.on`
+
+#### Call Signature
+
+> **on**(`event`, `listener`): `void`
+
+Defined in: [src/core/SabrStream.ts:170](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStream.ts#L170)
+
+Fired when the server directs the client to reload the player, usually indicating the current session is invalid.
+
+##### Parameters
+
+###### event
+
+`"reloadPlayerResponse"`
+
+###### listener
+
+(`reloadPlaybackContext`) => `void`
+
+##### Returns
+
+`void`
+
+##### Overrides
+
+`EventEmitterLike.on`
+
+#### Call Signature
+
+> **on**(`event`, `listener`): `void`
+
+Defined in: [src/core/SabrStream.ts:175](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStream.ts#L175)
+
+Fired when the entire stream has been successfully downloaded.
+
+##### Parameters
+
+###### event
+
+`"finish"`
+
+###### listener
+
+() => `void`
+
+##### Returns
+
+`void`
+
+##### Overrides
+
+`EventEmitterLike.on`
+
+#### Call Signature
+
+> **on**(`event`, `listener`): `void`
+
+Defined in: [src/core/SabrStream.ts:180](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStream.ts#L180)
+
+Fired when the download process is manually aborted via the `abort()` method.
+
+##### Parameters
+
+###### event
+
+`"abort"`
+
+###### listener
+
+() => `void`
+
+##### Returns
+
+`void`
+
+##### Overrides
+
+`EventEmitterLike.on`
diff --git a/docs/api/exports/sabr-stream/interfaces/InitializedFormat.md b/docs/api/exports/sabr-stream/interfaces/InitializedFormat.md
new file mode 100644
index 0000000..33fa778
--- /dev/null
+++ b/docs/api/exports/sabr-stream/interfaces/InitializedFormat.md
@@ -0,0 +1,29 @@
+[googlevideo](../../../README.md) / [exports/sabr-stream](../README.md) / InitializedFormat
+
+# Interface: InitializedFormat
+
+Defined in: [src/core/SabrStream.ts:53](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStream.ts#L53)
+
+## Properties
+
+### downloadedSegments
+
+> **downloadedSegments**: `Map`\<`number`, `Segment`\>
+
+Defined in: [src/core/SabrStream.ts:55](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStream.ts#L55)
+
+***
+
+### formatInitializationMetadata
+
+> **formatInitializationMetadata**: [`FormatInitializationMetadata`](../../protos/interfaces/FormatInitializationMetadata.md)
+
+Defined in: [src/core/SabrStream.ts:54](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStream.ts#L54)
+
+***
+
+### lastMediaHeaders
+
+> **lastMediaHeaders**: [`MediaHeader`](../../protos/interfaces/MediaHeader.md)[]
+
+Defined in: [src/core/SabrStream.ts:56](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStream.ts#L56)
diff --git a/docs/api/exports/sabr-stream/interfaces/SabrPlaybackOptions.md b/docs/api/exports/sabr-stream/interfaces/SabrPlaybackOptions.md
new file mode 100644
index 0000000..6d72e1c
--- /dev/null
+++ b/docs/api/exports/sabr-stream/interfaces/SabrPlaybackOptions.md
@@ -0,0 +1,153 @@
+[googlevideo](../../../README.md) / [exports/sabr-stream](../README.md) / SabrPlaybackOptions
+
+# Interface: SabrPlaybackOptions
+
+Defined in: [src/types/sabrStreamTypes.ts:48](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamTypes.ts#L48)
+
+## Properties
+
+### audioFormat?
+
+> `optional` **audioFormat**: `number` \| [`SabrFormat`](../../../types/shared/interfaces/SabrFormat.md) \| (`formats`) => `undefined` \| [`SabrFormat`](../../../types/shared/interfaces/SabrFormat.md)
+
+Defined in: [src/types/sabrStreamTypes.ts:59](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamTypes.ts#L59)
+
+Audio format selection, can be a format ID number, a SabrFormat object,
+or a function that selects a format from the available formats array.
+
+***
+
+### audioLanguage?
+
+> `optional` **audioLanguage**: `string`
+
+Defined in: [src/types/sabrStreamTypes.ts:79](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamTypes.ts#L79)
+
+Preferred audio language code.
+
+***
+
+### audioQuality?
+
+> `optional` **audioQuality**: `string`
+
+Defined in: [src/types/sabrStreamTypes.ts:69](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamTypes.ts#L69)
+
+Preferred audio quality (e.g., "high", "medium").
+
+***
+
+### enabledTrackTypes?
+
+> `optional` **enabledTrackTypes**: [`EnabledTrackTypes`](../../utils/enumerations/EnabledTrackTypes.md)
+
+Defined in: [src/types/sabrStreamTypes.ts:117](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamTypes.ts#L117)
+
+Enabled track types for streaming (audio only, video only, or both).
+
+#### See
+
+EnabledTrackTypes
+
+***
+
+### maxRetries?
+
+> `optional` **maxRetries**: `number`
+
+Defined in: [src/types/sabrStreamTypes.ts:105](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamTypes.ts#L105)
+
+Maximum number of retry attempts when fetching segments.
+Default is 10.
+
+***
+
+### preferH264?
+
+> `optional` **preferH264**: `boolean`
+
+Defined in: [src/types/sabrStreamTypes.ts:94](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamTypes.ts#L94)
+
+Whether to prefer H.264 video codec.
+
+***
+
+### preferMP4?
+
+> `optional` **preferMP4**: `boolean`
+
+Defined in: [src/types/sabrStreamTypes.ts:89](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamTypes.ts#L89)
+
+Whether to prefer MP4 container format.
+
+***
+
+### preferOpus?
+
+> `optional` **preferOpus**: `boolean`
+
+Defined in: [src/types/sabrStreamTypes.ts:99](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamTypes.ts#L99)
+
+Whether to prefer Opus audio codec.
+
+***
+
+### preferWebM?
+
+> `optional` **preferWebM**: `boolean`
+
+Defined in: [src/types/sabrStreamTypes.ts:84](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamTypes.ts#L84)
+
+Whether to prefer WebM container format.
+
+***
+
+### stallDetectionMs?
+
+> `optional` **stallDetectionMs**: `number`
+
+Defined in: [src/types/sabrStreamTypes.ts:111](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamTypes.ts#L111)
+
+Duration in milliseconds after which a stall is detected if no progress is made.
+Default is 30000 (30 seconds).
+
+***
+
+### state?
+
+> `optional` **state**: [`SabrStreamState`](SabrStreamState.md)
+
+Defined in: [src/types/sabrStreamTypes.ts:122](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamTypes.ts#L122)
+
+Previously saved state to resume a download.
+
+***
+
+### videoFormat?
+
+> `optional` **videoFormat**: `number` \| [`SabrFormat`](../../../types/shared/interfaces/SabrFormat.md) \| (`formats`) => `undefined` \| [`SabrFormat`](../../../types/shared/interfaces/SabrFormat.md)
+
+Defined in: [src/types/sabrStreamTypes.ts:53](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamTypes.ts#L53)
+
+Video format selection, can be a format ID number, a SabrFormat object,
+or a function that selects a format from the available formats array.
+
+***
+
+### videoLanguage?
+
+> `optional` **videoLanguage**: `string`
+
+Defined in: [src/types/sabrStreamTypes.ts:74](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamTypes.ts#L74)
+
+Preferred video language code.
+
+***
+
+### videoQuality?
+
+> `optional` **videoQuality**: `string`
+
+Defined in: [src/types/sabrStreamTypes.ts:64](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamTypes.ts#L64)
+
+Preferred video quality (e.g., "1080p", "720p").
diff --git a/docs/api/exports/sabr-stream/interfaces/SabrStreamConfig.md b/docs/api/exports/sabr-stream/interfaces/SabrStreamConfig.md
new file mode 100644
index 0000000..59ea0eb
--- /dev/null
+++ b/docs/api/exports/sabr-stream/interfaces/SabrStreamConfig.md
@@ -0,0 +1,120 @@
+[googlevideo](../../../README.md) / [exports/sabr-stream](../README.md) / SabrStreamConfig
+
+# Interface: SabrStreamConfig
+
+Defined in: [src/types/sabrStreamTypes.ts:6](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamTypes.ts#L6)
+
+## Properties
+
+### clientInfo?
+
+> `optional` **clientInfo**: [`ClientInfo`](../../protos/interfaces/ClientInfo.md)
+
+Defined in: [src/types/sabrStreamTypes.ts:29](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamTypes.ts#L29)
+
+Client information used to identify the requesting device/app.
+Contains details like client name, version, and capabilities.
+
+***
+
+### durationMs?
+
+> `optional` **durationMs**: `number`
+
+Defined in: [src/types/sabrStreamTypes.ts:40](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamTypes.ts#L40)
+
+Total duration of the media content in milliseconds.
+If not provided, will be determined from format metadata.
+
+***
+
+### fetch()?
+
+> `optional` **fetch**: \{(`input`, `init?`): `Promise`\<`Response`\>; (`input`, `init?`): `Promise`\<`Response`\>; \}
+
+Defined in: [src/types/sabrStreamTypes.ts:11](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamTypes.ts#L11)
+
+Custom fetch implementation to use for HTTP requests.
+If not provided, the global `fetch` function will be used.
+
+#### Call Signature
+
+> (`input`, `init?`): `Promise`\<`Response`\>
+
+[MDN Reference](https://developer.mozilla.org/docs/Web/API/fetch)
+
+##### Parameters
+
+###### input
+
+`URL` | `RequestInfo`
+
+###### init?
+
+`RequestInit`
+
+##### Returns
+
+`Promise`\<`Response`\>
+
+#### Call Signature
+
+> (`input`, `init?`): `Promise`\<`Response`\>
+
+[MDN Reference](https://developer.mozilla.org/docs/Web/API/fetch)
+
+##### Parameters
+
+###### input
+
+`string` | `URL` | `Request`
+
+###### init?
+
+`RequestInit`
+
+##### Returns
+
+`Promise`\<`Response`\>
+
+***
+
+### formats?
+
+> `optional` **formats**: [`SabrFormat`](../../../types/shared/interfaces/SabrFormat.md)[]
+
+Defined in: [src/types/sabrStreamTypes.ts:45](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamTypes.ts#L45)
+
+Array of available streaming formats obtained from the player response.
+
+***
+
+### poToken?
+
+> `optional` **poToken**: `string`
+
+Defined in: [src/types/sabrStreamTypes.ts:34](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamTypes.ts#L34)
+
+Proof of Origin token for content protection verification.
+
+***
+
+### serverAbrStreamingUrl?
+
+> `optional` **serverAbrStreamingUrl**: `string`
+
+Defined in: [src/types/sabrStreamTypes.ts:17](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamTypes.ts#L17)
+
+The URL endpoint for server-side ABR streaming requests.
+This is typically obtained from the initial player response.
+
+***
+
+### videoPlaybackUstreamerConfig?
+
+> `optional` **videoPlaybackUstreamerConfig**: `string`
+
+Defined in: [src/types/sabrStreamTypes.ts:23](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamTypes.ts#L23)
+
+Base64-encoded Ustreamer configuration obtained from the player response.
+Required for authorizing and configuring the streaming session.
diff --git a/docs/api/exports/sabr-stream/interfaces/SabrStreamState.md b/docs/api/exports/sabr-stream/interfaces/SabrStreamState.md
new file mode 100644
index 0000000..b7bfe56
--- /dev/null
+++ b/docs/api/exports/sabr-stream/interfaces/SabrStreamState.md
@@ -0,0 +1,93 @@
+[googlevideo](../../../README.md) / [exports/sabr-stream](../README.md) / SabrStreamState
+
+# Interface: SabrStreamState
+
+Defined in: [src/core/SabrStream.ts:59](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStream.ts#L59)
+
+## Properties
+
+### activeSabrContexts
+
+> **activeSabrContexts**: `number`[]
+
+Defined in: [src/core/SabrStream.ts:63](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStream.ts#L63)
+
+***
+
+### cachedBufferedRanges
+
+> **cachedBufferedRanges**: [`BufferedRange`](../../protos/interfaces/BufferedRange.md)[]
+
+Defined in: [src/core/SabrStream.ts:66](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStream.ts#L66)
+
+***
+
+### durationMs
+
+> **durationMs**: `number`
+
+Defined in: [src/core/SabrStream.ts:60](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStream.ts#L60)
+
+***
+
+### formatToDiscard?
+
+> `optional` **formatToDiscard**: `string`
+
+Defined in: [src/core/SabrStream.ts:65](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStream.ts#L65)
+
+***
+
+### initializedFormats
+
+> **initializedFormats**: `object`[]
+
+Defined in: [src/core/SabrStream.ts:68](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStream.ts#L68)
+
+#### downloadedSegments
+
+> **downloadedSegments**: \[`number`, `Segment`\][]
+
+#### formatInitializationMetadata
+
+> **formatInitializationMetadata**: [`FormatInitializationMetadata`](../../protos/interfaces/FormatInitializationMetadata.md)
+
+#### formatKey
+
+> **formatKey**: `string`
+
+#### lastMediaHeaders
+
+> **lastMediaHeaders**: [`MediaHeader`](../../protos/interfaces/MediaHeader.md)[]
+
+***
+
+### nextRequestPolicy?
+
+> `optional` **nextRequestPolicy**: [`NextRequestPolicy`](../../protos/interfaces/NextRequestPolicy.md)
+
+Defined in: [src/core/SabrStream.ts:67](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStream.ts#L67)
+
+***
+
+### playerTimeMs
+
+> **playerTimeMs**: `number`
+
+Defined in: [src/core/SabrStream.ts:62](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStream.ts#L62)
+
+***
+
+### requestNumber
+
+> **requestNumber**: `number`
+
+Defined in: [src/core/SabrStream.ts:61](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStream.ts#L61)
+
+***
+
+### sabrContextUpdates
+
+> **sabrContextUpdates**: \[`number`, [`SabrContextUpdate`](../../protos/interfaces/SabrContextUpdate.md)\][]
+
+Defined in: [src/core/SabrStream.ts:64](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStream.ts#L64)
diff --git a/docs/api/exports/sabr-streaming-adapter/README.md b/docs/api/exports/sabr-streaming-adapter/README.md
new file mode 100644
index 0000000..b86616b
--- /dev/null
+++ b/docs/api/exports/sabr-streaming-adapter/README.md
@@ -0,0 +1,27 @@
+[googlevideo](../../README.md) / exports/sabr-streaming-adapter
+
+# exports/sabr-streaming-adapter
+
+## Classes
+
+- [SabrStreamingAdapter](classes/SabrStreamingAdapter.md)
+- [SabrUmpProcessor](classes/SabrUmpProcessor.md)
+
+## Interfaces
+
+- [PlayerHttpRequest](interfaces/PlayerHttpRequest.md)
+- [PlayerHttpResponse](interfaces/PlayerHttpResponse.md)
+- [RequestSegment](interfaces/RequestSegment.md)
+- [SabrOptions](interfaces/SabrOptions.md)
+- [SabrPlayerAdapter](interfaces/SabrPlayerAdapter.md)
+- [SabrRequestMetadata](interfaces/SabrRequestMetadata.md)
+- [UmpProcessingResult](interfaces/UmpProcessingResult.md)
+
+## Type Aliases
+
+- [RequestFilter](type-aliases/RequestFilter.md)
+- [ResponseFilter](type-aliases/ResponseFilter.md)
+
+## Variables
+
+- [SABR\_CONSTANTS](variables/SABR_CONSTANTS.md)
diff --git a/docs/api/exports/sabr-streaming-adapter/classes/SabrStreamingAdapter.md b/docs/api/exports/sabr-streaming-adapter/classes/SabrStreamingAdapter.md
new file mode 100644
index 0000000..90aa20e
--- /dev/null
+++ b/docs/api/exports/sabr-streaming-adapter/classes/SabrStreamingAdapter.md
@@ -0,0 +1,229 @@
+[googlevideo](../../../README.md) / [exports/sabr-streaming-adapter](../README.md) / SabrStreamingAdapter
+
+# Class: SabrStreamingAdapter
+
+Defined in: [src/core/SabrStreamingAdapter.ts:74](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStreamingAdapter.ts#L74)
+
+Adapter class that handles YouTube SABR integration with media players (e.g., Shaka Player).
+
+What it does:
+- Sets up request/response interceptors so we can send proper SABR requests (UMP response parsing must be done in the player adapter).
+- Keeps track of initialized formats and their metadata.
+- Handles SABR-specific things, such as redirects, context updates, and playback cookies.
+
+## Constructors
+
+### Constructor
+
+> **new SabrStreamingAdapter**(`options`): `SabrStreamingAdapter`
+
+Defined in: [src/core/SabrStreamingAdapter.ts:125](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStreamingAdapter.ts#L125)
+
+#### Parameters
+
+##### options
+
+[`SabrOptions`](../interfaces/SabrOptions.md)
+
+Configuration options for the adapter.
+
+#### Returns
+
+`SabrStreamingAdapter`
+
+#### Throws
+
+SabrAdapterError if a player adapter is not provided.
+
+## Properties
+
+### isDisposed
+
+> **isDisposed**: `boolean` = `false`
+
+Defined in: [src/core/SabrStreamingAdapter.ts:96](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStreamingAdapter.ts#L96)
+
+## Methods
+
+### attach()
+
+> **attach**(`player`): `void`
+
+Defined in: [src/core/SabrStreamingAdapter.ts:149](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStreamingAdapter.ts#L149)
+
+Initializes the player adapter and sets up request/response interceptors.
+
+#### Parameters
+
+##### player
+
+`any`
+
+#### Returns
+
+`void`
+
+#### Throws
+
+SabrAdapterError if the adapter has been disposed.
+
+***
+
+### dispose()
+
+> **dispose**(): `void`
+
+Defined in: [src/core/SabrStreamingAdapter.ts:624](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStreamingAdapter.ts#L624)
+
+Releases resources and cleans up the adapter instance.
+After calling dispose, the adapter can no longer be used.
+
+#### Returns
+
+`void`
+
+***
+
+### getCacheManager()
+
+> **getCacheManager**(): `null` \| [`CacheManager`](../../utils/classes/CacheManager.md)
+
+Defined in: [src/core/SabrStreamingAdapter.ts:185](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStreamingAdapter.ts#L185)
+
+Returns the cache manager instance, if caching is enabled.
+
+#### Returns
+
+`null` \| [`CacheManager`](../../utils/classes/CacheManager.md)
+
+***
+
+### onMintPoToken()
+
+> **onMintPoToken**(`cb`): `void`
+
+Defined in: [src/core/SabrStreamingAdapter.ts:117](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStreamingAdapter.ts#L117)
+
+Registers a callback function to mint a new PoToken.
+
+#### Parameters
+
+##### cb
+
+`OnMintPoTokenCallback`
+
+#### Returns
+
+`void`
+
+***
+
+### onReloadPlayerResponse()
+
+> **onReloadPlayerResponse**(`cb`): `void`
+
+Defined in: [src/core/SabrStreamingAdapter.ts:109](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStreamingAdapter.ts#L109)
+
+Handles server requests to reload the player with new parameters.
+
+#### Parameters
+
+##### cb
+
+`OnReloadPlayerResponseCb`
+
+#### Returns
+
+`void`
+
+***
+
+### onSnackbarMessage()
+
+> **onSnackbarMessage**(`cb`): `void`
+
+Defined in: [src/core/SabrStreamingAdapter.ts:101](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStreamingAdapter.ts#L101)
+
+Registers a callback function to handle snackbar messages.
+
+#### Parameters
+
+##### cb
+
+`OnSnackbarMessageCb`
+
+#### Returns
+
+`void`
+
+***
+
+### setServerAbrFormats()
+
+> **setServerAbrFormats**(`sabrFormats`): `void`
+
+Defined in: [src/core/SabrStreamingAdapter.ts:177](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStreamingAdapter.ts#L177)
+
+Sets the available SABR formats for streaming.
+
+#### Parameters
+
+##### sabrFormats
+
+[`SabrFormat`](../../../types/shared/interfaces/SabrFormat.md)[]
+
+#### Returns
+
+`void`
+
+#### Throws
+
+SabrAdapterError if the adapter has been disposed.
+
+***
+
+### setStreamingURL()
+
+> **setStreamingURL**(`url?`): `void`
+
+Defined in: [src/core/SabrStreamingAdapter.ts:159](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStreamingAdapter.ts#L159)
+
+Sets the initial server abr streaming URL.
+
+#### Parameters
+
+##### url?
+
+`string`
+
+#### Returns
+
+`void`
+
+#### Throws
+
+SabrAdapterError if the adapter has been disposed.
+
+***
+
+### setUstreamerConfig()
+
+> **setUstreamerConfig**(`ustreamerConfig?`): `void`
+
+Defined in: [src/core/SabrStreamingAdapter.ts:168](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStreamingAdapter.ts#L168)
+
+Sets the ustreamer configuration for SABR requests.
+
+#### Parameters
+
+##### ustreamerConfig?
+
+`string`
+
+#### Returns
+
+`void`
+
+#### Throws
+
+SabrAdapterError if the adapter has been disposed.
diff --git a/docs/api/exports/sabr-streaming-adapter/classes/SabrUmpProcessor.md b/docs/api/exports/sabr-streaming-adapter/classes/SabrUmpProcessor.md
new file mode 100644
index 0000000..1b277ea
--- /dev/null
+++ b/docs/api/exports/sabr-streaming-adapter/classes/SabrUmpProcessor.md
@@ -0,0 +1,75 @@
+[googlevideo](../../../README.md) / [exports/sabr-streaming-adapter](../README.md) / SabrUmpProcessor
+
+# Class: SabrUmpProcessor
+
+Defined in: [src/core/SabrUmpProcessor.ts:47](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrUmpProcessor.ts#L47)
+
+This class is responsible for reading a UMP stream, handling different part types
+(like media headers, media data, and server directives), and populating a
+metadata object with the extracted information. It is supposed to be used
+in conjunction with a [`SabrPlayerAdapter`](../interfaces/SabrPlayerAdapter.md) in video player
+implementations.
+
+## Constructors
+
+### Constructor
+
+> **new SabrUmpProcessor**(`requestMetadata`, `cacheManager?`): `SabrUmpProcessor`
+
+Defined in: [src/core/SabrUmpProcessor.ts:68](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrUmpProcessor.ts#L68)
+
+#### Parameters
+
+##### requestMetadata
+
+[`SabrRequestMetadata`](../interfaces/SabrRequestMetadata.md)
+
+##### cacheManager?
+
+[`CacheManager`](../../utils/classes/CacheManager.md)
+
+#### Returns
+
+`SabrUmpProcessor`
+
+## Properties
+
+### partialPart?
+
+> `optional` **partialPart**: [`Part`](../../../types/shared/type-aliases/Part.md)
+
+Defined in: [src/core/SabrUmpProcessor.ts:48](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrUmpProcessor.ts#L48)
+
+## Methods
+
+### getSegmentInfo()
+
+> **getSegmentInfo**(): `undefined` \| `Segment`
+
+Defined in: [src/core/SabrUmpProcessor.ts:106](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrUmpProcessor.ts#L106)
+
+#### Returns
+
+`undefined` \| `Segment`
+
+***
+
+### processChunk()
+
+> **processChunk**(`value`): `Promise`\<`undefined` \| [`UmpProcessingResult`](../interfaces/UmpProcessingResult.md)\>
+
+Defined in: [src/core/SabrUmpProcessor.ts:78](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrUmpProcessor.ts#L78)
+
+Processes a chunk of data from a UMP stream and updates the request context.
+
+#### Parameters
+
+##### value
+
+`Uint8Array`
+
+#### Returns
+
+`Promise`\<`undefined` \| [`UmpProcessingResult`](../interfaces/UmpProcessingResult.md)\>
+
+A promise that resolves with a processing result if a terminal part is found (e.g., MediaEnd), or undefined otherwise.
diff --git a/docs/api/exports/sabr-streaming-adapter/interfaces/PlayerHttpRequest.md b/docs/api/exports/sabr-streaming-adapter/interfaces/PlayerHttpRequest.md
new file mode 100644
index 0000000..81cd895
--- /dev/null
+++ b/docs/api/exports/sabr-streaming-adapter/interfaces/PlayerHttpRequest.md
@@ -0,0 +1,45 @@
+[googlevideo](../../../README.md) / [exports/sabr-streaming-adapter](../README.md) / PlayerHttpRequest
+
+# Interface: PlayerHttpRequest
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:83](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L83)
+
+## Properties
+
+### body?
+
+> `optional` **body**: `null` \| `ArrayBuffer` \| `ArrayBufferView`
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:88](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L88)
+
+***
+
+### headers
+
+> **headers**: `Record`\<`string`, `string`\>
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:86](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L86)
+
+***
+
+### method
+
+> **method**: `string`
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:85](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L85)
+
+***
+
+### segment
+
+> **segment**: [`RequestSegment`](RequestSegment.md)
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:87](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L87)
+
+***
+
+### url
+
+> **url**: `string`
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:84](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L84)
diff --git a/docs/api/exports/sabr-streaming-adapter/interfaces/PlayerHttpResponse.md b/docs/api/exports/sabr-streaming-adapter/interfaces/PlayerHttpResponse.md
new file mode 100644
index 0000000..a6c436f
--- /dev/null
+++ b/docs/api/exports/sabr-streaming-adapter/interfaces/PlayerHttpResponse.md
@@ -0,0 +1,59 @@
+[googlevideo](../../../README.md) / [exports/sabr-streaming-adapter](../README.md) / PlayerHttpResponse
+
+# Interface: PlayerHttpResponse
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:75](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L75)
+
+## Properties
+
+### data?
+
+> `optional` **data**: `ArrayBuffer` \| `ArrayBufferView`
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:79](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L79)
+
+***
+
+### headers
+
+> **headers**: `Record`\<`string`, `string`\>
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:78](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L78)
+
+***
+
+### makeRequest()
+
+> **makeRequest**: (`url`, `headers`) => `Promise`\<`Omit`\<`PlayerHttpResponse`, `"makeRequest"`\>\>
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:80](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L80)
+
+#### Parameters
+
+##### url
+
+`string`
+
+##### headers
+
+`Record`\<`string`, `string`\>
+
+#### Returns
+
+`Promise`\<`Omit`\<`PlayerHttpResponse`, `"makeRequest"`\>\>
+
+***
+
+### method
+
+> **method**: `string`
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:77](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L77)
+
+***
+
+### url
+
+> **url**: `string`
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:76](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L76)
diff --git a/docs/api/exports/sabr-streaming-adapter/interfaces/RequestSegment.md b/docs/api/exports/sabr-streaming-adapter/interfaces/RequestSegment.md
new file mode 100644
index 0000000..c9c99cc
--- /dev/null
+++ b/docs/api/exports/sabr-streaming-adapter/interfaces/RequestSegment.md
@@ -0,0 +1,29 @@
+[googlevideo](../../../README.md) / [exports/sabr-streaming-adapter](../README.md) / RequestSegment
+
+# Interface: RequestSegment
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:91](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L91)
+
+## Properties
+
+### getStartTime()
+
+> **getStartTime**: () => `null` \| `number`
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:92](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L92)
+
+#### Returns
+
+`null` \| `number`
+
+***
+
+### isInit()
+
+> **isInit**: () => `boolean`
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:93](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L93)
+
+#### Returns
+
+`boolean`
diff --git a/docs/api/exports/sabr-streaming-adapter/interfaces/SabrOptions.md b/docs/api/exports/sabr-streaming-adapter/interfaces/SabrOptions.md
new file mode 100644
index 0000000..1d3ebc8
--- /dev/null
+++ b/docs/api/exports/sabr-streaming-adapter/interfaces/SabrOptions.md
@@ -0,0 +1,90 @@
+[googlevideo](../../../README.md) / [exports/sabr-streaming-adapter](../README.md) / SabrOptions
+
+# Interface: SabrOptions
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:43](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L43)
+
+## Properties
+
+### clientInfo?
+
+> `optional` **clientInfo**: [`ClientInfo`](../../protos/interfaces/ClientInfo.md)
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:72](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L72)
+
+Client information to send with SABR requests.
+
+***
+
+### enableCaching?
+
+> `optional` **enableCaching**: `boolean`
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:48](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L48)
+
+Whether to enable caching of SABR segments.
+
+#### Default
+
+```ts
+true
+```
+
+***
+
+### enableVerboseRequestLogging?
+
+> `optional` **enableVerboseRequestLogging**: `boolean`
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:54](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L54)
+
+Enables verbose logging of all SABR requests made by the player.
+@NOTE: `DEBUG` level logging must be enabled for this to take effect.
+
+#### Default
+
+```ts
+false
+```
+
+***
+
+### maxCacheAgeSeconds?
+
+> `optional` **maxCacheAgeSeconds**: `number`
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:64](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L64)
+
+Maximum age of cached segments in seconds.
+
+#### Default
+
+```ts
+300 (5 minutes)
+```
+
+***
+
+### maxCacheSizeMB?
+
+> `optional` **maxCacheSizeMB**: `number`
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:59](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L59)
+
+Maximum size of the segment cache in megabytes.
+
+#### Default
+
+```ts
+3
+```
+
+***
+
+### playerAdapter?
+
+> `optional` **playerAdapter**: [`SabrPlayerAdapter`](SabrPlayerAdapter.md)
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:68](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L68)
+
+Player adapter to use for SABR streaming.
diff --git a/docs/api/exports/sabr-streaming-adapter/interfaces/SabrPlayerAdapter.md b/docs/api/exports/sabr-streaming-adapter/interfaces/SabrPlayerAdapter.md
new file mode 100644
index 0000000..8a5ad73
--- /dev/null
+++ b/docs/api/exports/sabr-streaming-adapter/interfaces/SabrPlayerAdapter.md
@@ -0,0 +1,145 @@
+[googlevideo](../../../README.md) / [exports/sabr-streaming-adapter](../README.md) / SabrPlayerAdapter
+
+# Interface: SabrPlayerAdapter
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:99](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L99)
+
+## Methods
+
+### dispose()
+
+> **dispose**(): `void`
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:114](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L114)
+
+#### Returns
+
+`void`
+
+***
+
+### getActiveTrackFormats()
+
+> **getActiveTrackFormats**(`activeFormat`, `sabrFormats`): `object`
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:108](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L108)
+
+#### Parameters
+
+##### activeFormat
+
+[`SabrFormat`](../../../types/shared/interfaces/SabrFormat.md)
+
+##### sabrFormats
+
+[`SabrFormat`](../../../types/shared/interfaces/SabrFormat.md)[]
+
+#### Returns
+
+`object`
+
+##### audioFormat?
+
+> `optional` **audioFormat**: [`SabrFormat`](../../../types/shared/interfaces/SabrFormat.md)
+
+##### videoFormat?
+
+> `optional` **videoFormat**: [`SabrFormat`](../../../types/shared/interfaces/SabrFormat.md)
+
+***
+
+### getBandwidthEstimate()
+
+> **getBandwidthEstimate**(): `number`
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:107](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L107)
+
+#### Returns
+
+`number`
+
+***
+
+### getPlaybackRate()
+
+> **getPlaybackRate**(): `number`
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:106](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L106)
+
+#### Returns
+
+`number`
+
+***
+
+### getPlayerTime()
+
+> **getPlayerTime**(): `number`
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:105](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L105)
+
+#### Returns
+
+`number`
+
+***
+
+### initialize()
+
+> **initialize**(`player`, `requestMetadataManager`, `cache`): `void`
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:100](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L100)
+
+#### Parameters
+
+##### player
+
+`any`
+
+##### requestMetadataManager
+
+[`RequestMetadataManager`](../../utils/classes/RequestMetadataManager.md)
+
+##### cache
+
+`null` | [`CacheManager`](../../utils/classes/CacheManager.md)
+
+#### Returns
+
+`void`
+
+***
+
+### registerRequestInterceptor()
+
+> **registerRequestInterceptor**(`interceptor`): `void`
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:112](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L112)
+
+#### Parameters
+
+##### interceptor
+
+[`RequestFilter`](../type-aliases/RequestFilter.md)
+
+#### Returns
+
+`void`
+
+***
+
+### registerResponseInterceptor()
+
+> **registerResponseInterceptor**(`interceptor`): `void`
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:113](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L113)
+
+#### Parameters
+
+##### interceptor
+
+[`ResponseFilter`](../type-aliases/ResponseFilter.md)
+
+#### Returns
+
+`void`
diff --git a/docs/api/exports/sabr-streaming-adapter/interfaces/SabrRequestMetadata.md b/docs/api/exports/sabr-streaming-adapter/interfaces/SabrRequestMetadata.md
new file mode 100644
index 0000000..cf25eb6
--- /dev/null
+++ b/docs/api/exports/sabr-streaming-adapter/interfaces/SabrRequestMetadata.md
@@ -0,0 +1,121 @@
+[googlevideo](../../../README.md) / [exports/sabr-streaming-adapter](../README.md) / SabrRequestMetadata
+
+# Interface: SabrRequestMetadata
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:19](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L19)
+
+## Properties
+
+### byteRange?
+
+> `optional` **byteRange**: `object`
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:20](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L20)
+
+#### end
+
+> **end**: `number`
+
+#### start
+
+> **start**: `number`
+
+***
+
+### error?
+
+> `optional` **error**: `object`
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:37](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L37)
+
+#### sabrError?
+
+> `optional` **sabrError**: [`SabrError`](../../protos/interfaces/SabrError.md)
+
+***
+
+### format?
+
+> `optional` **format**: [`SabrFormat`](../../../types/shared/interfaces/SabrFormat.md)
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:21](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L21)
+
+***
+
+### isInit?
+
+> `optional` **isInit**: `boolean`
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:22](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L22)
+
+***
+
+### isSABR?
+
+> `optional` **isSABR**: `boolean`
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:24](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L24)
+
+***
+
+### isUMP?
+
+> `optional` **isUMP**: `boolean`
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:23](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L23)
+
+***
+
+### streamInfo?
+
+> `optional` **streamInfo**: `object`
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:25](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L25)
+
+#### formatInitMetadata?
+
+> `optional` **formatInitMetadata**: [`FormatInitializationMetadata`](../../protos/interfaces/FormatInitializationMetadata.md)[]
+
+#### mediaHeader?
+
+> `optional` **mediaHeader**: [`MediaHeader`](../../protos/interfaces/MediaHeader.md)
+
+#### nextRequestPolicy?
+
+> `optional` **nextRequestPolicy**: [`NextRequestPolicy`](../../protos/interfaces/NextRequestPolicy.md)
+
+#### playbackCookie?
+
+> `optional` **playbackCookie**: [`PlaybackCookie`](../../protos/interfaces/PlaybackCookie.md)
+
+#### redirect?
+
+> `optional` **redirect**: [`SabrRedirect`](../../protos/interfaces/SabrRedirect.md)
+
+#### reloadPlaybackContext?
+
+> `optional` **reloadPlaybackContext**: [`ReloadPlaybackContext`](../../protos/interfaces/ReloadPlaybackContext.md)
+
+#### sabrContextSendingPolicy?
+
+> `optional` **sabrContextSendingPolicy**: [`SabrContextSendingPolicy`](../../protos/interfaces/SabrContextSendingPolicy.md)
+
+#### sabrContextUpdate?
+
+> `optional` **sabrContextUpdate**: [`SabrContextUpdate`](../../protos/interfaces/SabrContextUpdate.md)
+
+#### snackbarMessage?
+
+> `optional` **snackbarMessage**: [`SnackbarMessage`](../../protos/interfaces/SnackbarMessage.md)
+
+#### streamProtectionStatus?
+
+> `optional` **streamProtectionStatus**: [`StreamProtectionStatus`](../../protos/interfaces/StreamProtectionStatus.md)
+
+***
+
+### timestamp
+
+> **timestamp**: `number`
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:40](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L40)
diff --git a/docs/api/exports/sabr-streaming-adapter/interfaces/UmpProcessingResult.md b/docs/api/exports/sabr-streaming-adapter/interfaces/UmpProcessingResult.md
new file mode 100644
index 0000000..c48b15b
--- /dev/null
+++ b/docs/api/exports/sabr-streaming-adapter/interfaces/UmpProcessingResult.md
@@ -0,0 +1,21 @@
+[googlevideo](../../../README.md) / [exports/sabr-streaming-adapter](../README.md) / UmpProcessingResult
+
+# Interface: UmpProcessingResult
+
+Defined in: [src/core/SabrUmpProcessor.ts:33](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrUmpProcessor.ts#L33)
+
+## Properties
+
+### data?
+
+> `optional` **data**: `Uint8Array`
+
+Defined in: [src/core/SabrUmpProcessor.ts:34](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrUmpProcessor.ts#L34)
+
+***
+
+### done
+
+> **done**: `boolean`
+
+Defined in: [src/core/SabrUmpProcessor.ts:35](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrUmpProcessor.ts#L35)
diff --git a/docs/api/exports/sabr-streaming-adapter/type-aliases/RequestFilter.md b/docs/api/exports/sabr-streaming-adapter/type-aliases/RequestFilter.md
new file mode 100644
index 0000000..70f0a9e
--- /dev/null
+++ b/docs/api/exports/sabr-streaming-adapter/type-aliases/RequestFilter.md
@@ -0,0 +1,17 @@
+[googlevideo](../../../README.md) / [exports/sabr-streaming-adapter](../README.md) / RequestFilter
+
+# Type Alias: RequestFilter()
+
+> **RequestFilter** = (`request`) => `Promise`\<[`PlayerHttpRequest`](../interfaces/PlayerHttpRequest.md) \| `undefined`\> \| [`PlayerHttpRequest`](../interfaces/PlayerHttpRequest.md) \| `undefined`
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:96](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L96)
+
+## Parameters
+
+### request
+
+[`PlayerHttpRequest`](../interfaces/PlayerHttpRequest.md)
+
+## Returns
+
+`Promise`\<[`PlayerHttpRequest`](../interfaces/PlayerHttpRequest.md) \| `undefined`\> \| [`PlayerHttpRequest`](../interfaces/PlayerHttpRequest.md) \| `undefined`
diff --git a/docs/api/exports/sabr-streaming-adapter/type-aliases/ResponseFilter.md b/docs/api/exports/sabr-streaming-adapter/type-aliases/ResponseFilter.md
new file mode 100644
index 0000000..109dde4
--- /dev/null
+++ b/docs/api/exports/sabr-streaming-adapter/type-aliases/ResponseFilter.md
@@ -0,0 +1,17 @@
+[googlevideo](../../../README.md) / [exports/sabr-streaming-adapter](../README.md) / ResponseFilter
+
+# Type Alias: ResponseFilter()
+
+> **ResponseFilter** = (`response`) => `Promise`\<[`PlayerHttpResponse`](../interfaces/PlayerHttpResponse.md) \| `undefined`\> \| [`PlayerHttpResponse`](../interfaces/PlayerHttpResponse.md) \| `undefined`
+
+Defined in: [src/types/sabrStreamingAdapterTypes.ts:97](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/sabrStreamingAdapterTypes.ts#L97)
+
+## Parameters
+
+### response
+
+[`PlayerHttpResponse`](../interfaces/PlayerHttpResponse.md)
+
+## Returns
+
+`Promise`\<[`PlayerHttpResponse`](../interfaces/PlayerHttpResponse.md) \| `undefined`\> \| [`PlayerHttpResponse`](../interfaces/PlayerHttpResponse.md) \| `undefined`
diff --git a/docs/api/exports/sabr-streaming-adapter/variables/SABR_CONSTANTS.md b/docs/api/exports/sabr-streaming-adapter/variables/SABR_CONSTANTS.md
new file mode 100644
index 0000000..4bb535b
--- /dev/null
+++ b/docs/api/exports/sabr-streaming-adapter/variables/SABR_CONSTANTS.md
@@ -0,0 +1,37 @@
+[googlevideo](../../../README.md) / [exports/sabr-streaming-adapter](../README.md) / SABR\_CONSTANTS
+
+# Variable: SABR\_CONSTANTS
+
+> `const` **SABR\_CONSTANTS**: `object`
+
+Defined in: [src/core/SabrStreamingAdapter.ts:49](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/SabrStreamingAdapter.ts#L49)
+
+## Type declaration
+
+### DEFAULT\_OPTIONS
+
+> `readonly` **DEFAULT\_OPTIONS**: `object`
+
+#### DEFAULT\_OPTIONS.enableCaching
+
+> `readonly` **enableCaching**: `true` = `true`
+
+#### DEFAULT\_OPTIONS.enableVerboseRequestLogging
+
+> `readonly` **enableVerboseRequestLogging**: `false` = `false`
+
+#### DEFAULT\_OPTIONS.maxCacheAgeSeconds
+
+> `readonly` **maxCacheAgeSeconds**: `300` = `300`
+
+#### DEFAULT\_OPTIONS.maxCacheSizeMB
+
+> `readonly` **maxCacheSizeMB**: `3` = `3`
+
+### KEY\_PARAM
+
+> `readonly` **KEY\_PARAM**: `"key"` = `'key'`
+
+### PROTOCOL
+
+> `readonly` **PROTOCOL**: `"sabr:"` = `'sabr:'`
diff --git a/docs/api/exports/ump/README.md b/docs/api/exports/ump/README.md
new file mode 100644
index 0000000..e0ea8d9
--- /dev/null
+++ b/docs/api/exports/ump/README.md
@@ -0,0 +1,9 @@
+[googlevideo](../../README.md) / exports/ump
+
+# exports/ump
+
+## Classes
+
+- [CompositeBuffer](classes/CompositeBuffer.md)
+- [UmpReader](classes/UmpReader.md)
+- [UmpWriter](classes/UmpWriter.md)
diff --git a/docs/api/exports/ump/classes/CompositeBuffer.md b/docs/api/exports/ump/classes/CompositeBuffer.md
new file mode 100644
index 0000000..94631d0
--- /dev/null
+++ b/docs/api/exports/ump/classes/CompositeBuffer.md
@@ -0,0 +1,195 @@
+[googlevideo](../../../README.md) / [exports/ump](../README.md) / CompositeBuffer
+
+# Class: CompositeBuffer
+
+Defined in: [src/core/CompositeBuffer.ts:1](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/CompositeBuffer.ts#L1)
+
+## Constructors
+
+### Constructor
+
+> **new CompositeBuffer**(`chunks`): `CompositeBuffer`
+
+Defined in: [src/core/CompositeBuffer.ts:8](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/CompositeBuffer.ts#L8)
+
+#### Parameters
+
+##### chunks
+
+`Uint8Array`[] = `[]`
+
+#### Returns
+
+`CompositeBuffer`
+
+## Properties
+
+### chunks
+
+> **chunks**: `Uint8Array`[]
+
+Defined in: [src/core/CompositeBuffer.ts:2](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/CompositeBuffer.ts#L2)
+
+***
+
+### currentChunkIndex
+
+> **currentChunkIndex**: `number`
+
+Defined in: [src/core/CompositeBuffer.ts:4](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/CompositeBuffer.ts#L4)
+
+***
+
+### currentChunkOffset
+
+> **currentChunkOffset**: `number`
+
+Defined in: [src/core/CompositeBuffer.ts:3](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/CompositeBuffer.ts#L3)
+
+***
+
+### currentDataView?
+
+> `optional` **currentDataView**: `DataView`
+
+Defined in: [src/core/CompositeBuffer.ts:5](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/CompositeBuffer.ts#L5)
+
+***
+
+### totalLength
+
+> **totalLength**: `number`
+
+Defined in: [src/core/CompositeBuffer.ts:6](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/CompositeBuffer.ts#L6)
+
+## Methods
+
+### append()
+
+> **append**(`chunk`): `void`
+
+Defined in: [src/core/CompositeBuffer.ts:16](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/CompositeBuffer.ts#L16)
+
+#### Parameters
+
+##### chunk
+
+`CompositeBuffer` | `Uint8Array`
+
+#### Returns
+
+`void`
+
+***
+
+### canReadBytes()
+
+> **canReadBytes**(`position`, `length`): `boolean`
+
+Defined in: [src/core/CompositeBuffer.ts:65](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/CompositeBuffer.ts#L65)
+
+#### Parameters
+
+##### position
+
+`number`
+
+##### length
+
+`number`
+
+#### Returns
+
+`boolean`
+
+***
+
+### focus()
+
+> **focus**(`position`): `void`
+
+Defined in: [src/core/CompositeBuffer.ts:74](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/CompositeBuffer.ts#L74)
+
+#### Parameters
+
+##### position
+
+`number`
+
+#### Returns
+
+`void`
+
+***
+
+### getLength()
+
+> **getLength**(): `number`
+
+Defined in: [src/core/CompositeBuffer.ts:61](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/CompositeBuffer.ts#L61)
+
+#### Returns
+
+`number`
+
+***
+
+### getUint8()
+
+> **getUint8**(`position`): `number`
+
+Defined in: [src/core/CompositeBuffer.ts:69](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/CompositeBuffer.ts#L69)
+
+#### Parameters
+
+##### position
+
+`number`
+
+#### Returns
+
+`number`
+
+***
+
+### isFocused()
+
+> **isFocused**(`position`): `boolean`
+
+Defined in: [src/core/CompositeBuffer.ts:90](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/CompositeBuffer.ts#L90)
+
+#### Parameters
+
+##### position
+
+`number`
+
+#### Returns
+
+`boolean`
+
+***
+
+### split()
+
+> **split**(`position`): `object`
+
+Defined in: [src/core/CompositeBuffer.ts:35](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/CompositeBuffer.ts#L35)
+
+#### Parameters
+
+##### position
+
+`number`
+
+#### Returns
+
+`object`
+
+##### extractedBuffer
+
+> **extractedBuffer**: `CompositeBuffer`
+
+##### remainingBuffer
+
+> **remainingBuffer**: `CompositeBuffer`
diff --git a/docs/api/exports/ump/classes/UmpReader.md b/docs/api/exports/ump/classes/UmpReader.md
new file mode 100644
index 0000000..716061c
--- /dev/null
+++ b/docs/api/exports/ump/classes/UmpReader.md
@@ -0,0 +1,117 @@
+[googlevideo](../../../README.md) / [exports/ump](../README.md) / UmpReader
+
+# Class: UmpReader
+
+Defined in: [src/core/UmpReader.ts:4](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/UmpReader.ts#L4)
+
+## Constructors
+
+### Constructor
+
+> **new UmpReader**(`compositeBuffer`): `UmpReader`
+
+Defined in: [src/core/UmpReader.ts:5](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/UmpReader.ts#L5)
+
+#### Parameters
+
+##### compositeBuffer
+
+[`CompositeBuffer`](CompositeBuffer.md)
+
+#### Returns
+
+`UmpReader`
+
+## Methods
+
+### canReadFromCurrentChunk()
+
+> **canReadFromCurrentChunk**(`offset`, `length`): `boolean`
+
+Defined in: [src/core/UmpReader.ts:123](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/UmpReader.ts#L123)
+
+Checks if the specified bytes can be read from the current chunk.
+
+#### Parameters
+
+##### offset
+
+`number`
+
+Position to start reading from.
+
+##### length
+
+`number`
+
+Number of bytes to read.
+
+#### Returns
+
+`boolean`
+
+True if bytes can be read from current chunk, false otherwise.
+
+***
+
+### getCurrentDataView()
+
+> **getCurrentDataView**(): `DataView`
+
+Defined in: [src/core/UmpReader.ts:131](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/UmpReader.ts#L131)
+
+Gets a DataView of the current chunk, creating it if necessary.
+
+#### Returns
+
+`DataView`
+
+DataView for the current chunk.
+
+***
+
+### read()
+
+> **read**(`handlePart`): `undefined` \| [`Part`](../../../types/shared/type-aliases/Part.md)
+
+Defined in: [src/core/UmpReader.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/UmpReader.ts#L12)
+
+Parses parts from the buffer and calls the handler for each complete part.
+
+#### Parameters
+
+##### handlePart
+
+(`part`) => `void`
+
+Function called with each complete part.
+
+#### Returns
+
+`undefined` \| [`Part`](../../../types/shared/type-aliases/Part.md)
+
+Partial part if parsing is incomplete, undefined otherwise.
+
+***
+
+### readVarInt()
+
+> **readVarInt**(`offset`): \[`number`, `number`\]
+
+Defined in: [src/core/UmpReader.ts:54](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/UmpReader.ts#L54)
+
+Reads a variable-length integer from the buffer.
+
+#### Parameters
+
+##### offset
+
+`number`
+
+Position to start reading from.
+
+#### Returns
+
+\[`number`, `number`\]
+
+Tuple of [value, new offset] or [-1, offset] if incomplete.
diff --git a/docs/api/exports/ump/classes/UmpWriter.md b/docs/api/exports/ump/classes/UmpWriter.md
new file mode 100644
index 0000000..8360da6
--- /dev/null
+++ b/docs/api/exports/ump/classes/UmpWriter.md
@@ -0,0 +1,51 @@
+[googlevideo](../../../README.md) / [exports/ump](../README.md) / UmpWriter
+
+# Class: UmpWriter
+
+Defined in: [src/core/UmpWriter.ts:3](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/UmpWriter.ts#L3)
+
+## Constructors
+
+### Constructor
+
+> **new UmpWriter**(`compositeBuffer`): `UmpWriter`
+
+Defined in: [src/core/UmpWriter.ts:4](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/UmpWriter.ts#L4)
+
+#### Parameters
+
+##### compositeBuffer
+
+[`CompositeBuffer`](CompositeBuffer.md)
+
+#### Returns
+
+`UmpWriter`
+
+## Methods
+
+### write()
+
+> **write**(`partType`, `partData`): `void`
+
+Defined in: [src/core/UmpWriter.ts:13](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/core/UmpWriter.ts#L13)
+
+Writes a part to the buffer.
+
+#### Parameters
+
+##### partType
+
+`number`
+
+The type of the part.
+
+##### partData
+
+`Uint8Array`
+
+The data of the part.
+
+#### Returns
+
+`void`
diff --git a/docs/api/exports/utils/README.md b/docs/api/exports/utils/README.md
new file mode 100644
index 0000000..25ef6b1
--- /dev/null
+++ b/docs/api/exports/utils/README.md
@@ -0,0 +1,38 @@
+[googlevideo](../../README.md) / exports/utils
+
+# exports/utils
+
+## Namespaces
+
+- [FormatKeyUtils](namespaces/FormatKeyUtils/README.md)
+
+## Enumerations
+
+- [EnabledTrackTypes](enumerations/EnabledTrackTypes.md)
+- [LogLevel](enumerations/LogLevel.md)
+
+## Classes
+
+- [CacheManager](classes/CacheManager.md)
+- [EventEmitterLike](classes/EventEmitterLike.md)
+- [Logger](classes/Logger.md)
+- [RequestMetadataManager](classes/RequestMetadataManager.md)
+- [SabrAdapterError](classes/SabrAdapterError.md)
+
+## Interfaces
+
+- [CacheEntry](interfaces/CacheEntry.md)
+
+## Variables
+
+- [MAX\_INT32\_VALUE](variables/MAX_INT32_VALUE.md)
+
+## Functions
+
+- [base64ToU8](functions/base64ToU8.md)
+- [buildSabrFormat](functions/buildSabrFormat.md)
+- [concatenateChunks](functions/concatenateChunks.md)
+- [isGoogleVideoURL](functions/isGoogleVideoURL.md)
+- [parseRangeHeader](functions/parseRangeHeader.md)
+- [u8ToBase64](functions/u8ToBase64.md)
+- [wait](functions/wait.md)
diff --git a/docs/api/exports/utils/classes/CacheManager.md b/docs/api/exports/utils/classes/CacheManager.md
new file mode 100644
index 0000000..8424784
--- /dev/null
+++ b/docs/api/exports/utils/classes/CacheManager.md
@@ -0,0 +1,141 @@
+[googlevideo](../../../README.md) / [exports/utils](../README.md) / CacheManager
+
+# Class: CacheManager
+
+Defined in: [src/utils/CacheManager.ts:14](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/CacheManager.ts#L14)
+
+A "proper" cache for storing segments.
+
+## Constructors
+
+### Constructor
+
+> **new CacheManager**(`maxSizeMB`, `maxAgeSeconds`): `CacheManager`
+
+Defined in: [src/utils/CacheManager.ts:23](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/CacheManager.ts#L23)
+
+#### Parameters
+
+##### maxSizeMB
+
+`number` = `50`
+
+##### maxAgeSeconds
+
+`number` = `600`
+
+#### Returns
+
+`CacheManager`
+
+## Methods
+
+### dispose()
+
+> **dispose**(): `void`
+
+Defined in: [src/utils/CacheManager.ts:156](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/CacheManager.ts#L156)
+
+#### Returns
+
+`void`
+
+***
+
+### getCacheEntries()
+
+> **getCacheEntries**(): `object`
+
+Defined in: [src/utils/CacheManager.ts:29](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/CacheManager.ts#L29)
+
+#### Returns
+
+`object`
+
+##### initSegmentCache
+
+> **initSegmentCache**: `Map`\<`string`, [`CacheEntry`](../interfaces/CacheEntry.md)\>
+
+##### segmentCache
+
+> **segmentCache**: `Map`\<`string`, [`CacheEntry`](../interfaces/CacheEntry.md)\>
+
+***
+
+### getInitSegment()
+
+> **getInitSegment**(`key`): `undefined` \| `Uint8Array`
+
+Defined in: [src/utils/CacheManager.ts:63](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/CacheManager.ts#L63)
+
+#### Parameters
+
+##### key
+
+`string`
+
+#### Returns
+
+`undefined` \| `Uint8Array`
+
+***
+
+### getSegment()
+
+> **getSegment**(`key`): `undefined` \| `Uint8Array`
+
+Defined in: [src/utils/CacheManager.ts:81](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/CacheManager.ts#L81)
+
+#### Parameters
+
+##### key
+
+`string`
+
+#### Returns
+
+`undefined` \| `Uint8Array`
+
+***
+
+### setInitSegment()
+
+> **setInitSegment**(`key`, `data`): `void`
+
+Defined in: [src/utils/CacheManager.ts:36](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/CacheManager.ts#L36)
+
+#### Parameters
+
+##### key
+
+`string`
+
+##### data
+
+`Uint8Array`
+
+#### Returns
+
+`void`
+
+***
+
+### setSegment()
+
+> **setSegment**(`key`, `data`): `void`
+
+Defined in: [src/utils/CacheManager.ts:51](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/CacheManager.ts#L51)
+
+#### Parameters
+
+##### key
+
+`string`
+
+##### data
+
+`Uint8Array`
+
+#### Returns
+
+`void`
diff --git a/docs/api/exports/utils/classes/EventEmitterLike.md b/docs/api/exports/utils/classes/EventEmitterLike.md
new file mode 100644
index 0000000..27a0650
--- /dev/null
+++ b/docs/api/exports/utils/classes/EventEmitterLike.md
@@ -0,0 +1,241 @@
+[googlevideo](../../../README.md) / [exports/utils](../README.md) / EventEmitterLike
+
+# Class: EventEmitterLike
+
+Defined in: [src/utils/EventEmitterLike.ts:22](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/EventEmitterLike.ts#L22)
+
+## Extends
+
+- `EventTarget`
+
+## Extended by
+
+- [`SabrStream`](../../sabr-stream/classes/SabrStream.md)
+
+## Constructors
+
+### Constructor
+
+> **new EventEmitterLike**(): `EventEmitterLike`
+
+Defined in: [src/utils/EventEmitterLike.ts:25](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/EventEmitterLike.ts#L25)
+
+#### Returns
+
+`EventEmitterLike`
+
+#### Overrides
+
+`EventTarget.constructor`
+
+## Methods
+
+### addEventListener()
+
+> **addEventListener**(`type`, `callback`, `options?`): `void`
+
+Defined in: node\_modules/typescript/lib/lib.dom.d.ts:8303
+
+Appends an event listener for events whose type attribute value is type. The callback argument sets the callback that will be invoked when the event is dispatched.
+
+The options argument sets listener-specific options. For compatibility this can be a boolean, in which case the method behaves exactly as if the value was specified as options's capture.
+
+When set to true, options's capture prevents callback from being invoked when the event's eventPhase attribute value is BUBBLING_PHASE. When false (or not present), callback will not be invoked when event's eventPhase attribute value is CAPTURING_PHASE. Either way, callback will be invoked if event's eventPhase attribute value is AT_TARGET.
+
+When set to true, options's passive indicates that the callback will not cancel the event by invoking preventDefault(). This is used to enable performance optimizations described in § 2.8 Observing event listeners.
+
+When set to true, options's once indicates that the callback will only be invoked once after which the event listener will be removed.
+
+If an AbortSignal is passed for options's signal, then the event listener will be removed when signal is aborted.
+
+The event listener is appended to target's event listener list and is not appended if it has the same type, callback, and capture.
+
+[MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener)
+
+#### Parameters
+
+##### type
+
+`string`
+
+##### callback
+
+`null` | `EventListenerOrEventListenerObject`
+
+##### options?
+
+`boolean` | `AddEventListenerOptions`
+
+#### Returns
+
+`void`
+
+#### Inherited from
+
+`EventTarget.addEventListener`
+
+***
+
+### dispatchEvent()
+
+> **dispatchEvent**(`event`): `boolean`
+
+Defined in: node\_modules/typescript/lib/lib.dom.d.ts:8309
+
+Dispatches a synthetic event event to target and returns true if either event's cancelable attribute value is false or its preventDefault() method was not invoked, and false otherwise.
+
+[MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/dispatchEvent)
+
+#### Parameters
+
+##### event
+
+`Event`
+
+#### Returns
+
+`boolean`
+
+#### Inherited from
+
+`EventTarget.dispatchEvent`
+
+***
+
+### emit()
+
+> **emit**(`type`, ...`args`): `void`
+
+Defined in: [src/utils/EventEmitterLike.ts:29](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/EventEmitterLike.ts#L29)
+
+#### Parameters
+
+##### type
+
+`string`
+
+##### args
+
+...`any`[]
+
+#### Returns
+
+`void`
+
+***
+
+### off()
+
+> **off**(`type`, `listener`): `void`
+
+Defined in: [src/utils/EventEmitterLike.ts:59](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/EventEmitterLike.ts#L59)
+
+#### Parameters
+
+##### type
+
+`string`
+
+##### listener
+
+(...`args`) => `void`
+
+#### Returns
+
+`void`
+
+***
+
+### on()
+
+> **on**(`type`, `listener`): `void`
+
+Defined in: [src/utils/EventEmitterLike.ts:34](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/EventEmitterLike.ts#L34)
+
+#### Parameters
+
+##### type
+
+`string`
+
+##### listener
+
+(...`args`) => `void`
+
+#### Returns
+
+`void`
+
+***
+
+### once()
+
+> **once**(`type`, `listener`): `void`
+
+Defined in: [src/utils/EventEmitterLike.ts:46](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/EventEmitterLike.ts#L46)
+
+#### Parameters
+
+##### type
+
+`string`
+
+##### listener
+
+(...`args`) => `void`
+
+#### Returns
+
+`void`
+
+***
+
+### removeAllListeners()
+
+> **removeAllListeners**(`type?`): `void`
+
+Defined in: [src/utils/EventEmitterLike.ts:67](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/EventEmitterLike.ts#L67)
+
+#### Parameters
+
+##### type?
+
+`string`
+
+#### Returns
+
+`void`
+
+***
+
+### removeEventListener()
+
+> **removeEventListener**(`type`, `callback`, `options?`): `void`
+
+Defined in: node\_modules/typescript/lib/lib.dom.d.ts:8315
+
+Removes the event listener in target's event listener list with the same type, callback, and options.
+
+[MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/removeEventListener)
+
+#### Parameters
+
+##### type
+
+`string`
+
+##### callback
+
+`null` | `EventListenerOrEventListenerObject`
+
+##### options?
+
+`boolean` | `EventListenerOptions`
+
+#### Returns
+
+`void`
+
+#### Inherited from
+
+`EventTarget.removeEventListener`
diff --git a/docs/api/exports/utils/classes/Logger.md b/docs/api/exports/utils/classes/Logger.md
new file mode 100644
index 0000000..f3b6a6a
--- /dev/null
+++ b/docs/api/exports/utils/classes/Logger.md
@@ -0,0 +1,154 @@
+[googlevideo](../../../README.md) / [exports/utils](../README.md) / Logger
+
+# Class: Logger
+
+Defined in: [src/utils/Logger.ts:10](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/Logger.ts#L10)
+
+## Constructors
+
+### Constructor
+
+> **new Logger**(): `Logger`
+
+#### Returns
+
+`Logger`
+
+## Methods
+
+### debug()
+
+> **debug**(`tag`, ...`messages`): `void`
+
+Defined in: [src/utils/Logger.ts:82](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/Logger.ts#L82)
+
+#### Parameters
+
+##### tag
+
+`string`
+
+##### messages
+
+...`any`[]
+
+#### Returns
+
+`void`
+
+***
+
+### error()
+
+> **error**(`tag`, ...`messages`): `void`
+
+Defined in: [src/utils/Logger.ts:70](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/Logger.ts#L70)
+
+#### Parameters
+
+##### tag
+
+`string`
+
+##### messages
+
+...`any`[]
+
+#### Returns
+
+`void`
+
+***
+
+### getLogLevels()
+
+> **getLogLevels**(): `Set`\<[`LogLevel`](../enumerations/LogLevel.md)\>
+
+Defined in: [src/utils/Logger.ts:46](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/Logger.ts#L46)
+
+Gets the current set of active log levels.
+
+#### Returns
+
+`Set`\<[`LogLevel`](../enumerations/LogLevel.md)\>
+
+A new Set containing the active LogLevel enums.
+
+***
+
+### info()
+
+> **info**(`tag`, ...`messages`): `void`
+
+Defined in: [src/utils/Logger.ts:78](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/Logger.ts#L78)
+
+#### Parameters
+
+##### tag
+
+`string`
+
+##### messages
+
+...`any`[]
+
+#### Returns
+
+`void`
+
+***
+
+### setLogLevels()
+
+> **setLogLevels**(...`levels`): `void`
+
+Defined in: [src/utils/Logger.ts:27](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/Logger.ts#L27)
+
+Sets the active log levels.
+Call with LogLevel.NONE or no arguments to turn off all logging.
+Otherwise, specify one or more log levels to be active.
+Use LogLevel.ALL to enable all log levels.
+
+#### Parameters
+
+##### levels
+
+...[`LogLevel`](../enumerations/LogLevel.md)[]
+
+#### Returns
+
+`void`
+
+***
+
+### warn()
+
+> **warn**(`tag`, ...`messages`): `void`
+
+Defined in: [src/utils/Logger.ts:74](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/Logger.ts#L74)
+
+#### Parameters
+
+##### tag
+
+`string`
+
+##### messages
+
+...`any`[]
+
+#### Returns
+
+`void`
+
+***
+
+### getInstance()
+
+> `static` **getInstance**(): `Logger`
+
+Defined in: [src/utils/Logger.ts:14](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/Logger.ts#L14)
+
+#### Returns
+
+`Logger`
diff --git a/docs/api/exports/utils/classes/RequestMetadataManager.md b/docs/api/exports/utils/classes/RequestMetadataManager.md
new file mode 100644
index 0000000..2aacde1
--- /dev/null
+++ b/docs/api/exports/utils/classes/RequestMetadataManager.md
@@ -0,0 +1,71 @@
+[googlevideo](../../../README.md) / [exports/utils](../README.md) / RequestMetadataManager
+
+# Class: RequestMetadataManager
+
+Defined in: [src/utils/RequestMetadataManager.ts:6](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/RequestMetadataManager.ts#L6)
+
+Manages request metadata objects.
+
+## Constructors
+
+### Constructor
+
+> **new RequestMetadataManager**(): `RequestMetadataManager`
+
+Defined in: [src/utils/RequestMetadataManager.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/RequestMetadataManager.ts#L12)
+
+#### Returns
+
+`RequestMetadataManager`
+
+## Properties
+
+### metadataMap
+
+> **metadataMap**: `Map`\<`string`, [`SabrRequestMetadata`](../../sabr-streaming-adapter/interfaces/SabrRequestMetadata.md)\>
+
+Defined in: [src/utils/RequestMetadataManager.ts:7](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/RequestMetadataManager.ts#L7)
+
+## Methods
+
+### getRequestMetadata()
+
+> **getRequestMetadata**(`url`, `del`): `undefined` \| [`SabrRequestMetadata`](../../sabr-streaming-adapter/interfaces/SabrRequestMetadata.md)
+
+Defined in: [src/utils/RequestMetadataManager.ts:17](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/RequestMetadataManager.ts#L17)
+
+#### Parameters
+
+##### url
+
+`string`
+
+##### del
+
+`boolean` = `false`
+
+#### Returns
+
+`undefined` \| [`SabrRequestMetadata`](../../sabr-streaming-adapter/interfaces/SabrRequestMetadata.md)
+
+***
+
+### setRequestMetadata()
+
+> **setRequestMetadata**(`url`, `context`): `void`
+
+Defined in: [src/utils/RequestMetadataManager.ts:37](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/RequestMetadataManager.ts#L37)
+
+#### Parameters
+
+##### url
+
+`string`
+
+##### context
+
+[`SabrRequestMetadata`](../../sabr-streaming-adapter/interfaces/SabrRequestMetadata.md)
+
+#### Returns
+
+`void`
diff --git a/docs/api/exports/utils/classes/SabrAdapterError.md b/docs/api/exports/utils/classes/SabrAdapterError.md
new file mode 100644
index 0000000..0ec5c6d
--- /dev/null
+++ b/docs/api/exports/utils/classes/SabrAdapterError.md
@@ -0,0 +1,201 @@
+[googlevideo](../../../README.md) / [exports/utils](../README.md) / SabrAdapterError
+
+# Class: SabrAdapterError
+
+Defined in: [src/utils/EventEmitterLike.ts:15](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/EventEmitterLike.ts#L15)
+
+## Extends
+
+- `Error`
+
+## Constructors
+
+### Constructor
+
+> **new SabrAdapterError**(`message`, `code?`): `SabrAdapterError`
+
+Defined in: [src/utils/EventEmitterLike.ts:16](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/EventEmitterLike.ts#L16)
+
+#### Parameters
+
+##### message
+
+`string`
+
+##### code?
+
+`string`
+
+#### Returns
+
+`SabrAdapterError`
+
+#### Overrides
+
+`Error.constructor`
+
+## Properties
+
+### code?
+
+> `optional` **code**: `string`
+
+Defined in: [src/utils/EventEmitterLike.ts:16](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/EventEmitterLike.ts#L16)
+
+***
+
+### message
+
+> **message**: `string`
+
+Defined in: node\_modules/typescript/lib/lib.es5.d.ts:1077
+
+#### Inherited from
+
+`Error.message`
+
+***
+
+### name
+
+> **name**: `string`
+
+Defined in: node\_modules/typescript/lib/lib.es5.d.ts:1076
+
+#### Inherited from
+
+`Error.name`
+
+***
+
+### stack?
+
+> `optional` **stack**: `string`
+
+Defined in: node\_modules/typescript/lib/lib.es5.d.ts:1078
+
+#### Inherited from
+
+`Error.stack`
+
+***
+
+### stackTraceLimit
+
+> `static` **stackTraceLimit**: `number`
+
+Defined in: node\_modules/@types/node/globals.d.ts:162
+
+The `Error.stackTraceLimit` property specifies the number of stack frames
+collected by a stack trace (whether generated by `new Error().stack` or
+`Error.captureStackTrace(obj)`).
+
+The default value is `10` but may be set to any valid JavaScript number. Changes
+will affect any stack trace captured _after_ the value has been changed.
+
+If set to a non-number value, or set to a negative number, stack traces will
+not capture any frames.
+
+#### Inherited from
+
+`Error.stackTraceLimit`
+
+## Methods
+
+### captureStackTrace()
+
+> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
+
+Defined in: node\_modules/@types/node/globals.d.ts:146
+
+Creates a `.stack` property on `targetObject`, which when accessed returns
+a string representing the location in the code at which
+`Error.captureStackTrace()` was called.
+
+```js
+const myObject = {};
+Error.captureStackTrace(myObject);
+myObject.stack; // Similar to `new Error().stack`
+```
+
+The first line of the trace will be prefixed with
+`${myObject.name}: ${myObject.message}`.
+
+The optional `constructorOpt` argument accepts a function. If given, all frames
+above `constructorOpt`, including `constructorOpt`, will be omitted from the
+generated stack trace.
+
+The `constructorOpt` argument is useful for hiding implementation
+details of error generation from the user. For instance:
+
+```js
+function a() {
+ b();
+}
+
+function b() {
+ c();
+}
+
+function c() {
+ // Create an error without stack trace to avoid calculating the stack trace twice.
+ const { stackTraceLimit } = Error;
+ Error.stackTraceLimit = 0;
+ const error = new Error();
+ Error.stackTraceLimit = stackTraceLimit;
+
+ // Capture the stack trace above function b
+ Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
+ throw error;
+}
+
+a();
+```
+
+#### Parameters
+
+##### targetObject
+
+`object`
+
+##### constructorOpt?
+
+`Function`
+
+#### Returns
+
+`void`
+
+#### Inherited from
+
+`Error.captureStackTrace`
+
+***
+
+### prepareStackTrace()
+
+> `static` **prepareStackTrace**(`err`, `stackTraces`): `any`
+
+Defined in: node\_modules/@types/node/globals.d.ts:150
+
+#### Parameters
+
+##### err
+
+`Error`
+
+##### stackTraces
+
+`CallSite`[]
+
+#### Returns
+
+`any`
+
+#### See
+
+https://v8.dev/docs/stack-trace-api#customizing-stack-traces
+
+#### Inherited from
+
+`Error.prepareStackTrace`
diff --git a/docs/api/exports/utils/enumerations/EnabledTrackTypes.md b/docs/api/exports/utils/enumerations/EnabledTrackTypes.md
new file mode 100644
index 0000000..b9884f9
--- /dev/null
+++ b/docs/api/exports/utils/enumerations/EnabledTrackTypes.md
@@ -0,0 +1,29 @@
+[googlevideo](../../../README.md) / [exports/utils](../README.md) / EnabledTrackTypes
+
+# Enumeration: EnabledTrackTypes
+
+Defined in: [src/utils/shared.ts:5](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/shared.ts#L5)
+
+## Enumeration Members
+
+### AUDIO\_ONLY
+
+> **AUDIO\_ONLY**: `1`
+
+Defined in: [src/utils/shared.ts:7](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/shared.ts#L7)
+
+***
+
+### VIDEO\_AND\_AUDIO
+
+> **VIDEO\_AND\_AUDIO**: `0`
+
+Defined in: [src/utils/shared.ts:6](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/shared.ts#L6)
+
+***
+
+### VIDEO\_ONLY
+
+> **VIDEO\_ONLY**: `2`
+
+Defined in: [src/utils/shared.ts:8](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/shared.ts#L8)
diff --git a/docs/api/exports/utils/enumerations/LogLevel.md b/docs/api/exports/utils/enumerations/LogLevel.md
new file mode 100644
index 0000000..1c430ec
--- /dev/null
+++ b/docs/api/exports/utils/enumerations/LogLevel.md
@@ -0,0 +1,53 @@
+[googlevideo](../../../README.md) / [exports/utils](../README.md) / LogLevel
+
+# Enumeration: LogLevel
+
+Defined in: [src/utils/Logger.ts:1](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/Logger.ts#L1)
+
+## Enumeration Members
+
+### ALL
+
+> **ALL**: `99`
+
+Defined in: [src/utils/Logger.ts:7](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/Logger.ts#L7)
+
+***
+
+### DEBUG
+
+> **DEBUG**: `4`
+
+Defined in: [src/utils/Logger.ts:6](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/Logger.ts#L6)
+
+***
+
+### ERROR
+
+> **ERROR**: `1`
+
+Defined in: [src/utils/Logger.ts:3](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/Logger.ts#L3)
+
+***
+
+### INFO
+
+> **INFO**: `3`
+
+Defined in: [src/utils/Logger.ts:5](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/Logger.ts#L5)
+
+***
+
+### NONE
+
+> **NONE**: `0`
+
+Defined in: [src/utils/Logger.ts:2](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/Logger.ts#L2)
+
+***
+
+### WARN
+
+> **WARN**: `2`
+
+Defined in: [src/utils/Logger.ts:4](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/Logger.ts#L4)
diff --git a/docs/api/exports/utils/functions/base64ToU8.md b/docs/api/exports/utils/functions/base64ToU8.md
new file mode 100644
index 0000000..9103117
--- /dev/null
+++ b/docs/api/exports/utils/functions/base64ToU8.md
@@ -0,0 +1,19 @@
+[googlevideo](../../../README.md) / [exports/utils](../README.md) / base64ToU8
+
+# Function: base64ToU8()
+
+> **base64ToU8**(`base64`): `Uint8Array`
+
+Defined in: [src/utils/shared.ts:67](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/shared.ts#L67)
+
+Converts a Base64 string to a Uint8Array.
+
+## Parameters
+
+### base64
+
+`string`
+
+## Returns
+
+`Uint8Array`
diff --git a/docs/api/exports/utils/functions/buildSabrFormat.md b/docs/api/exports/utils/functions/buildSabrFormat.md
new file mode 100644
index 0000000..6b20578
--- /dev/null
+++ b/docs/api/exports/utils/functions/buildSabrFormat.md
@@ -0,0 +1,19 @@
+[googlevideo](../../../README.md) / [exports/utils](../README.md) / buildSabrFormat
+
+# Function: buildSabrFormat()
+
+> **buildSabrFormat**(`formatStream`): [`SabrFormat`](../../../types/shared/interfaces/SabrFormat.md)
+
+Defined in: [src/utils/shared.ts:92](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/shared.ts#L92)
+
+Converts a FormatStream object to a SabrFormat object.
+
+## Parameters
+
+### formatStream
+
+[`FormatStream`](../../../types/shared/interfaces/FormatStream.md)
+
+## Returns
+
+[`SabrFormat`](../../../types/shared/interfaces/SabrFormat.md)
diff --git a/docs/api/exports/utils/functions/concatenateChunks.md b/docs/api/exports/utils/functions/concatenateChunks.md
new file mode 100644
index 0000000..60d6777
--- /dev/null
+++ b/docs/api/exports/utils/functions/concatenateChunks.md
@@ -0,0 +1,19 @@
+[googlevideo](../../../README.md) / [exports/utils](../README.md) / concatenateChunks
+
+# Function: concatenateChunks()
+
+> **concatenateChunks**(`chunks`): `Uint8Array`
+
+Defined in: [src/utils/shared.ts:77](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/shared.ts#L77)
+
+Concatenates multiple Uint8Array chunks into a single Uint8Array.
+
+## Parameters
+
+### chunks
+
+`Uint8Array`[]
+
+## Returns
+
+`Uint8Array`
diff --git a/docs/api/exports/utils/functions/isGoogleVideoURL.md b/docs/api/exports/utils/functions/isGoogleVideoURL.md
new file mode 100644
index 0000000..8693fcc
--- /dev/null
+++ b/docs/api/exports/utils/functions/isGoogleVideoURL.md
@@ -0,0 +1,17 @@
+[googlevideo](../../../README.md) / [exports/utils](../README.md) / isGoogleVideoURL
+
+# Function: isGoogleVideoURL()
+
+> **isGoogleVideoURL**(`url`): `boolean`
+
+Defined in: [src/utils/shared.ts:11](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/shared.ts#L11)
+
+## Parameters
+
+### url
+
+`string`
+
+## Returns
+
+`boolean`
diff --git a/docs/api/exports/utils/functions/parseRangeHeader.md b/docs/api/exports/utils/functions/parseRangeHeader.md
new file mode 100644
index 0000000..7f075e3
--- /dev/null
+++ b/docs/api/exports/utils/functions/parseRangeHeader.md
@@ -0,0 +1,19 @@
+[googlevideo](../../../README.md) / [exports/utils](../README.md) / parseRangeHeader
+
+# Function: parseRangeHeader()
+
+> **parseRangeHeader**(`rangeHeaderValue`): `undefined` \| `Range`
+
+Defined in: [src/utils/shared.ts:42](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/shared.ts#L42)
+
+Parses the Range header value to extract the start and end byte positions.
+
+## Parameters
+
+### rangeHeaderValue
+
+`undefined` | `string`
+
+## Returns
+
+`undefined` \| `Range`
diff --git a/docs/api/exports/utils/functions/u8ToBase64.md b/docs/api/exports/utils/functions/u8ToBase64.md
new file mode 100644
index 0000000..30523f0
--- /dev/null
+++ b/docs/api/exports/utils/functions/u8ToBase64.md
@@ -0,0 +1,19 @@
+[googlevideo](../../../README.md) / [exports/utils](../README.md) / u8ToBase64
+
+# Function: u8ToBase64()
+
+> **u8ToBase64**(`u8`): `string`
+
+Defined in: [src/utils/shared.ts:59](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/shared.ts#L59)
+
+Converts a Uint8Array to a Base64 string.
+
+## Parameters
+
+### u8
+
+`Uint8Array`
+
+## Returns
+
+`string`
diff --git a/docs/api/exports/utils/functions/wait.md b/docs/api/exports/utils/functions/wait.md
new file mode 100644
index 0000000..5e466a4
--- /dev/null
+++ b/docs/api/exports/utils/functions/wait.md
@@ -0,0 +1,21 @@
+[googlevideo](../../../README.md) / [exports/utils](../README.md) / wait
+
+# Function: wait()
+
+> **wait**(`ms`): `Promise`\<`void`\>
+
+Defined in: [src/utils/shared.ts:124](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/shared.ts#L124)
+
+Returns a promise that resolves after a specified number of milliseconds.
+
+## Parameters
+
+### ms
+
+`number`
+
+The number of milliseconds to wait.
+
+## Returns
+
+`Promise`\<`void`\>
diff --git a/docs/api/exports/utils/interfaces/CacheEntry.md b/docs/api/exports/utils/interfaces/CacheEntry.md
new file mode 100644
index 0000000..3a9dd06
--- /dev/null
+++ b/docs/api/exports/utils/interfaces/CacheEntry.md
@@ -0,0 +1,29 @@
+[googlevideo](../../../README.md) / [exports/utils](../README.md) / CacheEntry
+
+# Interface: CacheEntry
+
+Defined in: [src/utils/CacheManager.ts:3](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/CacheManager.ts#L3)
+
+## Properties
+
+### data
+
+> **data**: `Uint8Array`
+
+Defined in: [src/utils/CacheManager.ts:4](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/CacheManager.ts#L4)
+
+***
+
+### size
+
+> **size**: `number`
+
+Defined in: [src/utils/CacheManager.ts:6](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/CacheManager.ts#L6)
+
+***
+
+### timestamp
+
+> **timestamp**: `number`
+
+Defined in: [src/utils/CacheManager.ts:5](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/CacheManager.ts#L5)
diff --git a/docs/api/exports/utils/namespaces/FormatKeyUtils/README.md b/docs/api/exports/utils/namespaces/FormatKeyUtils/README.md
new file mode 100644
index 0000000..aaaa275
--- /dev/null
+++ b/docs/api/exports/utils/namespaces/FormatKeyUtils/README.md
@@ -0,0 +1,13 @@
+[googlevideo](../../../../README.md) / [exports/utils](../../README.md) / FormatKeyUtils
+
+# FormatKeyUtils
+
+## Functions
+
+- [createKey](functions/createKey.md)
+- [createSegmentCacheKey](functions/createSegmentCacheKey.md)
+- [createSegmentCacheKeyFromMetadata](functions/createSegmentCacheKeyFromMetadata.md)
+- [fromFormat](functions/fromFormat.md)
+- [fromFormatInitializationMetadata](functions/fromFormatInitializationMetadata.md)
+- [fromMediaHeader](functions/fromMediaHeader.md)
+- [getUniqueFormatId](functions/getUniqueFormatId.md)
diff --git a/docs/api/exports/utils/namespaces/FormatKeyUtils/functions/createKey.md b/docs/api/exports/utils/namespaces/FormatKeyUtils/functions/createKey.md
new file mode 100644
index 0000000..771d5b1
--- /dev/null
+++ b/docs/api/exports/utils/namespaces/FormatKeyUtils/functions/createKey.md
@@ -0,0 +1,25 @@
+[googlevideo](../../../../../README.md) / [exports/utils](../../../README.md) / [FormatKeyUtils](../README.md) / createKey
+
+# Function: createKey()
+
+> **createKey**(`itag`, `xtags`): `string`
+
+Defined in: [src/utils/formatKeyUtils.ts:10](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/formatKeyUtils.ts#L10)
+
+Creates a format key based on itag and xtags.
+
+## Parameters
+
+### itag
+
+`undefined` | `number`
+
+### xtags
+
+`undefined` | `string`
+
+## Returns
+
+`string`
+
+A string format key.
diff --git a/docs/api/exports/utils/namespaces/FormatKeyUtils/functions/createSegmentCacheKey.md b/docs/api/exports/utils/namespaces/FormatKeyUtils/functions/createSegmentCacheKey.md
new file mode 100644
index 0000000..a0848c1
--- /dev/null
+++ b/docs/api/exports/utils/namespaces/FormatKeyUtils/functions/createSegmentCacheKey.md
@@ -0,0 +1,29 @@
+[googlevideo](../../../../../README.md) / [exports/utils](../../../README.md) / [FormatKeyUtils](../README.md) / createSegmentCacheKey
+
+# Function: createSegmentCacheKey()
+
+> **createSegmentCacheKey**(`mediaHeader`, `format?`): `string`
+
+Defined in: [src/utils/formatKeyUtils.ts:47](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/formatKeyUtils.ts#L47)
+
+Creates a segment cache key.
+
+## Parameters
+
+### mediaHeader
+
+[`MediaHeader`](../../../../protos/interfaces/MediaHeader.md)
+
+The MediaHeader object.
+
+### format?
+
+[`SabrFormat`](../../../../../types/shared/interfaces/SabrFormat.md)
+
+Format object (needed for init segments.)
+
+## Returns
+
+`string`
+
+A string key for caching segments.
diff --git a/docs/api/exports/utils/namespaces/FormatKeyUtils/functions/createSegmentCacheKeyFromMetadata.md b/docs/api/exports/utils/namespaces/FormatKeyUtils/functions/createSegmentCacheKeyFromMetadata.md
new file mode 100644
index 0000000..56f734e
--- /dev/null
+++ b/docs/api/exports/utils/namespaces/FormatKeyUtils/functions/createSegmentCacheKeyFromMetadata.md
@@ -0,0 +1,21 @@
+[googlevideo](../../../../../README.md) / [exports/utils](../../../README.md) / [FormatKeyUtils](../README.md) / createSegmentCacheKeyFromMetadata
+
+# Function: createSegmentCacheKeyFromMetadata()
+
+> **createSegmentCacheKeyFromMetadata**(`requestMetadata`): `string`
+
+Defined in: [src/utils/formatKeyUtils.ts:61](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/formatKeyUtils.ts#L61)
+
+Creates a cache key from request metadata.
+
+## Parameters
+
+### requestMetadata
+
+[`SabrRequestMetadata`](../../../../sabr-streaming-adapter/interfaces/SabrRequestMetadata.md)
+
+## Returns
+
+`string`
+
+A string key for caching segments.
diff --git a/docs/api/exports/utils/namespaces/FormatKeyUtils/functions/fromFormat.md b/docs/api/exports/utils/namespaces/FormatKeyUtils/functions/fromFormat.md
new file mode 100644
index 0000000..3135f78
--- /dev/null
+++ b/docs/api/exports/utils/namespaces/FormatKeyUtils/functions/fromFormat.md
@@ -0,0 +1,27 @@
+[googlevideo](../../../../../README.md) / [exports/utils](../../../README.md) / [FormatKeyUtils](../README.md) / fromFormat
+
+# Function: fromFormat()
+
+> **fromFormat**(`format?`): `undefined` \| `string`
+
+Defined in: [src/utils/formatKeyUtils.ts:18](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/formatKeyUtils.ts#L18)
+
+Creates a format key from a SabrFormat object.
+
+## Parameters
+
+### format?
+
+#### itag?
+
+`number`
+
+#### xtags?
+
+`string`
+
+## Returns
+
+`undefined` \| `string`
+
+A string format key or undefined if format is undefined.
diff --git a/docs/api/exports/utils/namespaces/FormatKeyUtils/functions/fromFormatInitializationMetadata.md b/docs/api/exports/utils/namespaces/FormatKeyUtils/functions/fromFormatInitializationMetadata.md
new file mode 100644
index 0000000..989a99f
--- /dev/null
+++ b/docs/api/exports/utils/namespaces/FormatKeyUtils/functions/fromFormatInitializationMetadata.md
@@ -0,0 +1,21 @@
+[googlevideo](../../../../../README.md) / [exports/utils](../../../README.md) / [FormatKeyUtils](../README.md) / fromFormatInitializationMetadata
+
+# Function: fromFormatInitializationMetadata()
+
+> **fromFormatInitializationMetadata**(`formatInitMetadata`): `string`
+
+Defined in: [src/utils/formatKeyUtils.ts:35](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/formatKeyUtils.ts#L35)
+
+Creates a format key from FormatInitializationMetadata.
+
+## Parameters
+
+### formatInitMetadata
+
+[`FormatInitializationMetadata`](../../../../protos/interfaces/FormatInitializationMetadata.md)
+
+## Returns
+
+`string`
+
+A string format key or undefined if formatId is undefined.
diff --git a/docs/api/exports/utils/namespaces/FormatKeyUtils/functions/fromMediaHeader.md b/docs/api/exports/utils/namespaces/FormatKeyUtils/functions/fromMediaHeader.md
new file mode 100644
index 0000000..9411768
--- /dev/null
+++ b/docs/api/exports/utils/namespaces/FormatKeyUtils/functions/fromMediaHeader.md
@@ -0,0 +1,21 @@
+[googlevideo](../../../../../README.md) / [exports/utils](../../../README.md) / [FormatKeyUtils](../README.md) / fromMediaHeader
+
+# Function: fromMediaHeader()
+
+> **fromMediaHeader**(`mediaHeader`): `string`
+
+Defined in: [src/utils/formatKeyUtils.ts:27](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/formatKeyUtils.ts#L27)
+
+Creates a format key from a MediaHeader object.
+
+## Parameters
+
+### mediaHeader
+
+[`MediaHeader`](../../../../protos/interfaces/MediaHeader.md)
+
+## Returns
+
+`string`
+
+A string format key.
diff --git a/docs/api/exports/utils/namespaces/FormatKeyUtils/functions/getUniqueFormatId.md b/docs/api/exports/utils/namespaces/FormatKeyUtils/functions/getUniqueFormatId.md
new file mode 100644
index 0000000..4e87f08
--- /dev/null
+++ b/docs/api/exports/utils/namespaces/FormatKeyUtils/functions/getUniqueFormatId.md
@@ -0,0 +1,23 @@
+[googlevideo](../../../../../README.md) / [exports/utils](../../../README.md) / [FormatKeyUtils](../README.md) / getUniqueFormatId
+
+# Function: getUniqueFormatId()
+
+> **getUniqueFormatId**(`format`): `string`
+
+Defined in: [src/utils/formatKeyUtils.ts:85](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/formatKeyUtils.ts#L85)
+
+Generates a unique format ID based on the SabrFormat properties.
+
+## Parameters
+
+### format
+
+[`SabrFormat`](../../../../../types/shared/interfaces/SabrFormat.md)
+
+The SabrFormat object.
+
+## Returns
+
+`string`
+
+A unique string identifier for the format.
diff --git a/docs/api/exports/utils/variables/MAX_INT32_VALUE.md b/docs/api/exports/utils/variables/MAX_INT32_VALUE.md
new file mode 100644
index 0000000..6bd4e42
--- /dev/null
+++ b/docs/api/exports/utils/variables/MAX_INT32_VALUE.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [exports/utils](../README.md) / MAX\_INT32\_VALUE
+
+# Variable: MAX\_INT32\_VALUE
+
+> `const` **MAX\_INT32\_VALUE**: `2147483647` = `2147483647`
+
+Defined in: [src/utils/shared.ts:3](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/utils/shared.ts#L3)
diff --git a/docs/api/types/shared/README.md b/docs/api/types/shared/README.md
new file mode 100644
index 0000000..5dbd2fd
--- /dev/null
+++ b/docs/api/types/shared/README.md
@@ -0,0 +1,13 @@
+[googlevideo](../../README.md) / types/shared
+
+# types/shared
+
+## Interfaces
+
+- [FormatStream](interfaces/FormatStream.md)
+- [SabrFormat](interfaces/SabrFormat.md)
+
+## Type Aliases
+
+- [FetchFunction](type-aliases/FetchFunction.md)
+- [Part](type-aliases/Part.md)
diff --git a/docs/api/types/shared/interfaces/FormatStream.md b/docs/api/types/shared/interfaces/FormatStream.md
new file mode 100644
index 0000000..e8322f0
--- /dev/null
+++ b/docs/api/types/shared/interfaces/FormatStream.md
@@ -0,0 +1,249 @@
+[googlevideo](../../../README.md) / [types/shared](../README.md) / FormatStream
+
+# Interface: FormatStream
+
+Defined in: [src/types/shared.ts:33](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L33)
+
+## Properties
+
+### approx\_duration\_ms?
+
+> `optional` **approx\_duration\_ms**: `number`
+
+Defined in: [src/types/shared.ts:54](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L54)
+
+***
+
+### approxDurationMs?
+
+> `optional` **approxDurationMs**: `string`
+
+Defined in: [src/types/shared.ts:55](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L55)
+
+***
+
+### audio\_quality?
+
+> `optional` **audio\_quality**: `string`
+
+Defined in: [src/types/shared.ts:42](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L42)
+
+***
+
+### audio\_track?
+
+> `optional` **audio\_track**: `object`
+
+Defined in: [src/types/shared.ts:50](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L50)
+
+#### id
+
+> **id**: `string`
+
+***
+
+### audioQuality?
+
+> `optional` **audioQuality**: `string`
+
+Defined in: [src/types/shared.ts:43](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L43)
+
+***
+
+### audioTrackId?
+
+> `optional` **audioTrackId**: `string`
+
+Defined in: [src/types/shared.ts:51](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L51)
+
+***
+
+### average\_bitrate?
+
+> `optional` **average\_bitrate**: `number`
+
+Defined in: [src/types/shared.ts:45](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L45)
+
+***
+
+### averageBitrate?
+
+> `optional` **averageBitrate**: `number`
+
+Defined in: [src/types/shared.ts:46](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L46)
+
+***
+
+### bitrate
+
+> **bitrate**: `number`
+
+Defined in: [src/types/shared.ts:44](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L44)
+
+***
+
+### content\_length?
+
+> `optional` **content\_length**: `number`
+
+Defined in: [src/types/shared.ts:56](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L56)
+
+***
+
+### contentLength?
+
+> `optional` **contentLength**: `string`
+
+Defined in: [src/types/shared.ts:57](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L57)
+
+***
+
+### height?
+
+> `optional` **height**: `number`
+
+Defined in: [src/types/shared.ts:39](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L39)
+
+***
+
+### is\_auto\_dubbed?
+
+> `optional` **is\_auto\_dubbed**: `boolean`
+
+Defined in: [src/types/shared.ts:58](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L58)
+
+***
+
+### is\_descriptive?
+
+> `optional` **is\_descriptive**: `boolean`
+
+Defined in: [src/types/shared.ts:59](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L59)
+
+***
+
+### is\_drc?
+
+> `optional` **is\_drc**: `boolean`
+
+Defined in: [src/types/shared.ts:52](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L52)
+
+***
+
+### is\_dubbed?
+
+> `optional` **is\_dubbed**: `boolean`
+
+Defined in: [src/types/shared.ts:60](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L60)
+
+***
+
+### is\_original?
+
+> `optional` **is\_original**: `boolean`
+
+Defined in: [src/types/shared.ts:62](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L62)
+
+***
+
+### is\_secondary?
+
+> `optional` **is\_secondary**: `boolean`
+
+Defined in: [src/types/shared.ts:63](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L63)
+
+***
+
+### isDrc?
+
+> `optional` **isDrc**: `boolean`
+
+Defined in: [src/types/shared.ts:53](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L53)
+
+***
+
+### itag
+
+> **itag**: `number`
+
+Defined in: [src/types/shared.ts:34](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L34)
+
+***
+
+### language?
+
+> `optional` **language**: `null` \| `string`
+
+Defined in: [src/types/shared.ts:61](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L61)
+
+***
+
+### last\_modified\_ms?
+
+> `optional` **last\_modified\_ms**: `string`
+
+Defined in: [src/types/shared.ts:35](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L35)
+
+***
+
+### lastModified?
+
+> `optional` **lastModified**: `string`
+
+Defined in: [src/types/shared.ts:36](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L36)
+
+***
+
+### mime\_type?
+
+> `optional` **mime\_type**: `string`
+
+Defined in: [src/types/shared.ts:40](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L40)
+
+***
+
+### mimeType?
+
+> `optional` **mimeType**: `string`
+
+Defined in: [src/types/shared.ts:41](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L41)
+
+***
+
+### quality?
+
+> `optional` **quality**: `string`
+
+Defined in: [src/types/shared.ts:47](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L47)
+
+***
+
+### quality\_label?
+
+> `optional` **quality\_label**: `string`
+
+Defined in: [src/types/shared.ts:48](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L48)
+
+***
+
+### qualityLabel?
+
+> `optional` **qualityLabel**: `string`
+
+Defined in: [src/types/shared.ts:49](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L49)
+
+***
+
+### width?
+
+> `optional` **width**: `number`
+
+Defined in: [src/types/shared.ts:38](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L38)
+
+***
+
+### xtags?
+
+> `optional` **xtags**: `string`
+
+Defined in: [src/types/shared.ts:37](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L37)
diff --git a/docs/api/types/shared/interfaces/SabrFormat.md b/docs/api/types/shared/interfaces/SabrFormat.md
new file mode 100644
index 0000000..3cf202f
--- /dev/null
+++ b/docs/api/types/shared/interfaces/SabrFormat.md
@@ -0,0 +1,173 @@
+[googlevideo](../../../README.md) / [types/shared](../README.md) / SabrFormat
+
+# Interface: SabrFormat
+
+Defined in: [src/types/shared.ts:9](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L9)
+
+## Properties
+
+### approxDurationMs
+
+> **approxDurationMs**: `number`
+
+Defined in: [src/types/shared.ts:24](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L24)
+
+***
+
+### audioQuality?
+
+> `optional` **audioQuality**: `string`
+
+Defined in: [src/types/shared.ts:23](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L23)
+
+***
+
+### audioTrackId?
+
+> `optional` **audioTrackId**: `string`
+
+Defined in: [src/types/shared.ts:16](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L16)
+
+***
+
+### averageBitrate?
+
+> `optional` **averageBitrate**: `number`
+
+Defined in: [src/types/shared.ts:21](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L21)
+
+***
+
+### bitrate
+
+> **bitrate**: `number`
+
+Defined in: [src/types/shared.ts:22](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L22)
+
+***
+
+### contentLength?
+
+> `optional` **contentLength**: `number`
+
+Defined in: [src/types/shared.ts:15](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L15)
+
+***
+
+### height?
+
+> `optional` **height**: `number`
+
+Defined in: [src/types/shared.ts:14](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L14)
+
+***
+
+### isAutoDubbed?
+
+> `optional` **isAutoDubbed**: `boolean`
+
+Defined in: [src/types/shared.ts:27](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L27)
+
+***
+
+### isDescriptive?
+
+> `optional` **isDescriptive**: `boolean`
+
+Defined in: [src/types/shared.ts:28](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L28)
+
+***
+
+### isDrc?
+
+> `optional` **isDrc**: `boolean`
+
+Defined in: [src/types/shared.ts:18](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L18)
+
+***
+
+### isDubbed?
+
+> `optional` **isDubbed**: `boolean`
+
+Defined in: [src/types/shared.ts:26](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L26)
+
+***
+
+### isOriginal?
+
+> `optional` **isOriginal**: `boolean`
+
+Defined in: [src/types/shared.ts:30](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L30)
+
+***
+
+### isSecondary?
+
+> `optional` **isSecondary**: `boolean`
+
+Defined in: [src/types/shared.ts:29](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L29)
+
+***
+
+### itag
+
+> **itag**: `number`
+
+Defined in: [src/types/shared.ts:10](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L10)
+
+***
+
+### language?
+
+> `optional` **language**: `null` \| `string`
+
+Defined in: [src/types/shared.ts:25](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L25)
+
+***
+
+### lastModified
+
+> **lastModified**: `number`
+
+Defined in: [src/types/shared.ts:11](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L11)
+
+***
+
+### mimeType?
+
+> `optional` **mimeType**: `string`
+
+Defined in: [src/types/shared.ts:17](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L17)
+
+***
+
+### quality?
+
+> `optional` **quality**: `string`
+
+Defined in: [src/types/shared.ts:19](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L19)
+
+***
+
+### qualityLabel?
+
+> `optional` **qualityLabel**: `string`
+
+Defined in: [src/types/shared.ts:20](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L20)
+
+***
+
+### width?
+
+> `optional` **width**: `number`
+
+Defined in: [src/types/shared.ts:13](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L13)
+
+***
+
+### xtags?
+
+> `optional` **xtags**: `string`
+
+Defined in: [src/types/shared.ts:12](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L12)
diff --git a/docs/api/types/shared/type-aliases/FetchFunction.md b/docs/api/types/shared/type-aliases/FetchFunction.md
new file mode 100644
index 0000000..c054fe7
--- /dev/null
+++ b/docs/api/types/shared/type-aliases/FetchFunction.md
@@ -0,0 +1,7 @@
+[googlevideo](../../../README.md) / [types/shared](../README.md) / FetchFunction
+
+# Type Alias: FetchFunction
+
+> **FetchFunction** = *typeof* `fetch`
+
+Defined in: [src/types/shared.ts:66](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L66)
diff --git a/docs/api/types/shared/type-aliases/Part.md b/docs/api/types/shared/type-aliases/Part.md
new file mode 100644
index 0000000..91348d4
--- /dev/null
+++ b/docs/api/types/shared/type-aliases/Part.md
@@ -0,0 +1,31 @@
+[googlevideo](../../../README.md) / [types/shared](../README.md) / Part
+
+# Type Alias: Part
+
+> **Part** = `object`
+
+Defined in: [src/types/shared.ts:3](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L3)
+
+## Properties
+
+### data
+
+> **data**: [`CompositeBuffer`](../../../exports/ump/classes/CompositeBuffer.md)
+
+Defined in: [src/types/shared.ts:6](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L6)
+
+***
+
+### size
+
+> **size**: `number`
+
+Defined in: [src/types/shared.ts:5](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L5)
+
+***
+
+### type
+
+> **type**: `number`
+
+Defined in: [src/types/shared.ts:4](https://github.com/LuanRT/googlevideo/blob/cc730b4dbadc5ae882d6aa28d716e442943577fa/src/types/shared.ts#L4)
diff --git a/eslint.config.js b/eslint.config.js
index 3d97968..e311936 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -74,7 +74,7 @@ export default [
"space-infix-ops": "error",
"template-curly-spacing": "error",
"wrap-regex": "error",
- "capitalized-comments": "error",
+ "capitalized-comments": "off",
"prefer-template": "error",
"keyword-spacing": ["error", {
before: true,
diff --git a/examples/README.md b/examples/README.md
index ced0380..a7b4094 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -4,7 +4,7 @@ https://github.com/LuanRT/yt-sabr-shaka-demo
## Downloader Example
```bash
-npm run build # If you haven't built the project yet.
+npm run build # If you haven't built the library yet.
cd examples/downloader
npm install
npx tsx main.ts
@@ -16,7 +16,7 @@ npx tsx ffmpeg-example.ts
## "Onesie" Request Example
```bash
-npm run build # If you haven't built the project yet.
+npm run build # If you haven't built the library yet.
cd examples/onesie-request
npm install
npx tsx main.ts
diff --git a/examples/downloader/ffmpeg-example.ts b/examples/downloader/ffmpeg-example.ts
index d5a2e91..8a009cb 100644
--- a/examples/downloader/ffmpeg-example.ts
+++ b/examples/downloader/ffmpeg-example.ts
@@ -1,170 +1,92 @@
+import { EnabledTrackTypes } from 'googlevideo/utils';
+import { promises as fs } from 'node:fs';
import ffmpeg from 'fluent-ffmpeg';
-import cliProgress from 'cli-progress';
-import type { WriteStream } from 'node:fs';
-import { createWriteStream, unlink } from 'node:fs';
-import { Innertube, UniversalCache } from 'youtubei.js';
-import GoogleVideo, { type Format } from '../../dist/src/index.js';
-import { generateWebPoToken } from './utils.js';
-const progressBars = new cliProgress.MultiBar({
- stopOnComplete: true,
- hideCursor: true
-}, cliProgress.Presets.rect);
+import {
+ createOutputStream,
+ createStreamSink,
+ createSabrStream,
+ createMultiProgressBar,
+ setupProgressBar
+} from './utils/sabr-stream-factory.js';
-const formatProgressBars = new Map();
+import type { SabrPlaybackOptions } from 'googlevideo/sabr-stream';
-const innertube = await Innertube.create({ cache: new UniversalCache(true) });
-const webPoTokenResult = await generateWebPoToken(innertube.session.context.client.visitorData || '');
-const info = await innertube.getBasicInfo('wRNnMQEKo7o');
-
-console.info(`
- Title: ${info.basic_info.title}
- Duration: ${info.basic_info.duration}
- Views: ${info.basic_info.view_count}
- Author: ${info.basic_info.author}
- Video ID: ${info.basic_info.id}
- WebPoToken: ${webPoTokenResult.poToken}
-`);
-
-const durationMs = (info.basic_info?.duration ?? 0) * 1000;
-const sanitizedTitle = info.basic_info.title?.replace(/[^a-z0-9]/gi, '_');
-
-let audioOutput: WriteStream | undefined;
-let videoOutput: WriteStream | undefined;
-let audioOutputFilename: string | undefined;
-let videoOutputFilename: string | undefined;
-
-const audioFormat = info.chooseFormat({ quality: 'best', format: 'webm', type: 'audio' });
-const videoFormat = info.chooseFormat({ quality: '1080p', format: 'webm', type: 'video' });
-
-const selectedAudioFormat: Format = {
- itag: audioFormat.itag,
- lastModified: audioFormat.last_modified_ms,
- xtags: audioFormat.xtags
+const VIDEO_ID = 'hzGmbwS_Drs';
+const OPTIONS: SabrPlaybackOptions = {
+ preferWebM: true,
+ preferOpus: true,
+ videoQuality: '480p',
+ audioQuality: 'AUDIO_QUALITY_MEDIUM',
+ enabledTrackTypes: EnabledTrackTypes.VIDEO_AND_AUDIO
};
-const selectedVideoFormat: Format = {
- itag: videoFormat.itag,
- lastModified: videoFormat.last_modified_ms,
- width: videoFormat.width,
- height: videoFormat.height,
- xtags: videoFormat.xtags
-};
-
-const serverAbrStreamingUrl = innertube.session.player?.decipher(info.page[0].streaming_data?.server_abr_streaming_url);
-const videoPlaybackUstreamerConfig = info.page[0].player_config?.media_common_config.media_ustreamer_request_config?.video_playback_ustreamer_config;
-
-if (!videoPlaybackUstreamerConfig)
- throw new Error('ustreamerConfig not found');
-
-if (!serverAbrStreamingUrl)
- throw new Error('serverAbrStreamingUrl not found');
-
-const serverAbrStream = new GoogleVideo.ServerAbrStream({
- fetch: innertube.session.http.fetch_function,
- poToken: webPoTokenResult.poToken,
- serverAbrStreamingUrl,
- videoPlaybackUstreamerConfig: videoPlaybackUstreamerConfig,
- durationMs
-});
-
-let downloadedBytesAudio = 0;
-let downloadedBytesVideo = 0;
-
-serverAbrStream.on('data', (streamData) => {
- for (const formatData of streamData.initializedFormats) {
- const isVideo = formatData.mimeType?.includes('video');
- const mediaFormat = info.streaming_data?.adaptive_formats.find((f) => f.itag === formatData.formatId.itag);
- const formatKey = formatData.formatKey;
-
- let bar = formatProgressBars.get(formatKey);
-
- if (!bar) {
- bar = progressBars.create(100, 0, undefined, { format: `${isVideo ? 'video' : 'audio'} (${formatData.formatId.itag}) [{bar}] {percentage}% | ETA: {eta}s` });
- formatProgressBars.set(formatKey, bar);
- }
-
- const mediaChunks = formatData.mediaChunks;
-
- if (isVideo && mediaChunks.length) {
- if (!videoOutput) {
- videoOutputFilename = `${sanitizedTitle}.${formatData.formatId.itag}.webm`;
- videoOutput = createWriteStream(videoOutputFilename);
- }
-
- for (const chunk of mediaChunks) {
- downloadedBytesVideo += chunk.length;
- videoOutput.write(chunk);
- }
- } else if (mediaChunks.length) {
- if (!audioOutput) {
- audioOutputFilename = `${sanitizedTitle}.${formatData.formatId.itag}.webm`;
- audioOutput = createWriteStream(audioOutputFilename);
- }
- for (const chunk of mediaChunks) {
- downloadedBytesAudio += chunk.length;
- audioOutput.write(chunk);
- }
- }
-
- const contentLength = mediaFormat?.content_length ?? 0;
- const downloadedBytes = isVideo ? downloadedBytesVideo : downloadedBytesAudio;
-
- if (contentLength > 0) {
- const percentage = (downloadedBytes / contentLength) * 100;
- bar.update(percentage);
+async function cleanupTempFiles(files: string[]) {
+ for (const file of files) {
+ try {
+ await fs.unlink(file);
+ } catch (error) {
+ console.warn(`Failed to delete temp file ${file}:`, error);
}
}
-});
+}
-serverAbrStream.on('error', (error) => {
- console.error(error);
-});
+async function mergeAudioAndVideo(videoTitle: string, audioPath: string, videoPath: string, mergeBar: any): Promise {
+ const sanitizedTitle = videoTitle?.replace(/[^a-z0-9]/gi, '_') || 'output';
+ const outputPath = `${sanitizedTitle}.webm`;
-await serverAbrStream.init({
- audioFormats: [ selectedAudioFormat ],
- videoFormats: [ selectedVideoFormat ],
- clientAbrState: {
- playerTimeMs: 0,
- enabledTrackTypesBitfield: 0 // 0 = BOTH, 1 = AUDIO (video-only is no longer supported by YouTube)
+ return new Promise((resolve, reject) => {
+ mergeBar.update(10);
+
+ ffmpeg()
+ .input(videoPath)
+ .input(audioPath)
+ .outputOptions([ '-c:v copy', '-c:a copy', '-map 0:v:0', '-map 1:a:0' ])
+ .on('progress', (progress) => {
+ if (progress.percent) {
+ mergeBar.update(Math.min(progress.percent, 99));
+ }
+ })
+ .on('end', () => {
+ mergeBar.update(100);
+ resolve(outputPath);
+ })
+ .on('error', (err) => {
+ reject(new Error(`Error merging files: ${err.message}`));
+ })
+ .save(outputPath);
+ });
+}
+
+async function main() {
+ const progressBars = createMultiProgressBar();
+
+ try {
+ const { streamResults } = await createSabrStream(VIDEO_ID, OPTIONS);
+ const { videoStream, audioStream, selectedFormats, videoTitle } = streamResults;
+
+ const audioOutputStream = createOutputStream(videoTitle, selectedFormats.audioFormat.mimeType!);
+ const videoOutputStream = createOutputStream(videoTitle, selectedFormats.videoFormat.mimeType!);
+
+ const audioBar = setupProgressBar(progressBars, 'audio', selectedFormats.audioFormat.contentLength || 0);
+ const videoBar = setupProgressBar(progressBars, 'video', selectedFormats.videoFormat.contentLength || 0);
+ const mergeBar = setupProgressBar(progressBars, 'merge');
+
+ await Promise.all([
+ videoStream.pipeTo(createStreamSink(selectedFormats.videoFormat, videoOutputStream.stream, videoBar)),
+ audioStream.pipeTo(createStreamSink(selectedFormats.audioFormat, audioOutputStream.stream, audioBar))
+ ]);
+
+ await mergeAudioAndVideo(videoTitle, audioOutputStream.filePath, videoOutputStream.filePath, mergeBar);
+ await cleanupTempFiles([ audioOutputStream.filePath, videoOutputStream.filePath ]);
+
+ progressBars.stop();
+ console.log(`Download complete! Output saved as "${videoTitle.replace(/[^a-z0-9]/gi, '_')}.webm"`);
+ } catch (error) {
+ console.error('Download failed:', error);
+ progressBars.stop();
+ process.exit(1);
}
-});
+}
-if (audioOutput)
- audioOutput.end();
-
-if (videoOutput)
- videoOutput.end();
-
-progressBars.stop();
-
-const outputFilename = `${sanitizedTitle}_final.webm`;
-
-await new Promise((resolve, reject) => {
- if (!videoOutputFilename || !audioOutputFilename)
- return reject(new Error('No video or audio output filename'));
-
- ffmpeg()
- .input(videoOutputFilename)
- .input(audioOutputFilename)
- .videoCodec('copy')
- .audioCodec('copy')
- .on('end', () => {
- if (videoOutputFilename) {
- unlink(videoOutputFilename, (err) => {
- if (err) console.error(`Error deleting video temp file: ${err}`);
- });
- }
- if (audioOutputFilename) {
- unlink(audioOutputFilename, (err) => {
- if (err) console.error(`Error deleting audio temp file: ${err}`);
- });
- }
- resolve();
- })
- .on('error', (err: Error) => {
- console.error('Error processing video:', err);
- reject(err);
- })
- .save(outputFilename);
-});
\ No newline at end of file
+main().then();
\ No newline at end of file
diff --git a/examples/downloader/main.ts b/examples/downloader/main.ts
index 7105e55..7fd8d6c 100644
--- a/examples/downloader/main.ts
+++ b/examples/downloader/main.ts
@@ -1,147 +1,52 @@
-import cliProgress from 'cli-progress';
-import type { WriteStream } from 'node:fs';
-import { createWriteStream } from 'node:fs';
-import { Innertube, UniversalCache } from 'youtubei.js';
-import GoogleVideo, { type Format } from '../../dist/src/index.js';
-import { generateWebPoToken } from './utils.js';
+import { EnabledTrackTypes } from 'googlevideo/utils';
-const progressBars = new cliProgress.MultiBar({
- stopOnComplete: true,
- hideCursor: true
-}, cliProgress.Presets.rect);
+import {
+ createOutputStream,
+ createStreamSink,
+ createSabrStream,
+ createMultiProgressBar,
+ setupProgressBar
+} from './utils/sabr-stream-factory.js';
-const formatProgressBars = new Map();
+import type { SabrPlaybackOptions } from 'googlevideo/sabr-stream';
-const innertube = await Innertube.create({ cache: new UniversalCache(true) });
-const webPoTokenResult = await generateWebPoToken(innertube.session.context.client.visitorData || '');
-const info = await innertube.getBasicInfo('mzqO7oKTJKI');
-
-console.info(`
- Title: ${info.basic_info.title}
- Duration: ${info.basic_info.duration}
- Views: ${info.basic_info.view_count}
- Author: ${info.basic_info.author}
- Video ID: ${info.basic_info.id}
- WebPoToken: ${webPoTokenResult.poToken}
-`);
-
-const durationMs = (info.basic_info?.duration ?? 0) * 1000;
-const sanitizedTitle = info.basic_info.title?.replace(/[^a-z0-9]/gi, '_');
-
-let audioOutput: WriteStream | undefined;
-let videoOutput: WriteStream | undefined;
-
-const audioFormat = info.chooseFormat({ quality: 'best', format: 'webm', type: 'audio' });
-const videoFormat = info.chooseFormat({ quality: '720p', format: 'webm', type: 'video' });
-
-const selectedAudioFormat: Format = {
- itag: audioFormat.itag,
- lastModified: audioFormat.last_modified_ms,
- xtags: audioFormat.xtags
+const VIDEO_ID = 'xjHO_02jZco';
+const OPTIONS: SabrPlaybackOptions = {
+ preferWebM: true,
+ preferOpus: true,
+ videoQuality: '720p',
+ audioQuality: 'AUDIO_QUALITY_MEDIUM',
+ enabledTrackTypes: EnabledTrackTypes.VIDEO_AND_AUDIO
};
-const selectedVideoFormat: Format = {
- itag: videoFormat.itag,
- lastModified: videoFormat.last_modified_ms,
- width: videoFormat.width,
- height: videoFormat.height,
- xtags: videoFormat.xtags
-};
+async function main() {
+ const progressBars = createMultiProgressBar();
-const serverAbrStreamingUrl = innertube.session.player?.decipher(info.page[0].streaming_data?.server_abr_streaming_url);
-const videoPlaybackUstreamerConfig = info.page[0].player_config?.media_common_config.media_ustreamer_request_config?.video_playback_ustreamer_config;
+ try {
+ const { streamResults } = await createSabrStream(VIDEO_ID, OPTIONS);
+ const { videoStream, audioStream, selectedFormats, videoTitle } = streamResults;
-if (!videoPlaybackUstreamerConfig)
- throw new Error('ustreamerConfig not found');
+ const audioOutputStream = createOutputStream(videoTitle, selectedFormats.audioFormat.mimeType!);
+ const videoOutputStream = createOutputStream(videoTitle, selectedFormats.videoFormat.mimeType!);
-if (!serverAbrStreamingUrl)
- throw new Error('serverAbrStreamingUrl not found');
+ const audioBar = setupProgressBar(progressBars, 'audio', selectedFormats.audioFormat.contentLength || 0);
+ const videoBar = setupProgressBar(progressBars, 'video', selectedFormats.videoFormat.contentLength || 0);
-const determineFileExtension = (mimeType: string) => {
- if (mimeType.includes('video')) {
- return mimeType.includes('webm') ? 'webm' : 'mp4';
- } else if (mimeType.includes('audio')) {
- return mimeType.includes('webm') ? 'webm' : 'm4a';
+ await Promise.all([
+ videoStream.pipeTo(createStreamSink(selectedFormats.videoFormat, videoOutputStream.stream, videoBar)),
+ audioStream.pipeTo(createStreamSink(selectedFormats.audioFormat, audioOutputStream.stream, audioBar))
+ ]);
+
+ progressBars.stop();
+
+ console.log(`Download completed! Files saved as:
+ Audio: ${audioOutputStream.filePath},
+ Video: ${videoOutputStream.filePath}`);
+ } catch (error) {
+ console.error('Download failed:', error);
+ progressBars.stop();
+ process.exit(1);
}
- return 'bin';
-};
+}
-const getOutputStream = (isVideo: boolean, mimeType: string, formatId?: number) => {
- const type = isVideo ? 'video' : 'audio';
- const extension = determineFileExtension(mimeType);
- return createWriteStream(`${sanitizedTitle}.${formatId}.${type}.${extension}`);
-};
-
-const serverAbrStream = new GoogleVideo.ServerAbrStream({
- fetch: innertube.session.http.fetch_function,
- poToken: webPoTokenResult.poToken,
- serverAbrStreamingUrl,
- videoPlaybackUstreamerConfig: videoPlaybackUstreamerConfig,
- durationMs
-});
-
-let downloadedBytesAudio = 0;
-let downloadedBytesVideo = 0;
-
-serverAbrStream.on('data', (streamData) => {
- for (const formatData of streamData.initializedFormats) {
- const isVideo = formatData.mimeType?.includes('video');
- const mediaFormat = info.streaming_data?.adaptive_formats.find((f) => f.itag === formatData.formatId.itag);
- const formatKey = formatData.formatKey;
-
- let bar = formatProgressBars.get(formatKey);
-
- if (!bar) {
- bar = progressBars.create(100, 0, undefined, { format: `${isVideo ? 'video' : 'audio'} (${formatData.formatId.itag}) [{bar}] {percentage}% | ETA: {eta}s` });
- formatProgressBars.set(formatKey, bar);
- }
-
- const mediaChunks = formatData.mediaChunks;
-
- if (isVideo && mediaChunks.length) {
- if (!videoOutput)
- videoOutput = getOutputStream(true, formatData.mimeType || '', formatData.formatId?.itag);
- for (const chunk of mediaChunks) {
- downloadedBytesVideo += chunk.length;
- videoOutput.write(chunk);
- }
- } else if (mediaChunks.length) {
- if (!audioOutput)
- audioOutput = getOutputStream(false, formatData.mimeType || '', formatData.formatId?.itag);
- for (const chunk of mediaChunks) {
- downloadedBytesAudio += chunk.length;
- audioOutput.write(chunk);
- }
- }
-
- const contentLength = mediaFormat?.content_length ?? 0;
- const downloadedBytes = isVideo ? downloadedBytesVideo : downloadedBytesAudio;
-
- if (contentLength > 0) {
- const percentage = (downloadedBytes / contentLength) * 100;
- bar.update(percentage);
- }
- }
-});
-
-serverAbrStream.on('error', (error) => {
- progressBars.stop();
- console.error(error);
-});
-
-await serverAbrStream.init({
- audioFormats: [ selectedAudioFormat ],
- videoFormats: [ selectedVideoFormat ],
- clientAbrState: {
- playerTimeMs: 0,
- enabledTrackTypesBitfield: 0 // 0 = BOTH, 1 = AUDIO (video-only is no longer supported by YouTube)
- }
-});
-
-progressBars.stop();
-
-if (audioOutput)
- audioOutput.end();
-
-if (videoOutput)
- videoOutput.end();
\ No newline at end of file
+main().then();
\ No newline at end of file
diff --git a/examples/downloader/package-lock.json b/examples/downloader/package-lock.json
index 59fc890..3e430de 100644
--- a/examples/downloader/package-lock.json
+++ b/examples/downloader/package-lock.json
@@ -12,9 +12,10 @@
"bgutils-js": "^3.1.0",
"cli-progress": "^3.12.0",
"fluent-ffmpeg": "^2.1.3",
+ "googlevideo": "file:../..",
"jsdom": "^25.0.1",
"shaka-player": "^4.11.2",
- "youtubei.js": "^12.2.0"
+ "youtubei.js": "^15.0.0"
},
"devDependencies": {
"@types/cli-progress": "^3.11.6",
@@ -23,19 +24,33 @@
"typescript": "^5.6.2"
}
},
+ "../..": {
+ "version": "3.0.0",
+ "funding": [
+ "https://github.com/sponsors/LuanRT"
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@bufbuild/protobuf": "^2.0.0"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.9.0",
+ "@stylistic/eslint-plugin": "^2.6.4",
+ "@types/eslint__js": "^8.42.3",
+ "eslint": "^9.9.0",
+ "globals": "^15.9.0",
+ "ts-patch": "^3.3.0",
+ "ts-proto": "^2.2.0",
+ "typescript": "^5.5.4",
+ "typescript-eslint": "^8.2.0",
+ "vitest": "^3.2.4"
+ }
+ },
"node_modules/@bufbuild/protobuf": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.0.0.tgz",
"integrity": "sha512-sw2JhwJyvyL0zlhG61aDzOVryEfJg2PDZFSV7i7IdC7nAE41WuXCru3QWLGiP87At0BMzKOoKO/FqEGoKygGZQ=="
},
- "node_modules/@fastify/busboy": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
- "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
- "engines": {
- "node": ">=14"
- }
- },
"node_modules/@types/cli-progress": {
"version": "3.11.6",
"resolved": "https://registry.npmjs.org/@types/cli-progress/-/cli-progress-3.11.6.tgz",
@@ -83,9 +98,9 @@
"license": "MIT"
},
"node_modules/acorn": {
- "version": "8.14.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
- "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
@@ -259,6 +274,10 @@
"node": ">= 6"
}
},
+ "node_modules/googlevideo": {
+ "resolved": "../..",
+ "link": true
+ },
"node_modules/html-encoding-sniffer": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
@@ -329,9 +348,9 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
},
"node_modules/jintr": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/jintr/-/jintr-3.2.0.tgz",
- "integrity": "sha512-psD1yf05kMKDNsUdW1l5YhO59pHScQ6OIHHb8W5SKSM2dCOFPsqolmIuSHgVA8+3Dc47NJR181CXZ4alCAPTkA==",
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/jintr/-/jintr-3.3.1.tgz",
+ "integrity": "sha512-nnOzyhf0SLpbWuZ270Omwbj5LcXUkTcZkVnK8/veJXtSZOiATM5gMZMdmzN75FmTyj+NVgrGaPdH12zIJ24oIA==",
"funding": [
"https://github.com/sponsors/LuanRT"
],
@@ -560,14 +579,12 @@
}
},
"node_modules/undici": {
- "version": "5.28.4",
- "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz",
- "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==",
- "dependencies": {
- "@fastify/busboy": "^2.0.0"
- },
+ "version": "6.21.3",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz",
+ "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==",
+ "license": "MIT",
"engines": {
- "node": ">=14.0"
+ "node": ">=18.17"
}
},
"node_modules/undici-types": {
@@ -679,18 +696,18 @@
"license": "MIT"
},
"node_modules/youtubei.js": {
- "version": "12.2.0",
- "resolved": "https://registry.npmjs.org/youtubei.js/-/youtubei.js-12.2.0.tgz",
- "integrity": "sha512-G+50qrbJCToMYhu8jbaHiS3Vf+RRul+CcDbz3hEGwHkGPh+zLiWwD6SS+YhYF+2/op4ZU5zDYQJrGqJ+wKh7Gw==",
+ "version": "15.0.0",
+ "resolved": "https://registry.npmjs.org/youtubei.js/-/youtubei.js-15.0.0.tgz",
+ "integrity": "sha512-giPZREn+q0z8Jr45NUcJUXE7QA2+UD2jx5FR+ULdnexvtHg5uQZr9Am8aYcECPKzbBNe6ksBD1yT4SKNbhpRqA==",
"funding": [
"https://github.com/sponsors/LuanRT"
],
"license": "MIT",
"dependencies": {
"@bufbuild/protobuf": "^2.0.0",
- "jintr": "^3.2.0",
+ "jintr": "^3.3.1",
"tslib": "^2.5.0",
- "undici": "^5.19.1"
+ "undici": "^6.21.3"
}
}
}
diff --git a/examples/downloader/package.json b/examples/downloader/package.json
index 2f7bc4a..78e2244 100644
--- a/examples/downloader/package.json
+++ b/examples/downloader/package.json
@@ -15,9 +15,10 @@
"bgutils-js": "^3.1.0",
"cli-progress": "^3.12.0",
"fluent-ffmpeg": "^2.1.3",
+ "googlevideo": "file:../..",
"jsdom": "^25.0.1",
"shaka-player": "^4.11.2",
- "youtubei.js": "^12.2.0"
+ "youtubei.js": "^15.0.0"
},
"devDependencies": {
"@types/cli-progress": "^3.11.6",
diff --git a/examples/downloader/tsconfig.json b/examples/downloader/tsconfig.json
index 2068b30..3fda00d 100644
--- a/examples/downloader/tsconfig.json
+++ b/examples/downloader/tsconfig.json
@@ -11,7 +11,7 @@
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
- "target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
+ "target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
diff --git a/examples/downloader/utils/sabr-stream-factory.ts b/examples/downloader/utils/sabr-stream-factory.ts
new file mode 100644
index 0000000..7bc0636
--- /dev/null
+++ b/examples/downloader/utils/sabr-stream-factory.ts
@@ -0,0 +1,211 @@
+import { createWriteStream, type WriteStream } from 'node:fs';
+import cliProgress from 'cli-progress';
+import { Constants, Innertube, type IPlayerResponse, UniversalCache, YTNodes } from 'youtubei.js';
+
+import { generateWebPoToken } from './webpo-helper.js';
+import type { SabrFormat } from 'googlevideo/shared-types';
+import type { ReloadPlaybackContext } from 'googlevideo/protos';
+import { SabrStream, type SabrPlaybackOptions } from 'googlevideo/sabr-stream';
+import { buildSabrFormat } from 'googlevideo/utils';
+
+export interface DownloadOutput {
+ stream: WriteStream;
+ filePath: string;
+}
+
+export interface StreamResults {
+ videoStream: ReadableStream;
+ audioStream: ReadableStream;
+ selectedFormats: {
+ videoFormat: SabrFormat;
+ audioFormat: SabrFormat;
+ };
+ videoTitle: string;
+}
+
+/**
+ * Fetches video details and streaming information from YouTube.
+ */
+export async function makePlayerRequest(innertube: Innertube, videoId: string, reloadPlaybackContext?: ReloadPlaybackContext): Promise {
+ const watchEndpoint = new YTNodes.NavigationEndpoint({ watchEndpoint: { videoId } });
+
+ const extraArgs: Record = {
+ playbackContext: {
+ adPlaybackContext: { pyv: true },
+ contentPlaybackContext: {
+ vis: 0,
+ splay: false,
+ lactMilliseconds: '-1',
+ signatureTimestamp: innertube.session.player?.sts
+ }
+ },
+ contentCheckOk: true,
+ racyCheckOk: true
+ };
+
+ if (reloadPlaybackContext) {
+ extraArgs.playbackContext.reloadPlaybackContext = reloadPlaybackContext;
+ }
+
+ return await watchEndpoint.call(innertube.actions, { ...extraArgs, parse: true });
+}
+
+export function determineFileExtension(mimeType: string): string {
+ if (mimeType.includes('video')) {
+ return mimeType.includes('webm') ? 'webm' : 'mp4';
+ } else if (mimeType.includes('audio')) {
+ return mimeType.includes('webm') ? 'webm' : 'm4a';
+ }
+ return 'bin';
+}
+
+export function createOutputStream(title: string, mimeType: string): DownloadOutput {
+ const type = mimeType.includes('video') ? 'video' : 'audio';
+ const sanitizedTitle = title?.replace(/[^a-z0-9]/gi, '_') || 'unknown';
+ const extension = determineFileExtension(mimeType);
+ const fileName = `${sanitizedTitle}.${type}.${extension}`;
+
+ return {
+ stream: createWriteStream(fileName, { flags: 'w', encoding: 'binary' }),
+ filePath: fileName
+ };
+}
+
+export function bytesToMB(bytes: number): string {
+ return (bytes / (1024 * 1024)).toFixed(2);
+}
+
+export function createMultiProgressBar(): cliProgress.MultiBar {
+ return new cliProgress.MultiBar({
+ stopOnComplete: true,
+ hideCursor: true
+ }, cliProgress.Presets.rect);
+}
+
+/**
+ * Creates and configures a progress bar.
+ */
+export function setupProgressBar(
+ multiBar: cliProgress.MultiBar,
+ type: 'audio' | 'video' | 'merge',
+ totalSizeBytes?: number
+): cliProgress.SingleBar {
+ if (type === 'merge') {
+ const bar = multiBar.create(100, 0, undefined, {
+ format: `${type} [{bar}] {percentage}%`
+ });
+ bar.update(0);
+ return bar;
+ }
+
+ const totalSizeMB = totalSizeBytes ? bytesToMB(totalSizeBytes) : '0.00';
+ const bar = multiBar.create(100, 0, undefined, {
+ format: `${type} [{bar}] {percentage}% | {currentSizeMB}/{totalSizeMB} MB`
+ });
+
+ bar.update(0, { currentSizeMB: '0.00', totalSizeMB });
+ return bar;
+}
+
+/**
+ * Creates a WritableStream that tracks download progress.
+ */
+export function createStreamSink(format: SabrFormat, outputStream: WriteStream, progressBar?: cliProgress.SingleBar) {
+ let size = 0;
+ const totalSize = Number(format.contentLength || 0);
+
+ return new WritableStream({
+ write(chunk) {
+ return new Promise((resolve, reject) => {
+ size += chunk.length;
+
+ if (totalSize > 0 && progressBar) {
+ const percentage = (size / totalSize) * 100;
+ progressBar.update(percentage, {
+ currentSizeMB: bytesToMB(size),
+ totalSizeMB: bytesToMB(totalSize)
+ });
+ }
+
+ outputStream.write(chunk, (err) => {
+ if (err) reject(err);
+ else resolve();
+ });
+ });
+ },
+ close() {
+ outputStream.end();
+ }
+ });
+}
+
+/**
+ * Initializes Innertube client and sets up SABR streaming for a YouTube video.
+ */
+export async function createSabrStream(
+ videoId: string,
+ options: SabrPlaybackOptions
+): Promise<{
+ innertube: Innertube;
+ streamResults: StreamResults;
+}> {
+ const innertube = await Innertube.create({ cache: new UniversalCache(true) });
+ const webPoTokenResult = await generateWebPoToken(innertube.session.context.client.visitorData || '');
+
+ // Get video metadata.
+ const playerResponse = await makePlayerRequest(innertube, videoId);
+ const videoTitle = playerResponse.video_details?.title || 'Unknown Video';
+
+ console.info(`
+ Title: ${videoTitle}
+ Duration: ${playerResponse.video_details?.duration}
+ Views: ${playerResponse.video_details?.view_count}
+ Author: ${playerResponse.video_details?.author}
+ Video ID: ${playerResponse.video_details?.id}
+ `);
+
+ // Now get the streaming information.
+ const serverAbrStreamingUrl = innertube.session.player?.decipher(playerResponse.streaming_data?.server_abr_streaming_url);
+ const videoPlaybackUstreamerConfig = playerResponse.player_config?.media_common_config.media_ustreamer_request_config?.video_playback_ustreamer_config;
+
+ if (!videoPlaybackUstreamerConfig) throw new Error('ustreamerConfig not found');
+ if (!serverAbrStreamingUrl) throw new Error('serverAbrStreamingUrl not found');
+
+ const sabrFormats = playerResponse.streaming_data?.adaptive_formats.map(buildSabrFormat) || [];
+
+ const serverAbrStream = new SabrStream({
+ formats: sabrFormats,
+ serverAbrStreamingUrl,
+ videoPlaybackUstreamerConfig,
+ poToken: webPoTokenResult.poToken,
+ clientInfo: {
+ clientName: parseInt(Constants.CLIENT_NAME_IDS[innertube.session.context.client.clientName as keyof typeof Constants.CLIENT_NAME_IDS]),
+ clientVersion: innertube.session.context.client.clientVersion
+ }
+ });
+
+ // Handle player response reload events (e.g, when IP changes, or formats expire).
+ serverAbrStream.on('reloadPlayerResponse', async (reloadPlaybackContext) => {
+ const playerResponse = await makePlayerRequest(innertube, videoId, reloadPlaybackContext);
+
+ const serverAbrStreamingUrl = innertube.session.player?.decipher(playerResponse.streaming_data?.server_abr_streaming_url);
+ const videoPlaybackUstreamerConfig = playerResponse.player_config?.media_common_config.media_ustreamer_request_config?.video_playback_ustreamer_config;
+
+ if (serverAbrStreamingUrl && videoPlaybackUstreamerConfig) {
+ serverAbrStream.setStreamingURL(serverAbrStreamingUrl);
+ serverAbrStream.setUstreamerConfig(videoPlaybackUstreamerConfig);
+ }
+ });
+
+ const { videoStream, audioStream, selectedFormats } = await serverAbrStream.start(options);
+
+ return {
+ innertube,
+ streamResults: {
+ videoStream,
+ audioStream,
+ selectedFormats,
+ videoTitle
+ }
+ };
+}
\ No newline at end of file
diff --git a/examples/downloader/utils.ts b/examples/downloader/utils/webpo-helper.ts
similarity index 100%
rename from examples/downloader/utils.ts
rename to examples/downloader/utils/webpo-helper.ts
diff --git a/examples/onesie-request/main.ts b/examples/onesie-request/main.ts
index e9c0c66..c695ee6 100644
--- a/examples/onesie-request/main.ts
+++ b/examples/onesie-request/main.ts
@@ -1,6 +1,21 @@
import Innertube, { Constants, type Context, UniversalCache, YT } from 'youtubei.js';
-import GoogleVideo, { base64ToU8, PART, Protos, QUALITY } from '../../dist/src/index.js';
+import { UmpReader, CompositeBuffer } from 'googlevideo/ump';
+
+import {
+ UMPPartId,
+ OnesieInnertubeRequest,
+ OnesieHeader,
+ SabrError,
+ OnesieProxyStatus,
+ OnesieInnertubeResponse,
+ OnesieRequest,
+ CompressionType,
+ OnesieHeaderType
+} from 'googlevideo/protos';
+
+import { base64ToU8 } from 'googlevideo/utils';
import { decryptResponse, encryptRequest } from './utils.js';
+import type { Part } from 'googlevideo/shared-types';
type ClientConfig = {
clientKeyData: Uint8Array;
@@ -9,6 +24,10 @@ type ClientConfig = {
baseUrl: string;
};
+type UmpPartHandler = (part: Part) => void;
+
+const enableCompression = true;
+
/**
* Fetches and parses the YouTube TV client configuration.
* Configurations from other clients can be used as well. I chose TVHTML5 for its simplicity.
@@ -50,15 +69,10 @@ type OnesieRequestArgs = {
innertube: Innertube;
};
-type OnesieRequest = {
- body: Uint8Array;
- encodedVideoId: string;
-}
-
/**
* Prepares a Onesie request.
*/
-async function prepareOnesieRequest(args: OnesieRequestArgs): Promise {
+async function prepareOnesieRequest(args: OnesieRequestArgs) {
const { videoId, poToken, clientConfig, innertube } = args;
const { clientKeyData, encryptedClientKey, onesieUstreamerConfig } = clientConfig;
const clonedInnerTubeContext: Context = structuredClone(innertube.session.context);
@@ -96,48 +110,42 @@ async function prepareOnesieRequest(args: OnesieRequestArgs): Promise {
+ function handleSabrError(part: Part) {
const data = part.data.chunks[0];
- switch (part.type) {
- case PART.SABR_ERROR:
- console.log('[SABR_ERROR]:', Protos.SabrError.decode(data));
- break;
- case PART.ONESIE_HEADER:
- onesie.push(Protos.OnesieHeader.decode(data));
- break;
- case PART.ONESIE_DATA:
- onesie[onesie.length - 1].data = data;
- break;
- default:
- break;
+ const error = SabrError.decode(data);
+ console.error('[SABR_ERROR]:', error);
+ }
+
+ function handleOnesieHeader(part: Part) {
+ const data = part.data.chunks[0];
+ onesie.push(OnesieHeader.decode(data));
+ }
+
+ function handleOnesieData(part: Part) {
+ const data = part.data.chunks[0];
+ if (onesie.length > 0) {
+ onesie[onesie.length - 1].data = data;
+ } else {
+ console.warn('Received ONESIE_DATA without a preceding ONESIE_HEADER');
}
+ }
+
+ const umpPartHandlers = new Map([
+ [ UMPPartId.SABR_ERROR, handleSabrError ],
+ [ UMPPartId.ONESIE_HEADER, handleOnesieHeader ],
+ [ UMPPartId.ONESIE_DATA, handleOnesieData ]
+ ]);
+
+ googUmp.read((part) => {
+ const handler = umpPartHandlers.get(part.type);
+ if (handler)
+ handler(part);
});
- const onesiePlayerResponse = onesie.find((header) => header.type === Protos.OnesieHeaderType.ONESIE_PLAYER_RESPONSE);
+ const onesiePlayerResponse = onesie.find((header) => header.type === OnesieHeaderType.ONESIE_PLAYER_RESPONSE);
if (onesiePlayerResponse) {
if (!onesiePlayerResponse.cryptoParams)
@@ -229,7 +263,7 @@ async function getBasicInfo(innertube: Innertube, videoId: string): Promise=14"
- }
- },
"node_modules/acorn": {
- "version": "8.14.1",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
- "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
@@ -37,10 +52,14 @@
"node": ">=0.4.0"
}
},
+ "node_modules/googlevideo": {
+ "resolved": "../..",
+ "link": true
+ },
"node_modules/jintr": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/jintr/-/jintr-3.3.0.tgz",
- "integrity": "sha512-ZsaajJ4Hr5XR0tSPhOZOTjFhxA0qscKNSOs41NRjx7ZOGwpfdp8NKIBEUtvUPbA37JXyv1sJlgeOOZHjr3h76Q==",
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/jintr/-/jintr-3.3.1.tgz",
+ "integrity": "sha512-nnOzyhf0SLpbWuZ270Omwbj5LcXUkTcZkVnK8/veJXtSZOiATM5gMZMdmzN75FmTyj+NVgrGaPdH12zIJ24oIA==",
"funding": [
"https://github.com/sponsors/LuanRT"
],
@@ -55,29 +74,27 @@
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
},
"node_modules/undici": {
- "version": "5.28.4",
- "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz",
- "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==",
- "dependencies": {
- "@fastify/busboy": "^2.0.0"
- },
+ "version": "6.21.3",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz",
+ "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==",
+ "license": "MIT",
"engines": {
- "node": ">=14.0"
+ "node": ">=18.17"
}
},
"node_modules/youtubei.js": {
- "version": "13.3.0",
- "resolved": "https://registry.npmjs.org/youtubei.js/-/youtubei.js-13.3.0.tgz",
- "integrity": "sha512-tbl7rxltpgKoSsmfGUe9JqWUAzv6HFLqrOn0N85EbTn5DLt24EXrjClnXdxyr3PBARMJ3LC4vbll100a0ABsYw==",
+ "version": "15.0.0",
+ "resolved": "https://registry.npmjs.org/youtubei.js/-/youtubei.js-15.0.0.tgz",
+ "integrity": "sha512-giPZREn+q0z8Jr45NUcJUXE7QA2+UD2jx5FR+ULdnexvtHg5uQZr9Am8aYcECPKzbBNe6ksBD1yT4SKNbhpRqA==",
"funding": [
"https://github.com/sponsors/LuanRT"
],
"license": "MIT",
"dependencies": {
"@bufbuild/protobuf": "^2.0.0",
- "jintr": "^3.3.0",
+ "jintr": "^3.3.1",
"tslib": "^2.5.0",
- "undici": "^5.19.1"
+ "undici": "^6.21.3"
}
}
}
diff --git a/examples/onesie-request/package.json b/examples/onesie-request/package.json
index 84c8f16..cf53c97 100644
--- a/examples/onesie-request/package.json
+++ b/examples/onesie-request/package.json
@@ -12,6 +12,7 @@
"author": "",
"license": "ISC",
"dependencies": {
- "youtubei.js": "^13.3.0"
+ "googlevideo": "file:../..",
+ "youtubei.js": "^15.0.0"
}
}
diff --git a/jsr.json b/jsr.json
index 93e6923..35cb1ea 100644
--- a/jsr.json
+++ b/jsr.json
@@ -1,7 +1,14 @@
{
"name": "@luanrt/googlevideo",
"version": "3.0.0",
- "exports": "./src/index.ts",
+ "exports": {
+ "./ump": "./src/exports/ump.ts",
+ "./sabr-streaming-adapter": "./src/exports/sabr-streaming-adapter.ts",
+ "./sabr-stream": "./src/exports/sabr-stream.ts",
+ "./protos": "./src/exports/protos.ts",
+ "./utils": "./src/exports/utils.ts",
+ "./shared-types": "./src/types/shared.ts"
+ },
"imports": {
"@bufbuild/protobuf": "npm:@bufbuild/protobuf@^2.0.0"
},
@@ -13,7 +20,7 @@
"src/**/*.ts"
],
"exclude": [
- "test",
+ "tests",
"dev-scripts",
"examples"
]
diff --git a/package-lock.json b/package-lock.json
index 0465a73..ed48748 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -22,8 +22,11 @@
"globals": "^15.9.0",
"ts-patch": "^3.3.0",
"ts-proto": "^2.2.0",
+ "typedoc": "^0.28.7",
+ "typedoc-plugin-markdown": "^4.7.1",
"typescript": "^5.5.4",
- "typescript-eslint": "^8.2.0"
+ "typescript-eslint": "^8.2.0",
+ "vitest": "^3.2.4"
}
},
"node_modules/@bufbuild/protobuf": {
@@ -31,6 +34,448 @@
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.0.0.tgz",
"integrity": "sha512-sw2JhwJyvyL0zlhG61aDzOVryEfJg2PDZFSV7i7IdC7nAE41WuXCru3QWLGiP87At0BMzKOoKO/FqEGoKygGZQ=="
},
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.6.tgz",
+ "integrity": "sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.6.tgz",
+ "integrity": "sha512-S8ToEOVfg++AU/bHwdksHNnyLyVM+eMVAOf6yRKFitnwnbwwPNqKr3srzFRe7nzV69RQKb5DgchIX5pt3L53xg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.6.tgz",
+ "integrity": "sha512-hd5zdUarsK6strW+3Wxi5qWws+rJhCCbMiC9QZyzoxfk5uHRIE8T287giQxzVpEvCwuJ9Qjg6bEjcRJcgfLqoA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.6.tgz",
+ "integrity": "sha512-0Z7KpHSr3VBIO9A/1wcT3NTy7EB4oNC4upJ5ye3R7taCc2GUdeynSLArnon5G8scPwaU866d3H4BCrE5xLW25A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.6.tgz",
+ "integrity": "sha512-FFCssz3XBavjxcFxKsGy2DYK5VSvJqa6y5HXljKzhRZ87LvEi13brPrf/wdyl/BbpbMKJNOr1Sd0jtW4Ge1pAA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.6.tgz",
+ "integrity": "sha512-GfXs5kry/TkGM2vKqK2oyiLFygJRqKVhawu3+DOCk7OxLy/6jYkWXhlHwOoTb0WqGnWGAS7sooxbZowy+pK9Yg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.6.tgz",
+ "integrity": "sha512-aoLF2c3OvDn2XDTRvn8hN6DRzVVpDlj2B/F66clWd/FHLiHaG3aVZjxQX2DYphA5y/evbdGvC6Us13tvyt4pWg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.6.tgz",
+ "integrity": "sha512-2SkqTjTSo2dYi/jzFbU9Plt1vk0+nNg8YC8rOXXea+iA3hfNJWebKYPs3xnOUf9+ZWhKAaxnQNUf2X9LOpeiMQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.6.tgz",
+ "integrity": "sha512-SZHQlzvqv4Du5PrKE2faN0qlbsaW/3QQfUUc6yO2EjFcA83xnwm91UbEEVx4ApZ9Z5oG8Bxz4qPE+HFwtVcfyw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.6.tgz",
+ "integrity": "sha512-b967hU0gqKd9Drsh/UuAm21Khpoh6mPBSgz8mKRq4P5mVK8bpA+hQzmm/ZwGVULSNBzKdZPQBRT3+WuVavcWsQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.6.tgz",
+ "integrity": "sha512-aHWdQ2AAltRkLPOsKdi3xv0mZ8fUGPdlKEjIEhxCPm5yKEThcUjHpWB1idN74lfXGnZ5SULQSgtr5Qos5B0bPw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.6.tgz",
+ "integrity": "sha512-VgKCsHdXRSQ7E1+QXGdRPlQ/e08bN6WMQb27/TMfV+vPjjTImuT9PmLXupRlC90S1JeNNW5lzkAEO/McKeJ2yg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.6.tgz",
+ "integrity": "sha512-WViNlpivRKT9/py3kCmkHnn44GkGXVdXfdc4drNmRl15zVQ2+D2uFwdlGh6IuK5AAnGTo2qPB1Djppj+t78rzw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.6.tgz",
+ "integrity": "sha512-wyYKZ9NTdmAMb5730I38lBqVu6cKl4ZfYXIs31Baf8aoOtB4xSGi3THmDYt4BTFHk7/EcVixkOV2uZfwU3Q2Jw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.6.tgz",
+ "integrity": "sha512-KZh7bAGGcrinEj4qzilJ4hqTY3Dg2U82c8bv+e1xqNqZCrCyc+TL9AUEn5WGKDzm3CfC5RODE/qc96OcbIe33w==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.6.tgz",
+ "integrity": "sha512-9N1LsTwAuE9oj6lHMyyAM+ucxGiVnEqUdp4v7IaMmrwb06ZTEVCIs3oPPplVsnjPfyjmxwHxHMF8b6vzUVAUGw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.6.tgz",
+ "integrity": "sha512-A6bJB41b4lKFWRKNrWoP2LHsjVzNiaurf7wyj/XtFNTsnPuxwEBWHLty+ZE0dWBKuSK1fvKgrKaNjBS7qbFKig==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.6.tgz",
+ "integrity": "sha512-IjA+DcwoVpjEvyxZddDqBY+uJ2Snc6duLpjmkXm/v4xuS3H+3FkLZlDm9ZsAbF9rsfP3zeA0/ArNDORZgrxR/Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.6.tgz",
+ "integrity": "sha512-dUXuZr5WenIDlMHdMkvDc1FAu4xdWixTCRgP7RQLBOkkGgwuuzaGSYcOpW4jFxzpzL1ejb8yF620UxAqnBrR9g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.6.tgz",
+ "integrity": "sha512-l8ZCvXP0tbTJ3iaqdNf3pjaOSd5ex/e6/omLIQCVBLmHTlfXW3zAxQ4fnDmPLOB1x9xrcSi/xtCWFwCZRIaEwg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.6.tgz",
+ "integrity": "sha512-hKrmDa0aOFOr71KQ/19JC7az1P0GWtCN1t2ahYAf4O007DHZt/dW8ym5+CUdJhQ/qkZmI1HAF8KkJbEFtCL7gw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.6.tgz",
+ "integrity": "sha512-+SqBcAWoB1fYKmpWoQP4pGtx+pUUC//RNYhFdbcSA16617cchuryuhOCRpPsjCblKukAckWsV+aQ3UKT/RMPcA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.6.tgz",
+ "integrity": "sha512-dyCGxv1/Br7MiSC42qinGL8KkG4kX0pEsdb0+TKhmJZgCUDBGmyo1/ArCjNGiOLiIAgdbWgmWgib4HoCi5t7kA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.6.tgz",
+ "integrity": "sha512-42QOgcZeZOvXfsCBJF5Afw73t4veOId//XD3i+/9gSkhSV6Gk3VPlWncctI+JcOyERv85FUo7RxuxGy+z8A43Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.6.tgz",
+ "integrity": "sha512-4AWhgXmDuYN7rJI6ORB+uU9DHLq/erBbuMoAuB4VWJTu5KtCgcKYPynF0YI1VkBNuEfjNlLrFr9KZPJzrtLkrQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.6.tgz",
+ "integrity": "sha512-NgJPHHbEpLQgDH2MjQu90pzW/5vvXIZ7KOnPyNBm92A6WgZ/7b6fJyUBjoumLqeOQQGqY2QjQxRo97ah4Sj0cA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@eslint-community/eslint-utils": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -190,6 +635,20 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
+ "node_modules/@gerrit0/mini-shiki": {
+ "version": "3.8.1",
+ "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.8.1.tgz",
+ "integrity": "sha512-HVZW+8pxoOExr5ZMPK15U79jQAZTO/S6i5byQyyZGjtNj+qaYd82cizTncwFzTQgiLo8uUBym6vh+/1tfJklTw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/engine-oniguruma": "^3.8.1",
+ "@shikijs/langs": "^3.8.1",
+ "@shikijs/themes": "^3.8.1",
+ "@shikijs/types": "^3.8.1",
+ "@shikijs/vscode-textmate": "^10.0.2"
+ }
+ },
"node_modules/@humanwhocodes/module-importer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
@@ -216,6 +675,13 @@
"url": "https://github.com/sponsors/nzakas"
}
},
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
+ "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -251,6 +717,335 @@
"node": ">= 8"
}
},
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.44.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.1.tgz",
+ "integrity": "sha512-JAcBr1+fgqx20m7Fwe1DxPUl/hPkee6jA6Pl7n1v2EFiktAHenTaXl5aIFjUIEsfn9w3HE4gK1lEgNGMzBDs1w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.44.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.44.1.tgz",
+ "integrity": "sha512-RurZetXqTu4p+G0ChbnkwBuAtwAbIwJkycw1n6GvlGlBuS4u5qlr5opix8cBAYFJgaY05TWtM+LaoFggUmbZEQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.44.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.44.1.tgz",
+ "integrity": "sha512-fM/xPesi7g2M7chk37LOnmnSTHLG/v2ggWqKj3CCA1rMA4mm5KVBT1fNoswbo1JhPuNNZrVwpTvlCVggv8A2zg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.44.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.44.1.tgz",
+ "integrity": "sha512-gDnWk57urJrkrHQ2WVx9TSVTH7lSlU7E3AFqiko+bgjlh78aJ88/3nycMax52VIVjIm3ObXnDL2H00e/xzoipw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.44.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.44.1.tgz",
+ "integrity": "sha512-wnFQmJ/zPThM5zEGcnDcCJeYJgtSLjh1d//WuHzhf6zT3Md1BvvhJnWoy+HECKu2bMxaIcfWiu3bJgx6z4g2XA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.44.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.44.1.tgz",
+ "integrity": "sha512-uBmIxoJ4493YATvU2c0upGz87f99e3wop7TJgOA/bXMFd2SvKCI7xkxY/5k50bv7J6dw1SXT4MQBQSLn8Bb/Uw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.44.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.44.1.tgz",
+ "integrity": "sha512-n0edDmSHlXFhrlmTK7XBuwKlG5MbS7yleS1cQ9nn4kIeW+dJH+ExqNgQ0RrFRew8Y+0V/x6C5IjsHrJmiHtkxQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.44.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.44.1.tgz",
+ "integrity": "sha512-8WVUPy3FtAsKSpyk21kV52HCxB+me6YkbkFHATzC2Yd3yuqHwy2lbFL4alJOLXKljoRw08Zk8/xEj89cLQ/4Nw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.44.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.44.1.tgz",
+ "integrity": "sha512-yuktAOaeOgorWDeFJggjuCkMGeITfqvPgkIXhDqsfKX8J3jGyxdDZgBV/2kj/2DyPaLiX6bPdjJDTu9RB8lUPQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.44.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.44.1.tgz",
+ "integrity": "sha512-W+GBM4ifET1Plw8pdVaecwUgxmiH23CfAUj32u8knq0JPFyK4weRy6H7ooxYFD19YxBulL0Ktsflg5XS7+7u9g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
+ "version": "4.44.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.44.1.tgz",
+ "integrity": "sha512-1zqnUEMWp9WrGVuVak6jWTl4fEtrVKfZY7CvcBmUUpxAJ7WcSowPSAWIKa/0o5mBL/Ij50SIf9tuirGx63Ovew==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+ "version": "4.44.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.44.1.tgz",
+ "integrity": "sha512-Rl3JKaRu0LHIx7ExBAAnf0JcOQetQffaw34T8vLlg9b1IhzcBgaIdnvEbbsZq9uZp3uAH+JkHd20Nwn0h9zPjA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.44.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.44.1.tgz",
+ "integrity": "sha512-j5akelU3snyL6K3N/iX7otLBIl347fGwmd95U5gS/7z6T4ftK288jKq3A5lcFKcx7wwzb5rgNvAg3ZbV4BqUSw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.44.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.44.1.tgz",
+ "integrity": "sha512-ppn5llVGgrZw7yxbIm8TTvtj1EoPgYUAbfw0uDjIOzzoqlZlZrLJ/KuiE7uf5EpTpCTrNt1EdtzF0naMm0wGYg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.44.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.44.1.tgz",
+ "integrity": "sha512-Hu6hEdix0oxtUma99jSP7xbvjkUM/ycke/AQQ4EC5g7jNRLLIwjcNwaUy95ZKBJJwg1ZowsclNnjYqzN4zwkAw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.44.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.44.1.tgz",
+ "integrity": "sha512-EtnsrmZGomz9WxK1bR5079zee3+7a+AdFlghyd6VbAjgRJDbTANJ9dcPIPAi76uG05micpEL+gPGmAKYTschQw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.44.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.44.1.tgz",
+ "integrity": "sha512-iAS4p+J1az6Usn0f8xhgL4PaU878KEtutP4hqw52I4IO6AGoyOkHCxcc4bqufv1tQLdDWFx8lR9YlwxKuv3/3g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.44.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.44.1.tgz",
+ "integrity": "sha512-NtSJVKcXwcqozOl+FwI41OH3OApDyLk3kqTJgx8+gp6On9ZEt5mYhIsKNPGuaZr3p9T6NWPKGU/03Vw4CNU9qg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.44.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.44.1.tgz",
+ "integrity": "sha512-JYA3qvCOLXSsnTR3oiyGws1Dm0YTuxAAeaYGVlGpUsHqloPcFjPg+X0Fj2qODGLNwQOAcCiQmHub/V007kiH5A==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.44.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.44.1.tgz",
+ "integrity": "sha512-J8o22LuF0kTe7m+8PvW9wk3/bRq5+mRo5Dqo6+vXb7otCm3TPhYOJqOaQtGU9YMWQSL3krMnoOxMr0+9E6F3Ug==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@shikijs/engine-oniguruma": {
+ "version": "3.8.1",
+ "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.8.1.tgz",
+ "integrity": "sha512-KGQJZHlNY7c656qPFEQpIoqOuC4LrxjyNndRdzk5WKB/Ie87+NJCF1xo9KkOUxwxylk7rT6nhlZyTGTC4fCe1g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/types": "3.8.1",
+ "@shikijs/vscode-textmate": "^10.0.2"
+ }
+ },
+ "node_modules/@shikijs/langs": {
+ "version": "3.8.1",
+ "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.8.1.tgz",
+ "integrity": "sha512-TjOFg2Wp1w07oKnXjs0AUMb4kJvujML+fJ1C5cmEj45lhjbUXtziT1x2bPQb9Db6kmPhkG5NI2tgYW1/DzhUuQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/types": "3.8.1"
+ }
+ },
+ "node_modules/@shikijs/themes": {
+ "version": "3.8.1",
+ "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.8.1.tgz",
+ "integrity": "sha512-Vu3t3BBLifc0GB0UPg2Pox1naTemrrvyZv2lkiSw3QayVV60me1ujFQwPZGgUTmwXl1yhCPW8Lieesm0CYruLQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/types": "3.8.1"
+ }
+ },
+ "node_modules/@shikijs/types": {
+ "version": "3.8.1",
+ "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.8.1.tgz",
+ "integrity": "sha512-5C39Q8/8r1I26suLh+5TPk1DTrbY/kn3IdWA5HdizR0FhlhD05zx5nKCqhzSfDHH3p4S0ZefxWd77DLV+8FhGg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/vscode-textmate": "^10.0.2",
+ "@types/hast": "^3.0.4"
+ }
+ },
+ "node_modules/@shikijs/vscode-textmate": {
+ "version": "10.0.2",
+ "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz",
+ "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@stylistic/eslint-plugin": {
"version": "2.10.1",
"resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.10.1.tgz",
@@ -271,6 +1066,23 @@
"eslint": ">=8.40.0"
}
},
+ "node_modules/@types/chai": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz",
+ "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/deep-eql": "*"
+ }
+ },
+ "node_modules/@types/deep-eql": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
+ "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/eslint": {
"version": "9.6.1",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
@@ -293,12 +1105,22 @@
}
},
"node_modules/@types/estree": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
- "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/hast": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
+ "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -306,6 +1128,25 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/node": {
+ "version": "24.0.8",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.8.tgz",
+ "integrity": "sha512-WytNrFSgWO/esSH9NbpWUfTMGQwCGIKfCmNlmFDNiI5gGhgMmEA+V1AEvKLeBNvvtBnailJtkrEa2OIISwrVAA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "undici-types": "~7.8.0"
+ }
+ },
+ "node_modules/@types/unist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.12.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.12.2.tgz",
@@ -509,6 +1350,121 @@
"url": "https://opencollective.com/eslint"
}
},
+ "node_modules/@vitest/expect": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz",
+ "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/chai": "^5.2.2",
+ "@vitest/spy": "3.2.4",
+ "@vitest/utils": "3.2.4",
+ "chai": "^5.2.0",
+ "tinyrainbow": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/mocker": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz",
+ "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "3.2.4",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.17"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/pretty-format": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz",
+ "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz",
+ "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "3.2.4",
+ "pathe": "^2.0.3",
+ "strip-literal": "^3.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz",
+ "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "3.2.4",
+ "magic-string": "^0.30.17",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz",
+ "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyspy": "^4.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz",
+ "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "3.2.4",
+ "loupe": "^3.1.4",
+ "tinyrainbow": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
"node_modules/acorn": {
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
@@ -577,6 +1533,16 @@
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true
},
+ "node_modules/assertion-error": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -606,6 +1572,16 @@
"node": ">=8"
}
},
+ "node_modules/cac": {
+ "version": "6.7.14",
+ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
+ "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -627,6 +1603,23 @@
"url": "https://github.com/sponsors/mesqueeb"
}
},
+ "node_modules/chai": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz",
+ "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "assertion-error": "^2.0.1",
+ "check-error": "^2.1.1",
+ "deep-eql": "^5.0.1",
+ "loupe": "^3.1.0",
+ "pathval": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -643,6 +1636,16 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/check-error": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz",
+ "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 16"
+ }
+ },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -668,10 +1671,11 @@
"dev": true
},
"node_modules/cross-spawn": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
- "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
@@ -682,9 +1686,9 @@
}
},
"node_modules/debug": {
- "version": "4.3.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
- "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
+ "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -699,6 +1703,16 @@
}
}
},
+ "node_modules/deep-eql": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
+ "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -726,6 +1740,68 @@
"detect-libc": "^1.0.3"
}
},
+ "node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/es-module-lexer": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
+ "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/esbuild": {
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.6.tgz",
+ "integrity": "sha512-GVuzuUwtdsghE3ocJ9Bs8PNoF13HNQ5TXbEi2AhvVb8xU1Iwt9Fos9FEamfoee+u/TOsn7GUWc04lz46n2bbTg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.6",
+ "@esbuild/android-arm": "0.25.6",
+ "@esbuild/android-arm64": "0.25.6",
+ "@esbuild/android-x64": "0.25.6",
+ "@esbuild/darwin-arm64": "0.25.6",
+ "@esbuild/darwin-x64": "0.25.6",
+ "@esbuild/freebsd-arm64": "0.25.6",
+ "@esbuild/freebsd-x64": "0.25.6",
+ "@esbuild/linux-arm": "0.25.6",
+ "@esbuild/linux-arm64": "0.25.6",
+ "@esbuild/linux-ia32": "0.25.6",
+ "@esbuild/linux-loong64": "0.25.6",
+ "@esbuild/linux-mips64el": "0.25.6",
+ "@esbuild/linux-ppc64": "0.25.6",
+ "@esbuild/linux-riscv64": "0.25.6",
+ "@esbuild/linux-s390x": "0.25.6",
+ "@esbuild/linux-x64": "0.25.6",
+ "@esbuild/netbsd-arm64": "0.25.6",
+ "@esbuild/netbsd-x64": "0.25.6",
+ "@esbuild/openbsd-arm64": "0.25.6",
+ "@esbuild/openbsd-x64": "0.25.6",
+ "@esbuild/openharmony-arm64": "0.25.6",
+ "@esbuild/sunos-x64": "0.25.6",
+ "@esbuild/win32-arm64": "0.25.6",
+ "@esbuild/win32-ia32": "0.25.6",
+ "@esbuild/win32-x64": "0.25.6"
+ }
+ },
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -912,6 +1988,16 @@
"node": ">=4.0"
}
},
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
"node_modules/esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
@@ -921,6 +2007,16 @@
"node": ">=0.10.0"
}
},
+ "node_modules/expect-type": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz",
+ "integrity": "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -965,6 +2061,21 @@
"reusify": "^1.0.4"
}
},
+ "node_modules/fdir": {
+ "version": "6.4.6",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
+ "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -1025,11 +2136,27 @@
"integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
"dev": true
},
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"dev": true,
+ "license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@@ -1291,6 +2418,16 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/linkify-it": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
+ "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "uc.micro": "^2.0.0"
+ }
+ },
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -1312,6 +2449,55 @@
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true
},
+ "node_modules/loupe": {
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.4.tgz",
+ "integrity": "sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lunr": {
+ "version": "2.3.9",
+ "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz",
+ "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.17",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
+ "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0"
+ }
+ },
+ "node_modules/markdown-it": {
+ "version": "14.1.0",
+ "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
+ "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1",
+ "entities": "^4.4.0",
+ "linkify-it": "^5.0.0",
+ "mdurl": "^2.0.0",
+ "punycode.js": "^2.3.1",
+ "uc.micro": "^2.1.0"
+ },
+ "bin": {
+ "markdown-it": "bin/markdown-it.mjs"
+ }
+ },
+ "node_modules/mdurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
+ "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -1381,6 +2567,25 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -1468,7 +2673,32 @@
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pathval": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz",
+ "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.16"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
},
"node_modules/picomatch": {
"version": "4.0.2",
@@ -1483,6 +2713,35 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -1501,6 +2760,16 @@
"node": ">=6"
}
},
+ "node_modules/punycode.js": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
+ "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -1560,6 +2829,46 @@
"node": ">=0.10.0"
}
},
+ "node_modules/rollup": {
+ "version": "4.44.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.1.tgz",
+ "integrity": "sha512-x8H8aPvD+xbl0Do8oez5f5o8eMS3trfCghc4HhLAnCkj7Vl0d1JWGs0UF/D886zLW2rOj2QymV/JcSSsw+XDNg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.44.1",
+ "@rollup/rollup-android-arm64": "4.44.1",
+ "@rollup/rollup-darwin-arm64": "4.44.1",
+ "@rollup/rollup-darwin-x64": "4.44.1",
+ "@rollup/rollup-freebsd-arm64": "4.44.1",
+ "@rollup/rollup-freebsd-x64": "4.44.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.44.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.44.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.44.1",
+ "@rollup/rollup-linux-arm64-musl": "4.44.1",
+ "@rollup/rollup-linux-loongarch64-gnu": "4.44.1",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.44.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.44.1",
+ "@rollup/rollup-linux-riscv64-musl": "4.44.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.44.1",
+ "@rollup/rollup-linux-x64-gnu": "4.44.1",
+ "@rollup/rollup-linux-x64-musl": "4.44.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.44.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.44.1",
+ "@rollup/rollup-win32-x64-msvc": "4.44.1",
+ "fsevents": "~2.3.2"
+ }
+ },
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -1584,9 +2893,9 @@
}
},
"node_modules/semver": {
- "version": "7.6.3",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
- "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
"dev": true,
"license": "ISC",
"bin": {
@@ -1617,6 +2926,37 @@
"node": ">=8"
}
},
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/std-env": {
+ "version": "3.9.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz",
+ "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
@@ -1641,6 +2981,26 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/strip-literal": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz",
+ "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^9.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/strip-literal/node_modules/js-tokens": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz",
+ "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -1671,6 +3031,67 @@
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
"dev": true
},
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyexec": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
+ "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.14",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
+ "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinypool": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz",
+ "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ }
+ },
+ "node_modules/tinyrainbow": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz",
+ "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tinyspy": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz",
+ "integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -1760,6 +3181,43 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/typedoc": {
+ "version": "0.28.7",
+ "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.7.tgz",
+ "integrity": "sha512-lpz0Oxl6aidFkmS90VQDQjk/Qf2iw0IUvFqirdONBdj7jPSN9mGXhy66BcGNDxx5ZMyKKiBVAREvPEzT6Uxipw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@gerrit0/mini-shiki": "^3.7.0",
+ "lunr": "^2.3.9",
+ "markdown-it": "^14.1.0",
+ "minimatch": "^9.0.5",
+ "yaml": "^2.8.0"
+ },
+ "bin": {
+ "typedoc": "bin/typedoc"
+ },
+ "engines": {
+ "node": ">= 18",
+ "pnpm": ">= 10"
+ },
+ "peerDependencies": {
+ "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x"
+ }
+ },
+ "node_modules/typedoc-plugin-markdown": {
+ "version": "4.7.1",
+ "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.7.1.tgz",
+ "integrity": "sha512-HN/fHLm2S6MD4HX8txfB4eWvVBzX/mEYy5U5s1KTAdh3E5uX5/lilswqTzZlPTT6fNZInAboAdFGpbAuBKnE4A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 18"
+ },
+ "peerDependencies": {
+ "typedoc": "0.28.x"
+ }
+ },
"node_modules/typescript": {
"version": "5.6.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz",
@@ -1797,6 +3255,22 @@
}
}
},
+ "node_modules/uc.micro": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
+ "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/undici-types": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
+ "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "peer": true
+ },
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
@@ -1806,6 +3280,177 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/vite": {
+ "version": "6.3.5",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
+ "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.25.0",
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2",
+ "postcss": "^8.5.3",
+ "rollup": "^4.34.9",
+ "tinyglobby": "^0.2.13"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "jiti": ">=1.21.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite-node": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz",
+ "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cac": "^6.7.14",
+ "debug": "^4.4.1",
+ "es-module-lexer": "^1.7.0",
+ "pathe": "^2.0.3",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
+ },
+ "bin": {
+ "vite-node": "vite-node.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/vitest": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz",
+ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/chai": "^5.2.2",
+ "@vitest/expect": "3.2.4",
+ "@vitest/mocker": "3.2.4",
+ "@vitest/pretty-format": "^3.2.4",
+ "@vitest/runner": "3.2.4",
+ "@vitest/snapshot": "3.2.4",
+ "@vitest/spy": "3.2.4",
+ "@vitest/utils": "3.2.4",
+ "chai": "^5.2.0",
+ "debug": "^4.4.1",
+ "expect-type": "^1.2.1",
+ "magic-string": "^0.30.17",
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.2",
+ "std-env": "^3.9.0",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^0.3.2",
+ "tinyglobby": "^0.2.14",
+ "tinypool": "^1.1.1",
+ "tinyrainbow": "^2.0.0",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0",
+ "vite-node": "3.2.4",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@types/debug": "^4.1.12",
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "@vitest/browser": "3.2.4",
+ "@vitest/ui": "3.2.4",
+ "happy-dom": "*",
+ "jsdom": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@types/debug": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -1821,6 +3466,23 @@
"node": ">= 8"
}
},
+ "node_modules/why-is-node-running": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+ "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/word-wrap": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
@@ -1830,6 +3492,19 @@
"node": ">=0.10.0"
}
},
+ "node_modules/yaml": {
+ "version": "2.8.0",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz",
+ "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "yaml": "bin.mjs"
+ },
+ "engines": {
+ "node": ">= 14.6"
+ }
+ },
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
diff --git a/package.json b/package.json
index 7a5b181..1c512c6 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "googlevideo",
"version": "3.0.0",
- "description": "A set of utilities for working with Google Video APIs.",
+ "description": "A collection of modules for working with YouTube's proprietary video streaming protocols (UMP/SABR).",
"main": "dist/index.js",
"type": "module",
"types": "./dist/src/index.d.ts",
@@ -14,6 +14,9 @@
"build": "npm run clean && npm run lint && npm run build:proto && npm run build:esm",
"build:esm": "npx tspc",
"build:proto": "node ./dev-scripts/generate-proto.mjs",
+ "build:docs": "typedoc",
+ "test": "vitest run --reporter verbose",
+ "test:watch": "vitest",
"prepare": "npm run build"
},
"author": "LuanRT (https://github.com/LuanRT)",
@@ -28,12 +31,33 @@
],
"license": "MIT",
"exports": {
- ".": {
- "node": {
- "import": "./dist/src/index.js"
- },
- "types": "./dist/src/index.d.ts",
- "default": "./dist/src/index.js"
+ "./ump": {
+ "types": "./dist/src/exports/ump.d.ts",
+ "import": "./dist/src/exports/ump.js",
+ "default": "./dist/src/exports/ump.js"
+ },
+ "./sabr-streaming-adapter": {
+ "types": "./dist/src/exports/sabr-streaming-adapter.d.ts",
+ "import": "./dist/src/exports/sabr-streaming-adapter.js",
+ "default": "./dist/src/exports/sabr-streaming-adapter.js"
+ },
+ "./sabr-stream": {
+ "types": "./dist/src/exports/sabr-stream.d.ts",
+ "import": "./dist/src/exports/sabr-stream.js",
+ "default": "./dist/src/exports/sabr-stream.js"
+ },
+ "./protos": {
+ "types": "./dist/src/exports/protos.d.ts",
+ "import": "./dist/src/exports/protos.js",
+ "default": "./dist/src/exports/protos.js"
+ },
+ "./utils": {
+ "types": "./dist/src/exports/utils.d.ts",
+ "import": "./dist/src/exports/utils.js",
+ "default": "./dist/src/exports/utils.js"
+ },
+ "./shared-types": {
+ "types": "./dist/src/types/shared.d.ts"
}
},
"devDependencies": {
@@ -44,8 +68,11 @@
"globals": "^15.9.0",
"ts-patch": "^3.3.0",
"ts-proto": "^2.2.0",
+ "typedoc": "^0.28.7",
+ "typedoc-plugin-markdown": "^4.7.1",
"typescript": "^5.5.4",
- "typescript-eslint": "^8.2.0"
+ "typescript-eslint": "^8.2.0",
+ "vitest": "^3.2.4"
},
"dependencies": {
"@bufbuild/protobuf": "^2.0.0"
@@ -58,4 +85,4 @@
"type": "git",
"url": "git+https://github.com/LuanRT/GoogleVideo.git"
}
-}
\ No newline at end of file
+}
diff --git a/protos/generated/misc/common.ts b/protos/generated/misc/common.ts
index 8ed2501..8780942 100644
--- a/protos/generated/misc/common.ts
+++ b/protos/generated/misc/common.ts
@@ -9,6 +9,13 @@ import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
export const protobufPackage = "misc";
+export enum CompressionType {
+ UNKNOWN = 0,
+ GZIP = 1,
+ BROTLI = 2,
+ UNRECOGNIZED = -1,
+}
+
export enum AudioQuality {
UNKNOWN = 0,
ULTRALOW = 5,
@@ -182,14 +189,16 @@ export interface FormatId {
xtags?: string | undefined;
}
-export interface InitRange {
+export interface Range {
+ legacyStart?: number | undefined;
+ legacyEnd?: number | undefined;
start?: number | undefined;
end?: number | undefined;
}
-export interface IndexRange {
- start?: number | undefined;
- end?: number | undefined;
+export interface IdentifierToken {
+ requestNumber?: number | undefined;
+ field5?: number | undefined;
}
export interface KeyValuePair {
@@ -197,6 +206,16 @@ export interface KeyValuePair {
value?: string | undefined;
}
+export interface AuthorizedFormat {
+ trackType?: number | undefined;
+ isHdr?: boolean | undefined;
+}
+
+export interface PlaybackAuthorization {
+ authorizedFormats: AuthorizedFormat[];
+ sabrLicenseConstraint?: Uint8Array | undefined;
+}
+
function createBaseHttpHeader(): HttpHeader {
return { name: "", value: "" };
}
@@ -299,25 +318,31 @@ export const FormatId: MessageFns = {
},
};
-function createBaseInitRange(): InitRange {
- return { start: 0, end: 0 };
+function createBaseRange(): Range {
+ return { legacyStart: 0, legacyEnd: 0, start: 0, end: 0 };
}
-export const InitRange: MessageFns = {
- encode(message: InitRange, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+export const Range: MessageFns = {
+ encode(message: Range, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ if (message.legacyStart !== undefined && message.legacyStart !== 0) {
+ writer.uint32(8).int32(message.legacyStart);
+ }
+ if (message.legacyEnd !== undefined && message.legacyEnd !== 0) {
+ writer.uint32(16).int32(message.legacyEnd);
+ }
if (message.start !== undefined && message.start !== 0) {
- writer.uint32(8).int32(message.start);
+ writer.uint32(24).int32(message.start);
}
if (message.end !== undefined && message.end !== 0) {
- writer.uint32(16).int32(message.end);
+ writer.uint32(32).int32(message.end);
}
return writer;
},
- decode(input: BinaryReader | Uint8Array, length?: number): InitRange {
+ decode(input: BinaryReader | Uint8Array, length?: number): Range {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
let end = length === undefined ? reader.len : reader.pos + length;
- const message = createBaseInitRange();
+ const message = createBaseRange();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
@@ -326,13 +351,27 @@ export const InitRange: MessageFns = {
break;
}
- message.start = reader.int32();
+ message.legacyStart = reader.int32();
continue;
case 2:
if (tag !== 16) {
break;
}
+ message.legacyEnd = reader.int32();
+ continue;
+ case 3:
+ if (tag !== 24) {
+ break;
+ }
+
+ message.start = reader.int32();
+ continue;
+ case 4:
+ if (tag !== 32) {
+ break;
+ }
+
message.end = reader.int32();
continue;
}
@@ -345,25 +384,25 @@ export const InitRange: MessageFns = {
},
};
-function createBaseIndexRange(): IndexRange {
- return { start: 0, end: 0 };
+function createBaseIdentifierToken(): IdentifierToken {
+ return { requestNumber: 0, field5: 0 };
}
-export const IndexRange: MessageFns = {
- encode(message: IndexRange, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
- if (message.start !== undefined && message.start !== 0) {
- writer.uint32(8).int32(message.start);
+export const IdentifierToken: MessageFns = {
+ encode(message: IdentifierToken, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ if (message.requestNumber !== undefined && message.requestNumber !== 0) {
+ writer.uint32(8).int32(message.requestNumber);
}
- if (message.end !== undefined && message.end !== 0) {
- writer.uint32(16).int32(message.end);
+ if (message.field5 !== undefined && message.field5 !== 0) {
+ writer.uint32(40).int32(message.field5);
}
return writer;
},
- decode(input: BinaryReader | Uint8Array, length?: number): IndexRange {
+ decode(input: BinaryReader | Uint8Array, length?: number): IdentifierToken {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
let end = length === undefined ? reader.len : reader.pos + length;
- const message = createBaseIndexRange();
+ const message = createBaseIdentifierToken();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
@@ -372,14 +411,14 @@ export const IndexRange: MessageFns = {
break;
}
- message.start = reader.int32();
+ message.requestNumber = reader.int32();
continue;
- case 2:
- if (tag !== 16) {
+ case 5:
+ if (tag !== 40) {
break;
}
- message.end = reader.int32();
+ message.field5 = reader.int32();
continue;
}
if ((tag & 7) === 4 || tag === 0) {
@@ -437,6 +476,98 @@ export const KeyValuePair: MessageFns = {
},
};
+function createBaseAuthorizedFormat(): AuthorizedFormat {
+ return { trackType: 0, isHdr: false };
+}
+
+export const AuthorizedFormat: MessageFns = {
+ encode(message: AuthorizedFormat, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ if (message.trackType !== undefined && message.trackType !== 0) {
+ writer.uint32(8).int32(message.trackType);
+ }
+ if (message.isHdr !== undefined && message.isHdr !== false) {
+ writer.uint32(16).bool(message.isHdr);
+ }
+ return writer;
+ },
+
+ decode(input: BinaryReader | Uint8Array, length?: number): AuthorizedFormat {
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
+ let end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseAuthorizedFormat();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1:
+ if (tag !== 8) {
+ break;
+ }
+
+ message.trackType = reader.int32();
+ continue;
+ case 2:
+ if (tag !== 16) {
+ break;
+ }
+
+ message.isHdr = reader.bool();
+ continue;
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ reader.skip(tag & 7);
+ }
+ return message;
+ },
+};
+
+function createBasePlaybackAuthorization(): PlaybackAuthorization {
+ return { authorizedFormats: [], sabrLicenseConstraint: new Uint8Array(0) };
+}
+
+export const PlaybackAuthorization: MessageFns = {
+ encode(message: PlaybackAuthorization, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ for (const v of message.authorizedFormats) {
+ AuthorizedFormat.encode(v!, writer.uint32(10).fork()).join();
+ }
+ if (message.sabrLicenseConstraint !== undefined && message.sabrLicenseConstraint.length !== 0) {
+ writer.uint32(18).bytes(message.sabrLicenseConstraint);
+ }
+ return writer;
+ },
+
+ decode(input: BinaryReader | Uint8Array, length?: number): PlaybackAuthorization {
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
+ let end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBasePlaybackAuthorization();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1:
+ if (tag !== 10) {
+ break;
+ }
+
+ message.authorizedFormats.push(AuthorizedFormat.decode(reader, reader.uint32()));
+ continue;
+ case 2:
+ if (tag !== 18) {
+ break;
+ }
+
+ message.sabrLicenseConstraint = reader.bytes();
+ continue;
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ reader.skip(tag & 7);
+ }
+ return message;
+ },
+};
+
function longToNumber(int64: { toString(): string }): number {
const num = globalThis.Number(int64.toString());
if (num > globalThis.Number.MAX_SAFE_INTEGER) {
diff --git a/protos/generated/video_streaming/buffered_range.ts b/protos/generated/video_streaming/buffered_range.ts
index 62aea8d..073d805 100644
--- a/protos/generated/video_streaming/buffered_range.ts
+++ b/protos/generated/video_streaming/buffered_range.ts
@@ -18,21 +18,21 @@ export interface BufferedRange {
startSegmentIndex: number;
endSegmentIndex: number;
timeRange?: TimeRange | undefined;
- field9?: Kob | undefined;
- field11?: YPa | undefined;
- field12?: YPa | undefined;
+ field9?: BufferedRange_UnknownMessage1 | undefined;
+ field11?: BufferedRange_UnknownMessage2 | undefined;
+ field12?: BufferedRange_UnknownMessage2 | undefined;
}
-export interface Kob {
- EW: Kob_Pa[];
+export interface BufferedRange_UnknownMessage1 {
+ field1: BufferedRange_UnknownMessage1_UnknownInnerMessage[];
}
-export interface Kob_Pa {
+export interface BufferedRange_UnknownMessage1_UnknownInnerMessage {
videoId?: string | undefined;
lmt?: number | undefined;
}
-export interface YPa {
+export interface BufferedRange_UnknownMessage2 {
field1?: number | undefined;
field2?: number | undefined;
field3?: number | undefined;
@@ -73,13 +73,13 @@ export const BufferedRange: MessageFns = {
TimeRange.encode(message.timeRange, writer.uint32(50).fork()).join();
}
if (message.field9 !== undefined) {
- Kob.encode(message.field9, writer.uint32(74).fork()).join();
+ BufferedRange_UnknownMessage1.encode(message.field9, writer.uint32(74).fork()).join();
}
if (message.field11 !== undefined) {
- YPa.encode(message.field11, writer.uint32(90).fork()).join();
+ BufferedRange_UnknownMessage2.encode(message.field11, writer.uint32(90).fork()).join();
}
if (message.field12 !== undefined) {
- YPa.encode(message.field12, writer.uint32(98).fork()).join();
+ BufferedRange_UnknownMessage2.encode(message.field12, writer.uint32(98).fork()).join();
}
return writer;
},
@@ -138,21 +138,21 @@ export const BufferedRange: MessageFns = {
break;
}
- message.field9 = Kob.decode(reader, reader.uint32());
+ message.field9 = BufferedRange_UnknownMessage1.decode(reader, reader.uint32());
continue;
case 11:
if (tag !== 90) {
break;
}
- message.field11 = YPa.decode(reader, reader.uint32());
+ message.field11 = BufferedRange_UnknownMessage2.decode(reader, reader.uint32());
continue;
case 12:
if (tag !== 98) {
break;
}
- message.field12 = YPa.decode(reader, reader.uint32());
+ message.field12 = BufferedRange_UnknownMessage2.decode(reader, reader.uint32());
continue;
}
if ((tag & 7) === 4 || tag === 0) {
@@ -164,22 +164,22 @@ export const BufferedRange: MessageFns = {
},
};
-function createBaseKob(): Kob {
- return { EW: [] };
+function createBaseBufferedRange_UnknownMessage1(): BufferedRange_UnknownMessage1 {
+ return { field1: [] };
}
-export const Kob: MessageFns = {
- encode(message: Kob, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
- for (const v of message.EW) {
- Kob_Pa.encode(v!, writer.uint32(10).fork()).join();
+export const BufferedRange_UnknownMessage1: MessageFns = {
+ encode(message: BufferedRange_UnknownMessage1, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ for (const v of message.field1) {
+ BufferedRange_UnknownMessage1_UnknownInnerMessage.encode(v!, writer.uint32(10).fork()).join();
}
return writer;
},
- decode(input: BinaryReader | Uint8Array, length?: number): Kob {
+ decode(input: BinaryReader | Uint8Array, length?: number): BufferedRange_UnknownMessage1 {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
let end = length === undefined ? reader.len : reader.pos + length;
- const message = createBaseKob();
+ const message = createBaseBufferedRange_UnknownMessage1();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
@@ -188,7 +188,7 @@ export const Kob: MessageFns = {
break;
}
- message.EW.push(Kob_Pa.decode(reader, reader.uint32()));
+ message.field1.push(BufferedRange_UnknownMessage1_UnknownInnerMessage.decode(reader, reader.uint32()));
continue;
}
if ((tag & 7) === 4 || tag === 0) {
@@ -200,12 +200,17 @@ export const Kob: MessageFns = {
},
};
-function createBaseKob_Pa(): Kob_Pa {
+function createBaseBufferedRange_UnknownMessage1_UnknownInnerMessage(): BufferedRange_UnknownMessage1_UnknownInnerMessage {
return { videoId: "", lmt: 0 };
}
-export const Kob_Pa: MessageFns = {
- encode(message: Kob_Pa, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+export const BufferedRange_UnknownMessage1_UnknownInnerMessage: MessageFns<
+ BufferedRange_UnknownMessage1_UnknownInnerMessage
+> = {
+ encode(
+ message: BufferedRange_UnknownMessage1_UnknownInnerMessage,
+ writer: BinaryWriter = new BinaryWriter(),
+ ): BinaryWriter {
if (message.videoId !== undefined && message.videoId !== "") {
writer.uint32(10).string(message.videoId);
}
@@ -215,10 +220,10 @@ export const Kob_Pa: MessageFns = {
return writer;
},
- decode(input: BinaryReader | Uint8Array, length?: number): Kob_Pa {
+ decode(input: BinaryReader | Uint8Array, length?: number): BufferedRange_UnknownMessage1_UnknownInnerMessage {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
let end = length === undefined ? reader.len : reader.pos + length;
- const message = createBaseKob_Pa();
+ const message = createBaseBufferedRange_UnknownMessage1_UnknownInnerMessage();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
@@ -246,12 +251,12 @@ export const Kob_Pa: MessageFns = {
},
};
-function createBaseYPa(): YPa {
+function createBaseBufferedRange_UnknownMessage2(): BufferedRange_UnknownMessage2 {
return { field1: 0, field2: 0, field3: 0 };
}
-export const YPa: MessageFns = {
- encode(message: YPa, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+export const BufferedRange_UnknownMessage2: MessageFns = {
+ encode(message: BufferedRange_UnknownMessage2, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
if (message.field1 !== undefined && message.field1 !== 0) {
writer.uint32(8).int32(message.field1);
}
@@ -264,10 +269,10 @@ export const YPa: MessageFns = {
return writer;
},
- decode(input: BinaryReader | Uint8Array, length?: number): YPa {
+ decode(input: BinaryReader | Uint8Array, length?: number): BufferedRange_UnknownMessage2 {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
let end = length === undefined ? reader.len : reader.pos + length;
- const message = createBaseYPa();
+ const message = createBaseBufferedRange_UnknownMessage2();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
diff --git a/protos/generated/video_streaming/client_abr_state.ts b/protos/generated/video_streaming/client_abr_state.ts
index 6c8d450..c4822eb 100644
--- a/protos/generated/video_streaming/client_abr_state.ts
+++ b/protos/generated/video_streaming/client_abr_state.ts
@@ -10,8 +10,10 @@ import {
AudioQuality,
NetworkMeteredState,
PlaybackAudioRouteOutputType,
+ PlaybackAuthorization,
VideoQualitySetting,
} from "../misc/common.js";
+import { MediaCapabilities } from "./media_capabilities.js";
export const protobufPackage = "video_streaming";
@@ -37,29 +39,34 @@ export interface ClientAbrState {
visibility?: number | undefined;
playbackRate?: number | undefined;
elapsedWallTimeMs?: number | undefined;
- mediaCapabilities?: Uint8Array | undefined;
+ mediaCapabilities?: MediaCapabilities | undefined;
timeSinceLastActionMs?: number | undefined;
enabledTrackTypesBitfield?: number | undefined;
maxPacingRate?: number | undefined;
playerState?: number | undefined;
drcEnabled?: boolean | undefined;
- Jda?: number | undefined;
- qw?: number | undefined;
- Ky?: number | undefined;
+ field48?: number | undefined;
+ field50?: number | undefined;
+ field51?: number | undefined;
sabrReportRequestCancellationInfo?: number | undefined;
- l?: boolean | undefined;
- G7?: number | undefined;
- preferVp9?: boolean | undefined;
- qj?: number | undefined;
- Hx?: number | undefined;
+ disableStreamingXhr?: boolean | undefined;
+ field57?: number | undefined;
+ preferVp9?:
+ | boolean
+ | undefined;
+ /** 2160 */
+ av1QualityThreshold?: number | undefined;
+ field60?: number | undefined;
isPrefetch?: boolean | undefined;
- sabrSupportQualityConstraints?: number | undefined;
+ sabrSupportQualityConstraints?: boolean | undefined;
sabrLicenseConstraint?: Uint8Array | undefined;
allowProximaLiveLatency?: number | undefined;
sabrForceProxima?: number | undefined;
- Tqb?: number | undefined;
+ field67?: number | undefined;
sabrForceMaxNetworkInterruptionDurationMs?: number | undefined;
audioTrackId?: string | undefined;
+ enableVoiceBoost?: boolean | undefined;
+ playbackAuthorization?: PlaybackAuthorization | undefined;
}
function createBaseClientAbrState(): ClientAbrState {
@@ -85,29 +92,31 @@ function createBaseClientAbrState(): ClientAbrState {
visibility: 0,
playbackRate: 0,
elapsedWallTimeMs: 0,
- mediaCapabilities: new Uint8Array(0),
+ mediaCapabilities: undefined,
timeSinceLastActionMs: 0,
enabledTrackTypesBitfield: 0,
maxPacingRate: 0,
playerState: 0,
drcEnabled: false,
- Jda: 0,
- qw: 0,
- Ky: 0,
+ field48: 0,
+ field50: 0,
+ field51: 0,
sabrReportRequestCancellationInfo: 0,
- l: false,
- G7: 0,
+ disableStreamingXhr: false,
+ field57: 0,
preferVp9: false,
- qj: 0,
- Hx: 0,
+ av1QualityThreshold: 0,
+ field60: 0,
isPrefetch: false,
- sabrSupportQualityConstraints: 0,
+ sabrSupportQualityConstraints: false,
sabrLicenseConstraint: new Uint8Array(0),
allowProximaLiveLatency: 0,
sabrForceProxima: 0,
- Tqb: 0,
+ field67: 0,
sabrForceMaxNetworkInterruptionDurationMs: 0,
audioTrackId: "",
+ enableVoiceBoost: false,
+ playbackAuthorization: undefined,
};
}
@@ -178,8 +187,8 @@ export const ClientAbrState: MessageFns = {
if (message.elapsedWallTimeMs !== undefined && message.elapsedWallTimeMs !== 0) {
writer.uint32(288).int64(message.elapsedWallTimeMs);
}
- if (message.mediaCapabilities !== undefined && message.mediaCapabilities.length !== 0) {
- writer.uint32(306).bytes(message.mediaCapabilities);
+ if (message.mediaCapabilities !== undefined) {
+ MediaCapabilities.encode(message.mediaCapabilities, writer.uint32(306).fork()).join();
}
if (message.timeSinceLastActionMs !== undefined && message.timeSinceLastActionMs !== 0) {
writer.uint32(312).int64(message.timeSinceLastActionMs);
@@ -196,38 +205,38 @@ export const ClientAbrState: MessageFns = {
if (message.drcEnabled !== undefined && message.drcEnabled !== false) {
writer.uint32(368).bool(message.drcEnabled);
}
- if (message.Jda !== undefined && message.Jda !== 0) {
- writer.uint32(384).int32(message.Jda);
+ if (message.field48 !== undefined && message.field48 !== 0) {
+ writer.uint32(384).int32(message.field48);
}
- if (message.qw !== undefined && message.qw !== 0) {
- writer.uint32(400).int32(message.qw);
+ if (message.field50 !== undefined && message.field50 !== 0) {
+ writer.uint32(400).int32(message.field50);
}
- if (message.Ky !== undefined && message.Ky !== 0) {
- writer.uint32(408).int32(message.Ky);
+ if (message.field51 !== undefined && message.field51 !== 0) {
+ writer.uint32(408).int32(message.field51);
}
if (message.sabrReportRequestCancellationInfo !== undefined && message.sabrReportRequestCancellationInfo !== 0) {
writer.uint32(432).int32(message.sabrReportRequestCancellationInfo);
}
- if (message.l !== undefined && message.l !== false) {
- writer.uint32(448).bool(message.l);
+ if (message.disableStreamingXhr !== undefined && message.disableStreamingXhr !== false) {
+ writer.uint32(448).bool(message.disableStreamingXhr);
}
- if (message.G7 !== undefined && message.G7 !== 0) {
- writer.uint32(456).int64(message.G7);
+ if (message.field57 !== undefined && message.field57 !== 0) {
+ writer.uint32(456).int64(message.field57);
}
if (message.preferVp9 !== undefined && message.preferVp9 !== false) {
writer.uint32(464).bool(message.preferVp9);
}
- if (message.qj !== undefined && message.qj !== 0) {
- writer.uint32(472).int32(message.qj);
+ if (message.av1QualityThreshold !== undefined && message.av1QualityThreshold !== 0) {
+ writer.uint32(472).int32(message.av1QualityThreshold);
}
- if (message.Hx !== undefined && message.Hx !== 0) {
- writer.uint32(480).int32(message.Hx);
+ if (message.field60 !== undefined && message.field60 !== 0) {
+ writer.uint32(480).int32(message.field60);
}
if (message.isPrefetch !== undefined && message.isPrefetch !== false) {
writer.uint32(488).bool(message.isPrefetch);
}
- if (message.sabrSupportQualityConstraints !== undefined && message.sabrSupportQualityConstraints !== 0) {
- writer.uint32(496).int32(message.sabrSupportQualityConstraints);
+ if (message.sabrSupportQualityConstraints !== undefined && message.sabrSupportQualityConstraints !== false) {
+ writer.uint32(496).bool(message.sabrSupportQualityConstraints);
}
if (message.sabrLicenseConstraint !== undefined && message.sabrLicenseConstraint.length !== 0) {
writer.uint32(506).bytes(message.sabrLicenseConstraint);
@@ -238,8 +247,8 @@ export const ClientAbrState: MessageFns = {
if (message.sabrForceProxima !== undefined && message.sabrForceProxima !== 0) {
writer.uint32(528).int32(message.sabrForceProxima);
}
- if (message.Tqb !== undefined && message.Tqb !== 0) {
- writer.uint32(536).int32(message.Tqb);
+ if (message.field67 !== undefined && message.field67 !== 0) {
+ writer.uint32(536).int32(message.field67);
}
if (
message.sabrForceMaxNetworkInterruptionDurationMs !== undefined &&
@@ -250,6 +259,12 @@ export const ClientAbrState: MessageFns = {
if (message.audioTrackId !== undefined && message.audioTrackId !== "") {
writer.uint32(554).string(message.audioTrackId);
}
+ if (message.enableVoiceBoost !== undefined && message.enableVoiceBoost !== false) {
+ writer.uint32(608).bool(message.enableVoiceBoost);
+ }
+ if (message.playbackAuthorization !== undefined) {
+ PlaybackAuthorization.encode(message.playbackAuthorization, writer.uint32(634).fork()).join();
+ }
return writer;
},
@@ -412,7 +427,7 @@ export const ClientAbrState: MessageFns = {
break;
}
- message.mediaCapabilities = reader.bytes();
+ message.mediaCapabilities = MediaCapabilities.decode(reader, reader.uint32());
continue;
case 39:
if (tag !== 312) {
@@ -454,21 +469,21 @@ export const ClientAbrState: MessageFns = {
break;
}
- message.Jda = reader.int32();
+ message.field48 = reader.int32();
continue;
case 50:
if (tag !== 400) {
break;
}
- message.qw = reader.int32();
+ message.field50 = reader.int32();
continue;
case 51:
if (tag !== 408) {
break;
}
- message.Ky = reader.int32();
+ message.field51 = reader.int32();
continue;
case 54:
if (tag !== 432) {
@@ -482,14 +497,14 @@ export const ClientAbrState: MessageFns = {
break;
}
- message.l = reader.bool();
+ message.disableStreamingXhr = reader.bool();
continue;
case 57:
if (tag !== 456) {
break;
}
- message.G7 = longToNumber(reader.int64());
+ message.field57 = longToNumber(reader.int64());
continue;
case 58:
if (tag !== 464) {
@@ -503,14 +518,14 @@ export const ClientAbrState: MessageFns = {
break;
}
- message.qj = reader.int32();
+ message.av1QualityThreshold = reader.int32();
continue;
case 60:
if (tag !== 480) {
break;
}
- message.Hx = reader.int32();
+ message.field60 = reader.int32();
continue;
case 61:
if (tag !== 488) {
@@ -524,7 +539,7 @@ export const ClientAbrState: MessageFns = {
break;
}
- message.sabrSupportQualityConstraints = reader.int32();
+ message.sabrSupportQualityConstraints = reader.bool();
continue;
case 63:
if (tag !== 506) {
@@ -552,7 +567,7 @@ export const ClientAbrState: MessageFns = {
break;
}
- message.Tqb = reader.int32();
+ message.field67 = reader.int32();
continue;
case 68:
if (tag !== 544) {
@@ -568,6 +583,20 @@ export const ClientAbrState: MessageFns = {
message.audioTrackId = reader.string();
continue;
+ case 76:
+ if (tag !== 608) {
+ break;
+ }
+
+ message.enableVoiceBoost = reader.bool();
+ continue;
+ case 79:
+ if (tag !== 634) {
+ break;
+ }
+
+ message.playbackAuthorization = PlaybackAuthorization.decode(reader, reader.uint32());
+ continue;
}
if ((tag & 7) === 4 || tag === 0) {
break;
diff --git a/protos/generated/video_streaming/crypto_params.ts b/protos/generated/video_streaming/crypto_params.ts
index 8798a80..fa7ddbe 100644
--- a/protos/generated/video_streaming/crypto_params.ts
+++ b/protos/generated/video_streaming/crypto_params.ts
@@ -6,20 +6,14 @@
/* eslint-disable */
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
+import { CompressionType } from "../misc/common.js";
export const protobufPackage = "video_streaming";
export interface CryptoParams {
hmac?: Uint8Array | undefined;
iv?: Uint8Array | undefined;
- compressionType?: CryptoParams_CompressionType | undefined;
-}
-
-export enum CryptoParams_CompressionType {
- NONE = 0,
- GZIP = 1,
- BROTLI = 2,
- UNRECOGNIZED = -1,
+ compressionType?: CompressionType | undefined;
}
function createBaseCryptoParams(): CryptoParams {
diff --git a/protos/generated/video_streaming/format_initialization_metadata.ts b/protos/generated/video_streaming/format_initialization_metadata.ts
index 24cdd09..c1a08c8 100644
--- a/protos/generated/video_streaming/format_initialization_metadata.ts
+++ b/protos/generated/video_streaming/format_initialization_metadata.ts
@@ -6,7 +6,7 @@
/* eslint-disable */
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
-import { FormatId, IndexRange, InitRange } from "../misc/common.js";
+import { FormatId, Range } from "../misc/common.js";
export const protobufPackage = "video_streaming";
@@ -16,11 +16,11 @@ export interface FormatInitializationMetadata {
endTimeMs?: number | undefined;
endSegmentNumber?: number | undefined;
mimeType?: string | undefined;
- initRange?: InitRange | undefined;
- indexRange?: IndexRange | undefined;
+ initRange?: Range | undefined;
+ indexRange?: Range | undefined;
field8?: number | undefined;
- durationMs?: number | undefined;
- field10?: number | undefined;
+ durationUnits?: number | undefined;
+ durationTimescale?: number | undefined;
}
function createBaseFormatInitializationMetadata(): FormatInitializationMetadata {
@@ -33,8 +33,8 @@ function createBaseFormatInitializationMetadata(): FormatInitializationMetadata
initRange: undefined,
indexRange: undefined,
field8: 0,
- durationMs: 0,
- field10: 0,
+ durationUnits: 0,
+ durationTimescale: 0,
};
}
@@ -47,7 +47,7 @@ export const FormatInitializationMetadata: MessageFns = {
+ encode(message: FormatSelectionConfig, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ writer.uint32(18).fork();
+ for (const v of message.itags) {
+ writer.int32(v);
+ }
+ writer.join();
+ if (message.videoId !== undefined && message.videoId !== "") {
+ writer.uint32(26).string(message.videoId);
+ }
+ if (message.resolution !== undefined && message.resolution !== 0) {
+ writer.uint32(32).int32(message.resolution);
+ }
+ return writer;
+ },
+
+ decode(input: BinaryReader | Uint8Array, length?: number): FormatSelectionConfig {
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
+ let end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseFormatSelectionConfig();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 2:
+ if (tag === 16) {
+ message.itags.push(reader.int32());
+
+ continue;
+ }
+
+ if (tag === 18) {
+ const end2 = reader.uint32() + reader.pos;
+ while (reader.pos < end2) {
+ message.itags.push(reader.int32());
+ }
+
+ continue;
+ }
+
+ break;
+ case 3:
+ if (tag !== 26) {
+ break;
+ }
+
+ message.videoId = reader.string();
+ continue;
+ case 4:
+ if (tag !== 32) {
+ break;
+ }
+
+ message.resolution = reader.int32();
+ continue;
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ reader.skip(tag & 7);
+ }
+ return message;
+ },
+};
+
+export interface MessageFns {
+ encode(message: T, writer?: BinaryWriter): BinaryWriter;
+ decode(input: BinaryReader | Uint8Array, length?: number): T;
+}
diff --git a/protos/generated/video_streaming/encrypted_player_request.ts b/protos/generated/video_streaming/innertube_request.ts
similarity index 73%
rename from protos/generated/video_streaming/encrypted_player_request.ts
rename to protos/generated/video_streaming/innertube_request.ts
index 8d23196..2ede380 100644
--- a/protos/generated/video_streaming/encrypted_player_request.ts
+++ b/protos/generated/video_streaming/innertube_request.ts
@@ -2,16 +2,16 @@
// versions:
// protoc-gen-ts_proto v2.2.0
// protoc v5.28.0
-// source: video_streaming/encrypted_player_request.proto
+// source: video_streaming/innertube_request.proto
/* eslint-disable */
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
export const protobufPackage = "video_streaming";
-export interface EncryptedPlayerRequest {
+export interface InnertubeRequest {
context?: Uint8Array | undefined;
- encryptedOnesiePlayerRequest?: Uint8Array | undefined;
+ encryptedOnesieInnertubeRequest?: Uint8Array | undefined;
encryptedClientKey?: Uint8Array | undefined;
iv?: Uint8Array | undefined;
hmac?: Uint8Array | undefined;
@@ -19,19 +19,19 @@ export interface EncryptedPlayerRequest {
serializeResponseAsJson?: boolean | undefined;
enableAdPlacementsPreroll?: boolean | undefined;
enableCompression?: boolean | undefined;
- ustreamerFlags?: EncryptedPlayerRequest_UstreamerFlags | undefined;
- unencryptedOnesiePlayerRequest?: Uint8Array | undefined;
+ ustreamerFlags?: UstreamerFlags | undefined;
+ unencryptedOnesieInnertubeRequest?: Uint8Array | undefined;
useJsonformatterToParsePlayerResponse?: boolean | undefined;
}
-export interface EncryptedPlayerRequest_UstreamerFlags {
+export interface UstreamerFlags {
sendVideoPlaybackConfig?: boolean | undefined;
}
-function createBaseEncryptedPlayerRequest(): EncryptedPlayerRequest {
+function createBaseInnertubeRequest(): InnertubeRequest {
return {
context: new Uint8Array(0),
- encryptedOnesiePlayerRequest: new Uint8Array(0),
+ encryptedOnesieInnertubeRequest: new Uint8Array(0),
encryptedClientKey: new Uint8Array(0),
iv: new Uint8Array(0),
hmac: new Uint8Array(0),
@@ -40,18 +40,18 @@ function createBaseEncryptedPlayerRequest(): EncryptedPlayerRequest {
enableAdPlacementsPreroll: false,
enableCompression: false,
ustreamerFlags: undefined,
- unencryptedOnesiePlayerRequest: new Uint8Array(0),
+ unencryptedOnesieInnertubeRequest: new Uint8Array(0),
useJsonformatterToParsePlayerResponse: false,
};
}
-export const EncryptedPlayerRequest: MessageFns = {
- encode(message: EncryptedPlayerRequest, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+export const InnertubeRequest: MessageFns = {
+ encode(message: InnertubeRequest, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
if (message.context !== undefined && message.context.length !== 0) {
writer.uint32(10).bytes(message.context);
}
- if (message.encryptedOnesiePlayerRequest !== undefined && message.encryptedOnesiePlayerRequest.length !== 0) {
- writer.uint32(18).bytes(message.encryptedOnesiePlayerRequest);
+ if (message.encryptedOnesieInnertubeRequest !== undefined && message.encryptedOnesieInnertubeRequest.length !== 0) {
+ writer.uint32(18).bytes(message.encryptedOnesieInnertubeRequest);
}
if (message.encryptedClientKey !== undefined && message.encryptedClientKey.length !== 0) {
writer.uint32(42).bytes(message.encryptedClientKey);
@@ -75,10 +75,12 @@ export const EncryptedPlayerRequest: MessageFns = {
writer.uint32(112).bool(message.enableCompression);
}
if (message.ustreamerFlags !== undefined) {
- EncryptedPlayerRequest_UstreamerFlags.encode(message.ustreamerFlags, writer.uint32(122).fork()).join();
+ UstreamerFlags.encode(message.ustreamerFlags, writer.uint32(122).fork()).join();
}
- if (message.unencryptedOnesiePlayerRequest !== undefined && message.unencryptedOnesiePlayerRequest.length !== 0) {
- writer.uint32(130).bytes(message.unencryptedOnesiePlayerRequest);
+ if (
+ message.unencryptedOnesieInnertubeRequest !== undefined && message.unencryptedOnesieInnertubeRequest.length !== 0
+ ) {
+ writer.uint32(130).bytes(message.unencryptedOnesieInnertubeRequest);
}
if (
message.useJsonformatterToParsePlayerResponse !== undefined &&
@@ -89,10 +91,10 @@ export const EncryptedPlayerRequest: MessageFns = {
return writer;
},
- decode(input: BinaryReader | Uint8Array, length?: number): EncryptedPlayerRequest {
+ decode(input: BinaryReader | Uint8Array, length?: number): InnertubeRequest {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
let end = length === undefined ? reader.len : reader.pos + length;
- const message = createBaseEncryptedPlayerRequest();
+ const message = createBaseInnertubeRequest();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
@@ -108,7 +110,7 @@ export const EncryptedPlayerRequest: MessageFns = {
break;
}
- message.encryptedOnesiePlayerRequest = reader.bytes();
+ message.encryptedOnesieInnertubeRequest = reader.bytes();
continue;
case 5:
if (tag !== 42) {
@@ -164,14 +166,14 @@ export const EncryptedPlayerRequest: MessageFns = {
break;
}
- message.ustreamerFlags = EncryptedPlayerRequest_UstreamerFlags.decode(reader, reader.uint32());
+ message.ustreamerFlags = UstreamerFlags.decode(reader, reader.uint32());
continue;
case 16:
if (tag !== 130) {
break;
}
- message.unencryptedOnesiePlayerRequest = reader.bytes();
+ message.unencryptedOnesieInnertubeRequest = reader.bytes();
continue;
case 17:
if (tag !== 136) {
@@ -190,22 +192,22 @@ export const EncryptedPlayerRequest: MessageFns = {
},
};
-function createBaseEncryptedPlayerRequest_UstreamerFlags(): EncryptedPlayerRequest_UstreamerFlags {
+function createBaseUstreamerFlags(): UstreamerFlags {
return { sendVideoPlaybackConfig: false };
}
-export const EncryptedPlayerRequest_UstreamerFlags: MessageFns = {
- encode(message: EncryptedPlayerRequest_UstreamerFlags, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+export const UstreamerFlags: MessageFns = {
+ encode(message: UstreamerFlags, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
if (message.sendVideoPlaybackConfig !== undefined && message.sendVideoPlaybackConfig !== false) {
writer.uint32(16).bool(message.sendVideoPlaybackConfig);
}
return writer;
},
- decode(input: BinaryReader | Uint8Array, length?: number): EncryptedPlayerRequest_UstreamerFlags {
+ decode(input: BinaryReader | Uint8Array, length?: number): UstreamerFlags {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
let end = length === undefined ? reader.len : reader.pos + length;
- const message = createBaseEncryptedPlayerRequest_UstreamerFlags();
+ const message = createBaseUstreamerFlags();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
diff --git a/protos/generated/video_streaming/media_header.ts b/protos/generated/video_streaming/media_header.ts
index 4e95187..86d0042 100644
--- a/protos/generated/video_streaming/media_header.ts
+++ b/protos/generated/video_streaming/media_header.ts
@@ -6,7 +6,7 @@
/* eslint-disable */
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
-import { FormatId } from "../misc/common.js";
+import { CompressionType, FormatId } from "../misc/common.js";
import { TimeRange } from "./time_range.js";
export const protobufPackage = "video_streaming";
@@ -18,22 +18,16 @@ export interface MediaHeader {
lmt?: number | undefined;
xtags?: string | undefined;
startRange?: number | undefined;
- compressionAlgorithm?: MediaHeader_CompressionAlgorithm | undefined;
+ compressionAlgorithm?: CompressionType | undefined;
isInitSeg?: boolean | undefined;
sequenceNumber?: number | undefined;
- field10?: number | undefined;
+ bitrateBps?: number | undefined;
startMs?: number | undefined;
durationMs?: number | undefined;
formatId?: FormatId | undefined;
contentLength?: number | undefined;
timeRange?: TimeRange | undefined;
-}
-
-export enum MediaHeader_CompressionAlgorithm {
- UNKNOWN = 0,
- NONE = 1,
- GZIP = 2,
- UNRECOGNIZED = -1,
+ sequenceLmt?: number | undefined;
}
function createBaseMediaHeader(): MediaHeader {
@@ -47,12 +41,13 @@ function createBaseMediaHeader(): MediaHeader {
compressionAlgorithm: 0,
isInitSeg: false,
sequenceNumber: 0,
- field10: 0,
+ bitrateBps: 0,
startMs: 0,
durationMs: 0,
formatId: undefined,
contentLength: 0,
timeRange: undefined,
+ sequenceLmt: 0,
};
}
@@ -85,8 +80,8 @@ export const MediaHeader: MessageFns = {
if (message.sequenceNumber !== undefined && message.sequenceNumber !== 0) {
writer.uint32(72).int64(message.sequenceNumber);
}
- if (message.field10 !== undefined && message.field10 !== 0) {
- writer.uint32(80).int64(message.field10);
+ if (message.bitrateBps !== undefined && message.bitrateBps !== 0) {
+ writer.uint32(80).int64(message.bitrateBps);
}
if (message.startMs !== undefined && message.startMs !== 0) {
writer.uint32(88).int64(message.startMs);
@@ -103,6 +98,9 @@ export const MediaHeader: MessageFns = {
if (message.timeRange !== undefined) {
TimeRange.encode(message.timeRange, writer.uint32(122).fork()).join();
}
+ if (message.sequenceLmt !== undefined && message.sequenceLmt !== 0) {
+ writer.uint32(128).uint64(message.sequenceLmt);
+ }
return writer;
},
@@ -181,7 +179,7 @@ export const MediaHeader: MessageFns = {
break;
}
- message.field10 = longToNumber(reader.int64());
+ message.bitrateBps = longToNumber(reader.int64());
continue;
case 11:
if (tag !== 88) {
@@ -218,6 +216,13 @@ export const MediaHeader: MessageFns = {
message.timeRange = TimeRange.decode(reader, reader.uint32());
continue;
+ case 16:
+ if (tag !== 128) {
+ break;
+ }
+
+ message.sequenceLmt = longToNumber(reader.uint64());
+ continue;
}
if ((tag & 7) === 4 || tag === 0) {
break;
diff --git a/protos/generated/video_streaming/next_request_policy.ts b/protos/generated/video_streaming/next_request_policy.ts
index 1791f43..5e2f6a1 100644
--- a/protos/generated/video_streaming/next_request_policy.ts
+++ b/protos/generated/video_streaming/next_request_policy.ts
@@ -13,7 +13,10 @@ export const protobufPackage = "video_streaming";
export interface NextRequestPolicy {
targetAudioReadaheadMs?: number | undefined;
targetVideoReadaheadMs?: number | undefined;
+ maxTimeSinceLastRequestMs?: number | undefined;
backoffTimeMs?: number | undefined;
+ minAudioReadaheadMs?: number | undefined;
+ minVideoReadaheadMs?: number | undefined;
playbackCookie?: PlaybackCookie | undefined;
videoId?: string | undefined;
}
@@ -22,7 +25,10 @@ function createBaseNextRequestPolicy(): NextRequestPolicy {
return {
targetAudioReadaheadMs: 0,
targetVideoReadaheadMs: 0,
+ maxTimeSinceLastRequestMs: 0,
backoffTimeMs: 0,
+ minAudioReadaheadMs: 0,
+ minVideoReadaheadMs: 0,
playbackCookie: undefined,
videoId: "",
};
@@ -36,9 +42,18 @@ export const NextRequestPolicy: MessageFns = {
if (message.targetVideoReadaheadMs !== undefined && message.targetVideoReadaheadMs !== 0) {
writer.uint32(16).int32(message.targetVideoReadaheadMs);
}
+ if (message.maxTimeSinceLastRequestMs !== undefined && message.maxTimeSinceLastRequestMs !== 0) {
+ writer.uint32(24).int32(message.maxTimeSinceLastRequestMs);
+ }
if (message.backoffTimeMs !== undefined && message.backoffTimeMs !== 0) {
writer.uint32(32).int32(message.backoffTimeMs);
}
+ if (message.minAudioReadaheadMs !== undefined && message.minAudioReadaheadMs !== 0) {
+ writer.uint32(40).int32(message.minAudioReadaheadMs);
+ }
+ if (message.minVideoReadaheadMs !== undefined && message.minVideoReadaheadMs !== 0) {
+ writer.uint32(48).int32(message.minVideoReadaheadMs);
+ }
if (message.playbackCookie !== undefined) {
PlaybackCookie.encode(message.playbackCookie, writer.uint32(58).fork()).join();
}
@@ -69,6 +84,13 @@ export const NextRequestPolicy: MessageFns = {
message.targetVideoReadaheadMs = reader.int32();
continue;
+ case 3:
+ if (tag !== 24) {
+ break;
+ }
+
+ message.maxTimeSinceLastRequestMs = reader.int32();
+ continue;
case 4:
if (tag !== 32) {
break;
@@ -76,6 +98,20 @@ export const NextRequestPolicy: MessageFns = {
message.backoffTimeMs = reader.int32();
continue;
+ case 5:
+ if (tag !== 40) {
+ break;
+ }
+
+ message.minAudioReadaheadMs = reader.int32();
+ continue;
+ case 6:
+ if (tag !== 48) {
+ break;
+ }
+
+ message.minVideoReadaheadMs = reader.int32();
+ continue;
case 7:
if (tag !== 58) {
break;
diff --git a/protos/generated/video_streaming/onesie_header.ts b/protos/generated/video_streaming/onesie_header.ts
index 8161acc..40916be 100644
--- a/protos/generated/video_streaming/onesie_header.ts
+++ b/protos/generated/video_streaming/onesie_header.ts
@@ -21,15 +21,15 @@ export interface OnesieHeader {
restrictedFormats: string[];
xtags?: string | undefined;
sequenceNumber?: number | undefined;
- field23?: OnesieHeader_Field23 | undefined;
- field34?: OnesieHeader_Field34 | undefined;
+ field23?: OnesieHeader_UnknownMessage1 | undefined;
+ field34?: OnesieHeader_UnknownMessage2 | undefined;
}
-export interface OnesieHeader_Field23 {
+export interface OnesieHeader_UnknownMessage1 {
videoId?: string | undefined;
}
-export interface OnesieHeader_Field34 {
+export interface OnesieHeader_UnknownMessage2 {
itagDenylist: string[];
}
@@ -79,10 +79,10 @@ export const OnesieHeader: MessageFns = {
writer.uint32(144).int64(message.sequenceNumber);
}
if (message.field23 !== undefined) {
- OnesieHeader_Field23.encode(message.field23, writer.uint32(186).fork()).join();
+ OnesieHeader_UnknownMessage1.encode(message.field23, writer.uint32(186).fork()).join();
}
if (message.field34 !== undefined) {
- OnesieHeader_Field34.encode(message.field34, writer.uint32(274).fork()).join();
+ OnesieHeader_UnknownMessage2.encode(message.field34, writer.uint32(274).fork()).join();
}
return writer;
},
@@ -162,14 +162,14 @@ export const OnesieHeader: MessageFns = {
break;
}
- message.field23 = OnesieHeader_Field23.decode(reader, reader.uint32());
+ message.field23 = OnesieHeader_UnknownMessage1.decode(reader, reader.uint32());
continue;
case 34:
if (tag !== 274) {
break;
}
- message.field34 = OnesieHeader_Field34.decode(reader, reader.uint32());
+ message.field34 = OnesieHeader_UnknownMessage2.decode(reader, reader.uint32());
continue;
}
if ((tag & 7) === 4 || tag === 0) {
@@ -181,22 +181,22 @@ export const OnesieHeader: MessageFns = {
},
};
-function createBaseOnesieHeader_Field23(): OnesieHeader_Field23 {
+function createBaseOnesieHeader_UnknownMessage1(): OnesieHeader_UnknownMessage1 {
return { videoId: "" };
}
-export const OnesieHeader_Field23: MessageFns = {
- encode(message: OnesieHeader_Field23, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+export const OnesieHeader_UnknownMessage1: MessageFns = {
+ encode(message: OnesieHeader_UnknownMessage1, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
if (message.videoId !== undefined && message.videoId !== "") {
writer.uint32(18).string(message.videoId);
}
return writer;
},
- decode(input: BinaryReader | Uint8Array, length?: number): OnesieHeader_Field23 {
+ decode(input: BinaryReader | Uint8Array, length?: number): OnesieHeader_UnknownMessage1 {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
let end = length === undefined ? reader.len : reader.pos + length;
- const message = createBaseOnesieHeader_Field23();
+ const message = createBaseOnesieHeader_UnknownMessage1();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
@@ -217,22 +217,22 @@ export const OnesieHeader_Field23: MessageFns = {
},
};
-function createBaseOnesieHeader_Field34(): OnesieHeader_Field34 {
+function createBaseOnesieHeader_UnknownMessage2(): OnesieHeader_UnknownMessage2 {
return { itagDenylist: [] };
}
-export const OnesieHeader_Field34: MessageFns = {
- encode(message: OnesieHeader_Field34, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+export const OnesieHeader_UnknownMessage2: MessageFns = {
+ encode(message: OnesieHeader_UnknownMessage2, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
for (const v of message.itagDenylist) {
writer.uint32(10).string(v!);
}
return writer;
},
- decode(input: BinaryReader | Uint8Array, length?: number): OnesieHeader_Field34 {
+ decode(input: BinaryReader | Uint8Array, length?: number): OnesieHeader_UnknownMessage2 {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
let end = length === undefined ? reader.len : reader.pos + length;
- const message = createBaseOnesieHeader_Field34();
+ const message = createBaseOnesieHeader_UnknownMessage2();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
diff --git a/protos/generated/video_streaming/onesie_player_request.ts b/protos/generated/video_streaming/onesie_innertube_request.ts
similarity index 86%
rename from protos/generated/video_streaming/onesie_player_request.ts
rename to protos/generated/video_streaming/onesie_innertube_request.ts
index 7c30852..2f4745a 100644
--- a/protos/generated/video_streaming/onesie_player_request.ts
+++ b/protos/generated/video_streaming/onesie_innertube_request.ts
@@ -2,7 +2,7 @@
// versions:
// protoc-gen-ts_proto v2.2.0
// protoc v5.28.0
-// source: video_streaming/onesie_player_request.proto
+// source: video_streaming/onesie_innertube_request.proto
/* eslint-disable */
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
@@ -10,7 +10,7 @@ import { HttpHeader } from "../misc/common.js";
export const protobufPackage = "video_streaming";
-export interface OnesiePlayerRequest {
+export interface OnesieInnertubeRequest {
url?: string | undefined;
headers: HttpHeader[];
body?: string | undefined;
@@ -18,12 +18,12 @@ export interface OnesiePlayerRequest {
skipResponseEncryption?: boolean | undefined;
}
-function createBaseOnesiePlayerRequest(): OnesiePlayerRequest {
+function createBaseOnesieInnertubeRequest(): OnesieInnertubeRequest {
return { url: "", headers: [], body: "", proxiedByTrustedBandaid: false, skipResponseEncryption: false };
}
-export const OnesiePlayerRequest: MessageFns = {
- encode(message: OnesiePlayerRequest, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+export const OnesieInnertubeRequest: MessageFns = {
+ encode(message: OnesieInnertubeRequest, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
if (message.url !== undefined && message.url !== "") {
writer.uint32(10).string(message.url);
}
@@ -42,10 +42,10 @@ export const OnesiePlayerRequest: MessageFns = {
return writer;
},
- decode(input: BinaryReader | Uint8Array, length?: number): OnesiePlayerRequest {
+ decode(input: BinaryReader | Uint8Array, length?: number): OnesieInnertubeRequest {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
let end = length === undefined ? reader.len : reader.pos + length;
- const message = createBaseOnesiePlayerRequest();
+ const message = createBaseOnesieInnertubeRequest();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
diff --git a/protos/generated/video_streaming/onesie_player_response.ts b/protos/generated/video_streaming/onesie_innertube_response.ts
similarity index 84%
rename from protos/generated/video_streaming/onesie_player_response.ts
rename to protos/generated/video_streaming/onesie_innertube_response.ts
index de8978f..bb54a1d 100644
--- a/protos/generated/video_streaming/onesie_player_response.ts
+++ b/protos/generated/video_streaming/onesie_innertube_response.ts
@@ -2,7 +2,7 @@
// versions:
// protoc-gen-ts_proto v2.2.0
// protoc v5.28.0
-// source: video_streaming/onesie_player_response.proto
+// source: video_streaming/onesie_innertube_response.proto
/* eslint-disable */
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
@@ -11,19 +11,19 @@ import { OnesieProxyStatus } from "./onesie_proxy_status.js";
export const protobufPackage = "video_streaming";
-export interface OnesiePlayerResponse {
+export interface OnesieInnertubeResponse {
onesieProxyStatus?: OnesieProxyStatus | undefined;
httpStatus?: number | undefined;
headers: HttpHeader[];
body?: Uint8Array | undefined;
}
-function createBaseOnesiePlayerResponse(): OnesiePlayerResponse {
+function createBaseOnesieInnertubeResponse(): OnesieInnertubeResponse {
return { onesieProxyStatus: 0, httpStatus: 0, headers: [], body: new Uint8Array(0) };
}
-export const OnesiePlayerResponse: MessageFns = {
- encode(message: OnesiePlayerResponse, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+export const OnesieInnertubeResponse: MessageFns = {
+ encode(message: OnesieInnertubeResponse, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
if (message.onesieProxyStatus !== undefined && message.onesieProxyStatus !== 0) {
writer.uint32(8).int32(message.onesieProxyStatus);
}
@@ -39,10 +39,10 @@ export const OnesiePlayerResponse: MessageFns = {
return writer;
},
- decode(input: BinaryReader | Uint8Array, length?: number): OnesiePlayerResponse {
+ decode(input: BinaryReader | Uint8Array, length?: number): OnesieInnertubeResponse {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
let end = length === undefined ? reader.len : reader.pos + length;
- const message = createBaseOnesiePlayerResponse();
+ const message = createBaseOnesieInnertubeResponse();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
diff --git a/protos/generated/video_streaming/onesie_request.ts b/protos/generated/video_streaming/onesie_request.ts
index 8d93c92..85425c2 100644
--- a/protos/generated/video_streaming/onesie_request.ts
+++ b/protos/generated/video_streaming/onesie_request.ts
@@ -9,7 +9,8 @@ import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
import { OnesieRequestTarget } from "../misc/common.js";
import { BufferedRange } from "./buffered_range.js";
import { ClientAbrState } from "./client_abr_state.js";
-import { EncryptedPlayerRequest } from "./encrypted_player_request.js";
+import { InnertubeRequest } from "./innertube_request.js";
+import { ReloadPlaybackParams } from "./reload_player_response.js";
import { StreamerContext } from "./streamer_context.js";
export const protobufPackage = "video_streaming";
@@ -17,7 +18,7 @@ export const protobufPackage = "video_streaming";
export interface OnesieRequest {
urls: string[];
clientAbrState?: ClientAbrState | undefined;
- playerRequest?: EncryptedPlayerRequest | undefined;
+ innertubeRequest?: InnertubeRequest | undefined;
onesieUstreamerConfig?: Uint8Array | undefined;
maxVp9Height?: number | undefined;
clientDisplayHeight?: number | undefined;
@@ -27,19 +28,21 @@ export interface OnesieRequest {
/** MLOnesieRequestTarget */
requestTarget?: OnesieRequestTarget | undefined;
bufferedRanges: BufferedRange[];
+ reloadPlaybackParams?: ReloadPlaybackParams | undefined;
}
function createBaseOnesieRequest(): OnesieRequest {
return {
urls: [],
clientAbrState: undefined,
- playerRequest: undefined,
+ innertubeRequest: undefined,
onesieUstreamerConfig: new Uint8Array(0),
maxVp9Height: 0,
clientDisplayHeight: 0,
streamerContext: undefined,
requestTarget: 0,
bufferedRanges: [],
+ reloadPlaybackParams: undefined,
};
}
@@ -51,8 +54,8 @@ export const OnesieRequest: MessageFns = {
if (message.clientAbrState !== undefined) {
ClientAbrState.encode(message.clientAbrState, writer.uint32(18).fork()).join();
}
- if (message.playerRequest !== undefined) {
- EncryptedPlayerRequest.encode(message.playerRequest, writer.uint32(26).fork()).join();
+ if (message.innertubeRequest !== undefined) {
+ InnertubeRequest.encode(message.innertubeRequest, writer.uint32(26).fork()).join();
}
if (message.onesieUstreamerConfig !== undefined && message.onesieUstreamerConfig.length !== 0) {
writer.uint32(34).bytes(message.onesieUstreamerConfig);
@@ -72,6 +75,9 @@ export const OnesieRequest: MessageFns = {
for (const v of message.bufferedRanges) {
BufferedRange.encode(v!, writer.uint32(114).fork()).join();
}
+ if (message.reloadPlaybackParams !== undefined) {
+ ReloadPlaybackParams.encode(message.reloadPlaybackParams, writer.uint32(122).fork()).join();
+ }
return writer;
},
@@ -101,7 +107,7 @@ export const OnesieRequest: MessageFns = {
break;
}
- message.playerRequest = EncryptedPlayerRequest.decode(reader, reader.uint32());
+ message.innertubeRequest = InnertubeRequest.decode(reader, reader.uint32());
continue;
case 4:
if (tag !== 34) {
@@ -145,6 +151,13 @@ export const OnesieRequest: MessageFns = {
message.bufferedRanges.push(BufferedRange.decode(reader, reader.uint32()));
continue;
+ case 15:
+ if (tag !== 122) {
+ break;
+ }
+
+ message.reloadPlaybackParams = ReloadPlaybackParams.decode(reader, reader.uint32());
+ continue;
}
if ((tag & 7) === 4 || tag === 0) {
break;
diff --git a/protos/generated/video_streaming/playback_cookie.ts b/protos/generated/video_streaming/playback_cookie.ts
index 7d19fc7..a8a192e 100644
--- a/protos/generated/video_streaming/playback_cookie.ts
+++ b/protos/generated/video_streaming/playback_cookie.ts
@@ -11,21 +11,21 @@ import { FormatId } from "../misc/common.js";
export const protobufPackage = "video_streaming";
export interface PlaybackCookie {
- /** Always 999999?? */
- field1?: number | undefined;
+ /** Always 999999 when resolution is set manually, or if the auto selected one is the max available resolution. */
+ resolution?: number | undefined;
field2?: number | undefined;
videoFmt?: FormatId | undefined;
audioFmt?: FormatId | undefined;
}
function createBasePlaybackCookie(): PlaybackCookie {
- return { field1: 0, field2: 0, videoFmt: undefined, audioFmt: undefined };
+ return { resolution: 0, field2: 0, videoFmt: undefined, audioFmt: undefined };
}
export const PlaybackCookie: MessageFns = {
encode(message: PlaybackCookie, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
- if (message.field1 !== undefined && message.field1 !== 0) {
- writer.uint32(8).int32(message.field1);
+ if (message.resolution !== undefined && message.resolution !== 0) {
+ writer.uint32(8).int32(message.resolution);
}
if (message.field2 !== undefined && message.field2 !== 0) {
writer.uint32(16).int32(message.field2);
@@ -51,7 +51,7 @@ export const PlaybackCookie: MessageFns = {
break;
}
- message.field1 = reader.int32();
+ message.resolution = reader.int32();
continue;
case 2:
if (tag !== 16) {
diff --git a/protos/generated/video_streaming/reload_player_response.ts b/protos/generated/video_streaming/reload_player_response.ts
new file mode 100644
index 0000000..49308ab
--- /dev/null
+++ b/protos/generated/video_streaming/reload_player_response.ts
@@ -0,0 +1,95 @@
+// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
+// versions:
+// protoc-gen-ts_proto v2.2.0
+// protoc v5.28.0
+// source: video_streaming/reload_player_response.proto
+
+/* eslint-disable */
+import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
+
+export const protobufPackage = "video_streaming";
+
+export interface ReloadPlaybackParams {
+ token?: string | undefined;
+}
+
+export interface ReloadPlaybackContext {
+ reloadPlaybackParams?: ReloadPlaybackParams | undefined;
+}
+
+function createBaseReloadPlaybackParams(): ReloadPlaybackParams {
+ return { token: "" };
+}
+
+export const ReloadPlaybackParams: MessageFns = {
+ encode(message: ReloadPlaybackParams, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ if (message.token !== undefined && message.token !== "") {
+ writer.uint32(10).string(message.token);
+ }
+ return writer;
+ },
+
+ decode(input: BinaryReader | Uint8Array, length?: number): ReloadPlaybackParams {
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
+ let end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseReloadPlaybackParams();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1:
+ if (tag !== 10) {
+ break;
+ }
+
+ message.token = reader.string();
+ continue;
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ reader.skip(tag & 7);
+ }
+ return message;
+ },
+};
+
+function createBaseReloadPlaybackContext(): ReloadPlaybackContext {
+ return { reloadPlaybackParams: undefined };
+}
+
+export const ReloadPlaybackContext: MessageFns = {
+ encode(message: ReloadPlaybackContext, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ if (message.reloadPlaybackParams !== undefined) {
+ ReloadPlaybackParams.encode(message.reloadPlaybackParams, writer.uint32(10).fork()).join();
+ }
+ return writer;
+ },
+
+ decode(input: BinaryReader | Uint8Array, length?: number): ReloadPlaybackContext {
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
+ let end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseReloadPlaybackContext();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1:
+ if (tag !== 10) {
+ break;
+ }
+
+ message.reloadPlaybackParams = ReloadPlaybackParams.decode(reader, reader.uint32());
+ continue;
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ reader.skip(tag & 7);
+ }
+ return message;
+ },
+};
+
+export interface MessageFns {
+ encode(message: T, writer?: BinaryWriter): BinaryWriter;
+ decode(input: BinaryReader | Uint8Array, length?: number): T;
+}
diff --git a/protos/generated/video_streaming/request_identifier.ts b/protos/generated/video_streaming/request_identifier.ts
new file mode 100644
index 0000000..9622d7a
--- /dev/null
+++ b/protos/generated/video_streaming/request_identifier.ts
@@ -0,0 +1,55 @@
+// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
+// versions:
+// protoc-gen-ts_proto v2.2.0
+// protoc v5.28.0
+// source: video_streaming/request_identifier.proto
+
+/* eslint-disable */
+import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
+
+export const protobufPackage = "video_streaming";
+
+export interface RequestIdentifier {
+ token?: string | undefined;
+}
+
+function createBaseRequestIdentifier(): RequestIdentifier {
+ return { token: "" };
+}
+
+export const RequestIdentifier: MessageFns = {
+ encode(message: RequestIdentifier, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ if (message.token !== undefined && message.token !== "") {
+ writer.uint32(10).string(message.token);
+ }
+ return writer;
+ },
+
+ decode(input: BinaryReader | Uint8Array, length?: number): RequestIdentifier {
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
+ let end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseRequestIdentifier();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1:
+ if (tag !== 10) {
+ break;
+ }
+
+ message.token = reader.string();
+ continue;
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ reader.skip(tag & 7);
+ }
+ return message;
+ },
+};
+
+export interface MessageFns {
+ encode(message: T, writer?: BinaryWriter): BinaryWriter;
+ decode(input: BinaryReader | Uint8Array, length?: number): T;
+}
diff --git a/protos/generated/video_streaming/sabr_context_sending_policy.ts b/protos/generated/video_streaming/sabr_context_sending_policy.ts
new file mode 100644
index 0000000..26fc481
--- /dev/null
+++ b/protos/generated/video_streaming/sabr_context_sending_policy.ts
@@ -0,0 +1,113 @@
+// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
+// versions:
+// protoc-gen-ts_proto v2.2.0
+// protoc v5.28.0
+// source: video_streaming/sabr_context_sending_policy.proto
+
+/* eslint-disable */
+import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
+
+export const protobufPackage = "video_streaming";
+
+export interface SabrContextSendingPolicy {
+ startPolicy: number[];
+ stopPolicy: number[];
+ discardPolicy: number[];
+}
+
+function createBaseSabrContextSendingPolicy(): SabrContextSendingPolicy {
+ return { startPolicy: [], stopPolicy: [], discardPolicy: [] };
+}
+
+export const SabrContextSendingPolicy: MessageFns = {
+ encode(message: SabrContextSendingPolicy, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ writer.uint32(10).fork();
+ for (const v of message.startPolicy) {
+ writer.int32(v);
+ }
+ writer.join();
+ writer.uint32(18).fork();
+ for (const v of message.stopPolicy) {
+ writer.int32(v);
+ }
+ writer.join();
+ writer.uint32(26).fork();
+ for (const v of message.discardPolicy) {
+ writer.int32(v);
+ }
+ writer.join();
+ return writer;
+ },
+
+ decode(input: BinaryReader | Uint8Array, length?: number): SabrContextSendingPolicy {
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
+ let end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseSabrContextSendingPolicy();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1:
+ if (tag === 8) {
+ message.startPolicy.push(reader.int32());
+
+ continue;
+ }
+
+ if (tag === 10) {
+ const end2 = reader.uint32() + reader.pos;
+ while (reader.pos < end2) {
+ message.startPolicy.push(reader.int32());
+ }
+
+ continue;
+ }
+
+ break;
+ case 2:
+ if (tag === 16) {
+ message.stopPolicy.push(reader.int32());
+
+ continue;
+ }
+
+ if (tag === 18) {
+ const end2 = reader.uint32() + reader.pos;
+ while (reader.pos < end2) {
+ message.stopPolicy.push(reader.int32());
+ }
+
+ continue;
+ }
+
+ break;
+ case 3:
+ if (tag === 24) {
+ message.discardPolicy.push(reader.int32());
+
+ continue;
+ }
+
+ if (tag === 26) {
+ const end2 = reader.uint32() + reader.pos;
+ while (reader.pos < end2) {
+ message.discardPolicy.push(reader.int32());
+ }
+
+ continue;
+ }
+
+ break;
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ reader.skip(tag & 7);
+ }
+ return message;
+ },
+};
+
+export interface MessageFns {
+ encode(message: T, writer?: BinaryWriter): BinaryWriter;
+ decode(input: BinaryReader | Uint8Array, length?: number): T;
+}
diff --git a/protos/generated/video_streaming/sabr_context_update.ts b/protos/generated/video_streaming/sabr_context_update.ts
new file mode 100644
index 0000000..4862df9
--- /dev/null
+++ b/protos/generated/video_streaming/sabr_context_update.ts
@@ -0,0 +1,306 @@
+// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
+// versions:
+// protoc-gen-ts_proto v2.2.0
+// protoc v5.28.0
+// source: video_streaming/sabr_context_update.proto
+
+/* eslint-disable */
+import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
+
+export const protobufPackage = "video_streaming";
+
+export interface SabrContextUpdate {
+ type?: number | undefined;
+ scope?: SabrContextUpdate_SabrContextScope | undefined;
+ value?: Uint8Array | undefined;
+ sendByDefault?: boolean | undefined;
+ writePolicy?: SabrContextUpdate_SabrContextWritePolicy | undefined;
+}
+
+export enum SabrContextUpdate_SabrContextScope {
+ UNKNOWN = 0,
+ PLAYBACK = 1,
+ REQUEST = 2,
+ WATCH_ENDPOINT = 3,
+ CONTENT_ADS = 4,
+ UNRECOGNIZED = -1,
+}
+
+export enum SabrContextUpdate_SabrContextWritePolicy {
+ UNSPECIFIED = 0,
+ OVERWRITE = 1,
+ KEEP_EXISTING = 2,
+ UNRECOGNIZED = -1,
+}
+
+/** For debugging */
+export interface SabrContextValue {
+ timing?: SabrContextValue_TimingInfo | undefined;
+ signature?: Uint8Array | undefined;
+ field5?: number | undefined;
+}
+
+export interface SabrContextValue_ContentInfo {
+ /** Looks like a content identifier of some sort "mQxOaLekHJ2f-LAPtq3hwQ4" */
+ contentId?:
+ | string
+ | undefined;
+ /** Value of 1 observed (unsure what it truly means/is) */
+ contentType?: number | undefined;
+}
+
+export interface SabrContextValue_TimingInfo {
+ timestampMs?: number | undefined;
+ durationMs?: number | undefined;
+ content?: SabrContextValue_ContentInfo | undefined;
+}
+
+function createBaseSabrContextUpdate(): SabrContextUpdate {
+ return { type: 0, scope: 0, value: new Uint8Array(0), sendByDefault: false, writePolicy: 0 };
+}
+
+export const SabrContextUpdate: MessageFns = {
+ encode(message: SabrContextUpdate, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ if (message.type !== undefined && message.type !== 0) {
+ writer.uint32(8).int32(message.type);
+ }
+ if (message.scope !== undefined && message.scope !== 0) {
+ writer.uint32(16).int32(message.scope);
+ }
+ if (message.value !== undefined && message.value.length !== 0) {
+ writer.uint32(26).bytes(message.value);
+ }
+ if (message.sendByDefault !== undefined && message.sendByDefault !== false) {
+ writer.uint32(32).bool(message.sendByDefault);
+ }
+ if (message.writePolicy !== undefined && message.writePolicy !== 0) {
+ writer.uint32(40).int32(message.writePolicy);
+ }
+ return writer;
+ },
+
+ decode(input: BinaryReader | Uint8Array, length?: number): SabrContextUpdate {
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
+ let end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseSabrContextUpdate();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1:
+ if (tag !== 8) {
+ break;
+ }
+
+ message.type = reader.int32();
+ continue;
+ case 2:
+ if (tag !== 16) {
+ break;
+ }
+
+ message.scope = reader.int32() as any;
+ continue;
+ case 3:
+ if (tag !== 26) {
+ break;
+ }
+
+ message.value = reader.bytes();
+ continue;
+ case 4:
+ if (tag !== 32) {
+ break;
+ }
+
+ message.sendByDefault = reader.bool();
+ continue;
+ case 5:
+ if (tag !== 40) {
+ break;
+ }
+
+ message.writePolicy = reader.int32() as any;
+ continue;
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ reader.skip(tag & 7);
+ }
+ return message;
+ },
+};
+
+function createBaseSabrContextValue(): SabrContextValue {
+ return { timing: undefined, signature: new Uint8Array(0), field5: 0 };
+}
+
+export const SabrContextValue: MessageFns = {
+ encode(message: SabrContextValue, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ if (message.timing !== undefined) {
+ SabrContextValue_TimingInfo.encode(message.timing, writer.uint32(10).fork()).join();
+ }
+ if (message.signature !== undefined && message.signature.length !== 0) {
+ writer.uint32(18).bytes(message.signature);
+ }
+ if (message.field5 !== undefined && message.field5 !== 0) {
+ writer.uint32(40).int32(message.field5);
+ }
+ return writer;
+ },
+
+ decode(input: BinaryReader | Uint8Array, length?: number): SabrContextValue {
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
+ let end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseSabrContextValue();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1:
+ if (tag !== 10) {
+ break;
+ }
+
+ message.timing = SabrContextValue_TimingInfo.decode(reader, reader.uint32());
+ continue;
+ case 2:
+ if (tag !== 18) {
+ break;
+ }
+
+ message.signature = reader.bytes();
+ continue;
+ case 5:
+ if (tag !== 40) {
+ break;
+ }
+
+ message.field5 = reader.int32();
+ continue;
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ reader.skip(tag & 7);
+ }
+ return message;
+ },
+};
+
+function createBaseSabrContextValue_ContentInfo(): SabrContextValue_ContentInfo {
+ return { contentId: "", contentType: 0 };
+}
+
+export const SabrContextValue_ContentInfo: MessageFns = {
+ encode(message: SabrContextValue_ContentInfo, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ if (message.contentId !== undefined && message.contentId !== "") {
+ writer.uint32(10).string(message.contentId);
+ }
+ if (message.contentType !== undefined && message.contentType !== 0) {
+ writer.uint32(16).int32(message.contentType);
+ }
+ return writer;
+ },
+
+ decode(input: BinaryReader | Uint8Array, length?: number): SabrContextValue_ContentInfo {
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
+ let end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseSabrContextValue_ContentInfo();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1:
+ if (tag !== 10) {
+ break;
+ }
+
+ message.contentId = reader.string();
+ continue;
+ case 2:
+ if (tag !== 16) {
+ break;
+ }
+
+ message.contentType = reader.int32();
+ continue;
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ reader.skip(tag & 7);
+ }
+ return message;
+ },
+};
+
+function createBaseSabrContextValue_TimingInfo(): SabrContextValue_TimingInfo {
+ return { timestampMs: 0, durationMs: 0, content: undefined };
+}
+
+export const SabrContextValue_TimingInfo: MessageFns = {
+ encode(message: SabrContextValue_TimingInfo, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ if (message.timestampMs !== undefined && message.timestampMs !== 0) {
+ writer.uint32(8).int64(message.timestampMs);
+ }
+ if (message.durationMs !== undefined && message.durationMs !== 0) {
+ writer.uint32(16).int32(message.durationMs);
+ }
+ if (message.content !== undefined) {
+ SabrContextValue_ContentInfo.encode(message.content, writer.uint32(26).fork()).join();
+ }
+ return writer;
+ },
+
+ decode(input: BinaryReader | Uint8Array, length?: number): SabrContextValue_TimingInfo {
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
+ let end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseSabrContextValue_TimingInfo();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1:
+ if (tag !== 8) {
+ break;
+ }
+
+ message.timestampMs = longToNumber(reader.int64());
+ continue;
+ case 2:
+ if (tag !== 16) {
+ break;
+ }
+
+ message.durationMs = reader.int32();
+ continue;
+ case 3:
+ if (tag !== 26) {
+ break;
+ }
+
+ message.content = SabrContextValue_ContentInfo.decode(reader, reader.uint32());
+ continue;
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ reader.skip(tag & 7);
+ }
+ return message;
+ },
+};
+
+function longToNumber(int64: { toString(): string }): number {
+ const num = globalThis.Number(int64.toString());
+ if (num > globalThis.Number.MAX_SAFE_INTEGER) {
+ throw new globalThis.Error("Value is larger than Number.MAX_SAFE_INTEGER");
+ }
+ if (num < globalThis.Number.MIN_SAFE_INTEGER) {
+ throw new globalThis.Error("Value is smaller than Number.MIN_SAFE_INTEGER");
+ }
+ return num;
+}
+
+export interface MessageFns {
+ encode(message: T, writer?: BinaryWriter): BinaryWriter;
+ decode(input: BinaryReader | Uint8Array, length?: number): T;
+}
diff --git a/protos/generated/video_streaming/snackbar_message.ts b/protos/generated/video_streaming/snackbar_message.ts
new file mode 100644
index 0000000..ca2add6
--- /dev/null
+++ b/protos/generated/video_streaming/snackbar_message.ts
@@ -0,0 +1,55 @@
+// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
+// versions:
+// protoc-gen-ts_proto v2.2.0
+// protoc v5.28.0
+// source: video_streaming/snackbar_message.proto
+
+/* eslint-disable */
+import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
+
+export const protobufPackage = "video_streaming";
+
+export interface SnackbarMessage {
+ id?: number | undefined;
+}
+
+function createBaseSnackbarMessage(): SnackbarMessage {
+ return { id: 0 };
+}
+
+export const SnackbarMessage: MessageFns = {
+ encode(message: SnackbarMessage, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ if (message.id !== undefined && message.id !== 0) {
+ writer.uint32(8).int32(message.id);
+ }
+ return writer;
+ },
+
+ decode(input: BinaryReader | Uint8Array, length?: number): SnackbarMessage {
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
+ let end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseSnackbarMessage();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1:
+ if (tag !== 8) {
+ break;
+ }
+
+ message.id = reader.int32();
+ continue;
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ reader.skip(tag & 7);
+ }
+ return message;
+ },
+};
+
+export interface MessageFns {
+ encode(message: T, writer?: BinaryWriter): BinaryWriter;
+ decode(input: BinaryReader | Uint8Array, length?: number): T;
+}
diff --git a/protos/generated/video_streaming/stream_protection_status.ts b/protos/generated/video_streaming/stream_protection_status.ts
index 8e73c53..1bc809f 100644
--- a/protos/generated/video_streaming/stream_protection_status.ts
+++ b/protos/generated/video_streaming/stream_protection_status.ts
@@ -11,11 +11,11 @@ export const protobufPackage = "video_streaming";
export interface StreamProtectionStatus {
status?: number | undefined;
- field2?: number | undefined;
+ maxRetries?: number | undefined;
}
function createBaseStreamProtectionStatus(): StreamProtectionStatus {
- return { status: 0, field2: 0 };
+ return { status: 0, maxRetries: 0 };
}
export const StreamProtectionStatus: MessageFns = {
@@ -23,8 +23,8 @@ export const StreamProtectionStatus: MessageFns = {
if (message.status !== undefined && message.status !== 0) {
writer.uint32(8).int32(message.status);
}
- if (message.field2 !== undefined && message.field2 !== 0) {
- writer.uint32(16).int32(message.field2);
+ if (message.maxRetries !== undefined && message.maxRetries !== 0) {
+ writer.uint32(16).int32(message.maxRetries);
}
return writer;
},
@@ -48,7 +48,7 @@ export const StreamProtectionStatus: MessageFns = {
break;
}
- message.field2 = reader.int32();
+ message.maxRetries = reader.int32();
continue;
}
if ((tag & 7) === 4 || tag === 0) {
diff --git a/protos/generated/video_streaming/streamer_context.ts b/protos/generated/video_streaming/streamer_context.ts
index f7c8941..a1d34f8 100644
--- a/protos/generated/video_streaming/streamer_context.ts
+++ b/protos/generated/video_streaming/streamer_context.ts
@@ -13,11 +13,11 @@ export interface StreamerContext {
clientInfo?: StreamerContext_ClientInfo | undefined;
poToken?: Uint8Array | undefined;
playbackCookie?: Uint8Array | undefined;
- gp?: Uint8Array | undefined;
- field5: StreamerContext_Fqa[];
- field6: number[];
+ field4?: Uint8Array | undefined;
+ sabrContexts: StreamerContext_SabrContext[];
+ unsentSabrContexts: number[];
field7?: string | undefined;
- field8?: StreamerContext_Gqa | undefined;
+ field8?: StreamerContext_UnknownMessage1 | undefined;
}
export enum StreamerContext_ClientFormFactor {
@@ -65,17 +65,17 @@ export interface StreamerContext_GLDeviceInfo {
glEsVersionMinor?: number | undefined;
}
-export interface StreamerContext_Fqa {
+export interface StreamerContext_SabrContext {
type?: number | undefined;
value?: Uint8Array | undefined;
}
-export interface StreamerContext_Gqa {
+export interface StreamerContext_UnknownMessage1 {
field1?: Uint8Array | undefined;
- field2?: StreamerContext_Gqa_Hqa | undefined;
+ field2?: StreamerContext_UnknownMessage1_UnknownInnerMessage1 | undefined;
}
-export interface StreamerContext_Gqa_Hqa {
+export interface StreamerContext_UnknownMessage1_UnknownInnerMessage1 {
code?: number | undefined;
message?: string | undefined;
}
@@ -85,9 +85,9 @@ function createBaseStreamerContext(): StreamerContext {
clientInfo: undefined,
poToken: new Uint8Array(0),
playbackCookie: new Uint8Array(0),
- gp: new Uint8Array(0),
- field5: [],
- field6: [],
+ field4: new Uint8Array(0),
+ sabrContexts: [],
+ unsentSabrContexts: [],
field7: "",
field8: undefined,
};
@@ -104,14 +104,14 @@ export const StreamerContext: MessageFns = {
if (message.playbackCookie !== undefined && message.playbackCookie.length !== 0) {
writer.uint32(26).bytes(message.playbackCookie);
}
- if (message.gp !== undefined && message.gp.length !== 0) {
- writer.uint32(34).bytes(message.gp);
+ if (message.field4 !== undefined && message.field4.length !== 0) {
+ writer.uint32(34).bytes(message.field4);
}
- for (const v of message.field5) {
- StreamerContext_Fqa.encode(v!, writer.uint32(42).fork()).join();
+ for (const v of message.sabrContexts) {
+ StreamerContext_SabrContext.encode(v!, writer.uint32(42).fork()).join();
}
writer.uint32(50).fork();
- for (const v of message.field6) {
+ for (const v of message.unsentSabrContexts) {
writer.int32(v);
}
writer.join();
@@ -119,7 +119,7 @@ export const StreamerContext: MessageFns = {
writer.uint32(58).string(message.field7);
}
if (message.field8 !== undefined) {
- StreamerContext_Gqa.encode(message.field8, writer.uint32(66).fork()).join();
+ StreamerContext_UnknownMessage1.encode(message.field8, writer.uint32(66).fork()).join();
}
return writer;
},
@@ -157,18 +157,18 @@ export const StreamerContext: MessageFns = {
break;
}
- message.gp = reader.bytes();
+ message.field4 = reader.bytes();
continue;
case 5:
if (tag !== 42) {
break;
}
- message.field5.push(StreamerContext_Fqa.decode(reader, reader.uint32()));
+ message.sabrContexts.push(StreamerContext_SabrContext.decode(reader, reader.uint32()));
continue;
case 6:
if (tag === 48) {
- message.field6.push(reader.int32());
+ message.unsentSabrContexts.push(reader.int32());
continue;
}
@@ -176,7 +176,7 @@ export const StreamerContext: MessageFns = {
if (tag === 50) {
const end2 = reader.uint32() + reader.pos;
while (reader.pos < end2) {
- message.field6.push(reader.int32());
+ message.unsentSabrContexts.push(reader.int32());
}
continue;
@@ -195,7 +195,7 @@ export const StreamerContext: MessageFns = {
break;
}
- message.field8 = StreamerContext_Gqa.decode(reader, reader.uint32());
+ message.field8 = StreamerContext_UnknownMessage1.decode(reader, reader.uint32());
continue;
}
if ((tag & 7) === 4 || tag === 0) {
@@ -543,12 +543,12 @@ export const StreamerContext_GLDeviceInfo: MessageFns = {
- encode(message: StreamerContext_Fqa, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+export const StreamerContext_SabrContext: MessageFns = {
+ encode(message: StreamerContext_SabrContext, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
if (message.type !== undefined && message.type !== 0) {
writer.uint32(8).int32(message.type);
}
@@ -558,10 +558,10 @@ export const StreamerContext_Fqa: MessageFns = {
return writer;
},
- decode(input: BinaryReader | Uint8Array, length?: number): StreamerContext_Fqa {
+ decode(input: BinaryReader | Uint8Array, length?: number): StreamerContext_SabrContext {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
let end = length === undefined ? reader.len : reader.pos + length;
- const message = createBaseStreamerContext_Fqa();
+ const message = createBaseStreamerContext_SabrContext();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
@@ -589,25 +589,25 @@ export const StreamerContext_Fqa: MessageFns = {
},
};
-function createBaseStreamerContext_Gqa(): StreamerContext_Gqa {
+function createBaseStreamerContext_UnknownMessage1(): StreamerContext_UnknownMessage1 {
return { field1: new Uint8Array(0), field2: undefined };
}
-export const StreamerContext_Gqa: MessageFns = {
- encode(message: StreamerContext_Gqa, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+export const StreamerContext_UnknownMessage1: MessageFns = {
+ encode(message: StreamerContext_UnknownMessage1, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
if (message.field1 !== undefined && message.field1.length !== 0) {
writer.uint32(10).bytes(message.field1);
}
if (message.field2 !== undefined) {
- StreamerContext_Gqa_Hqa.encode(message.field2, writer.uint32(18).fork()).join();
+ StreamerContext_UnknownMessage1_UnknownInnerMessage1.encode(message.field2, writer.uint32(18).fork()).join();
}
return writer;
},
- decode(input: BinaryReader | Uint8Array, length?: number): StreamerContext_Gqa {
+ decode(input: BinaryReader | Uint8Array, length?: number): StreamerContext_UnknownMessage1 {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
let end = length === undefined ? reader.len : reader.pos + length;
- const message = createBaseStreamerContext_Gqa();
+ const message = createBaseStreamerContext_UnknownMessage1();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
@@ -623,7 +623,7 @@ export const StreamerContext_Gqa: MessageFns = {
break;
}
- message.field2 = StreamerContext_Gqa_Hqa.decode(reader, reader.uint32());
+ message.field2 = StreamerContext_UnknownMessage1_UnknownInnerMessage1.decode(reader, reader.uint32());
continue;
}
if ((tag & 7) === 4 || tag === 0) {
@@ -635,12 +635,17 @@ export const StreamerContext_Gqa: MessageFns = {
},
};
-function createBaseStreamerContext_Gqa_Hqa(): StreamerContext_Gqa_Hqa {
+function createBaseStreamerContext_UnknownMessage1_UnknownInnerMessage1(): StreamerContext_UnknownMessage1_UnknownInnerMessage1 {
return { code: 0, message: "" };
}
-export const StreamerContext_Gqa_Hqa: MessageFns = {
- encode(message: StreamerContext_Gqa_Hqa, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+export const StreamerContext_UnknownMessage1_UnknownInnerMessage1: MessageFns<
+ StreamerContext_UnknownMessage1_UnknownInnerMessage1
+> = {
+ encode(
+ message: StreamerContext_UnknownMessage1_UnknownInnerMessage1,
+ writer: BinaryWriter = new BinaryWriter(),
+ ): BinaryWriter {
if (message.code !== undefined && message.code !== 0) {
writer.uint32(8).int32(message.code);
}
@@ -650,10 +655,10 @@ export const StreamerContext_Gqa_Hqa: MessageFns = {
return writer;
},
- decode(input: BinaryReader | Uint8Array, length?: number): StreamerContext_Gqa_Hqa {
+ decode(input: BinaryReader | Uint8Array, length?: number): StreamerContext_UnknownMessage1_UnknownInnerMessage1 {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
let end = length === undefined ? reader.len : reader.pos + length;
- const message = createBaseStreamerContext_Gqa_Hqa();
+ const message = createBaseStreamerContext_UnknownMessage1_UnknownInnerMessage1();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
diff --git a/protos/generated/video_streaming/time_range.ts b/protos/generated/video_streaming/time_range.ts
index 03c1116..a62bb69 100644
--- a/protos/generated/video_streaming/time_range.ts
+++ b/protos/generated/video_streaming/time_range.ts
@@ -10,22 +10,22 @@ import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
export const protobufPackage = "video_streaming";
export interface TimeRange {
- start?: number | undefined;
- duration?: number | undefined;
+ startTicks?: number | undefined;
+ durationTicks?: number | undefined;
timescale?: number | undefined;
}
function createBaseTimeRange(): TimeRange {
- return { start: 0, duration: 0, timescale: 0 };
+ return { startTicks: 0, durationTicks: 0, timescale: 0 };
}
export const TimeRange: MessageFns = {
encode(message: TimeRange, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
- if (message.start !== undefined && message.start !== 0) {
- writer.uint32(8).int64(message.start);
+ if (message.startTicks !== undefined && message.startTicks !== 0) {
+ writer.uint32(8).int64(message.startTicks);
}
- if (message.duration !== undefined && message.duration !== 0) {
- writer.uint32(16).int64(message.duration);
+ if (message.durationTicks !== undefined && message.durationTicks !== 0) {
+ writer.uint32(16).int64(message.durationTicks);
}
if (message.timescale !== undefined && message.timescale !== 0) {
writer.uint32(24).int32(message.timescale);
@@ -45,14 +45,14 @@ export const TimeRange: MessageFns = {
break;
}
- message.start = longToNumber(reader.int64());
+ message.startTicks = longToNumber(reader.int64());
continue;
case 2:
if (tag !== 16) {
break;
}
- message.duration = longToNumber(reader.int64());
+ message.durationTicks = longToNumber(reader.int64());
continue;
case 3:
if (tag !== 24) {
diff --git a/protos/generated/video_streaming/ump_part_id.ts b/protos/generated/video_streaming/ump_part_id.ts
new file mode 100644
index 0000000..bb35361
--- /dev/null
+++ b/protos/generated/video_streaming/ump_part_id.ts
@@ -0,0 +1,67 @@
+// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
+// versions:
+// protoc-gen-ts_proto v2.2.0
+// protoc v5.28.0
+// source: video_streaming/ump_part_id.proto
+
+/* eslint-disable */
+
+export const protobufPackage = "video_streaming";
+
+export enum UMPPartId {
+ UNKNOWN = 0,
+ ONESIE_HEADER = 10,
+ ONESIE_DATA = 11,
+ ONESIE_ENCRYPTED_MEDIA = 12,
+ /** MEDIA_HEADER - Header for a media segment; includes sequence and timing information. */
+ MEDIA_HEADER = 20,
+ /** MEDIA - Chunk of media segment data. */
+ MEDIA = 21,
+ /** MEDIA_END - Indicates end of media segment; finalizes segment processing. */
+ MEDIA_END = 22,
+ CONFIG = 30,
+ LIVE_METADATA = 31,
+ HOSTNAME_CHANGE_HINT_DEPRECATED = 32,
+ LIVE_METADATA_PROMISE = 33,
+ LIVE_METADATA_PROMISE_CANCELLATION = 34,
+ /** NEXT_REQUEST_POLICY - Server's policy for the next request; includes backoff time and playback cookie. */
+ NEXT_REQUEST_POLICY = 35,
+ USTREAMER_VIDEO_AND_FORMAT_METADATA = 36,
+ FORMAT_SELECTION_CONFIG = 37,
+ USTREAMER_SELECTED_MEDIA_STREAM = 38,
+ /** FORMAT_INITIALIZATION_METADATA - Metadata for format initialization; contains total number of segments, duration, etc. */
+ FORMAT_INITIALIZATION_METADATA = 42,
+ /** SABR_REDIRECT - Indicates a redirect to a different streaming URL. */
+ SABR_REDIRECT = 43,
+ /** SABR_ERROR - Indicates a SABR error; happens when the payload is invalid or the server cannot process the request. */
+ SABR_ERROR = 44,
+ SABR_SEEK = 45,
+ /** RELOAD_PLAYER_RESPONSE - Directive to reload the player with new parameters. */
+ RELOAD_PLAYER_RESPONSE = 46,
+ PLAYBACK_START_POLICY = 47,
+ ALLOWED_CACHED_FORMATS = 48,
+ START_BW_SAMPLING_HINT = 49,
+ PAUSE_BW_SAMPLING_HINT = 50,
+ SELECTABLE_FORMATS = 51,
+ REQUEST_IDENTIFIER = 52,
+ REQUEST_CANCELLATION_POLICY = 53,
+ ONESIE_PREFETCH_REJECTION = 54,
+ TIMELINE_CONTEXT = 55,
+ REQUEST_PIPELINING = 56,
+ /** SABR_CONTEXT_UPDATE - Updates SABR context data; usually used for ads. */
+ SABR_CONTEXT_UPDATE = 57,
+ /** STREAM_PROTECTION_STATUS - Status of stream protection; indicates whether attestation is required. */
+ STREAM_PROTECTION_STATUS = 58,
+ /** SABR_CONTEXT_SENDING_POLICY - Policy indicating which SABR contexts to send or discard in future requests. */
+ SABR_CONTEXT_SENDING_POLICY = 59,
+ LAWNMOWER_POLICY = 60,
+ SABR_ACK = 61,
+ END_OF_TRACK = 62,
+ CACHE_LOAD_POLICY = 63,
+ LAWNMOWER_MESSAGING_POLICY = 64,
+ PREWARM_CONNECTION = 65,
+ PLAYBACK_DEBUG_INFO = 66,
+ /** SNACKBAR_MESSAGE - Directive to show the user a notification message. */
+ SNACKBAR_MESSAGE = 67,
+ UNRECOGNIZED = -1,
+}
diff --git a/protos/generated/video_streaming/video_playback_abr_request.ts b/protos/generated/video_streaming/video_playback_abr_request.ts
index 409167d..9da4f8a 100644
--- a/protos/generated/video_streaming/video_playback_abr_request.ts
+++ b/protos/generated/video_streaming/video_playback_abr_request.ts
@@ -10,6 +10,7 @@ import { FormatId } from "../misc/common.js";
import { BufferedRange } from "./buffered_range.js";
import { ClientAbrState } from "./client_abr_state.js";
import { StreamerContext } from "./streamer_context.js";
+import { TimeRange } from "./time_range.js";
export const protobufPackage = "video_streaming";
@@ -17,33 +18,33 @@ export interface VideoPlaybackAbrRequest {
clientAbrState?: ClientAbrState | undefined;
selectedFormatIds: FormatId[];
bufferedRanges: BufferedRange[];
+ /** `osts` (Onesie Start Time Seconds) param on Onesie requests. */
playerTimeMs?: number | undefined;
videoPlaybackUstreamerConfig?: Uint8Array | undefined;
- lo?: Lo | undefined;
- selectedAudioFormatIds: FormatId[];
- selectedVideoFormatIds: FormatId[];
+ field6?:
+ | UnknownMessage1
+ | undefined;
+ /** `pai` (Preferred Audio Itags) param on Onesie requests. */
+ preferredAudioFormatIds: FormatId[];
+ /** `pvi` (Preferred Video Itags) param on Onesie requests. */
+ preferredVideoFormatIds: FormatId[];
+ preferredSubtitleFormatIds: FormatId[];
streamerContext?: StreamerContext | undefined;
- field21?: OQa | undefined;
+ field21?: UnknownMessage2 | undefined;
field22?: number | undefined;
field23?: number | undefined;
- field1000: Pqa[];
+ field1000: UnknownMessage3[];
}
-export interface Lo {
+export interface UnknownMessage1 {
formatId?: FormatId | undefined;
- Lj?: number | undefined;
+ lmt?: number | undefined;
sequenceNumber?: number | undefined;
- field4?: Lo_Field4 | undefined;
- MZ?: number | undefined;
+ timeRange?: TimeRange | undefined;
+ field5?: number | undefined;
}
-export interface Lo_Field4 {
- field1?: number | undefined;
- field2?: number | undefined;
- field3?: number | undefined;
-}
-
-export interface OQa {
+export interface UnknownMessage2 {
field1: string[];
field2?: Uint8Array | undefined;
field3?: string | undefined;
@@ -52,8 +53,8 @@ export interface OQa {
field6?: string | undefined;
}
-export interface Pqa {
- formats: FormatId[];
+export interface UnknownMessage3 {
+ formatIds: FormatId[];
ud: BufferedRange[];
clipId?: string | undefined;
}
@@ -65,9 +66,10 @@ function createBaseVideoPlaybackAbrRequest(): VideoPlaybackAbrRequest {
bufferedRanges: [],
playerTimeMs: 0,
videoPlaybackUstreamerConfig: new Uint8Array(0),
- lo: undefined,
- selectedAudioFormatIds: [],
- selectedVideoFormatIds: [],
+ field6: undefined,
+ preferredAudioFormatIds: [],
+ preferredVideoFormatIds: [],
+ preferredSubtitleFormatIds: [],
streamerContext: undefined,
field21: undefined,
field22: 0,
@@ -93,20 +95,23 @@ export const VideoPlaybackAbrRequest: MessageFns = {
if (message.videoPlaybackUstreamerConfig !== undefined && message.videoPlaybackUstreamerConfig.length !== 0) {
writer.uint32(42).bytes(message.videoPlaybackUstreamerConfig);
}
- if (message.lo !== undefined) {
- Lo.encode(message.lo, writer.uint32(50).fork()).join();
+ if (message.field6 !== undefined) {
+ UnknownMessage1.encode(message.field6, writer.uint32(50).fork()).join();
}
- for (const v of message.selectedAudioFormatIds) {
+ for (const v of message.preferredAudioFormatIds) {
FormatId.encode(v!, writer.uint32(130).fork()).join();
}
- for (const v of message.selectedVideoFormatIds) {
+ for (const v of message.preferredVideoFormatIds) {
FormatId.encode(v!, writer.uint32(138).fork()).join();
}
+ for (const v of message.preferredSubtitleFormatIds) {
+ FormatId.encode(v!, writer.uint32(146).fork()).join();
+ }
if (message.streamerContext !== undefined) {
StreamerContext.encode(message.streamerContext, writer.uint32(154).fork()).join();
}
if (message.field21 !== undefined) {
- OQa.encode(message.field21, writer.uint32(170).fork()).join();
+ UnknownMessage2.encode(message.field21, writer.uint32(170).fork()).join();
}
if (message.field22 !== undefined && message.field22 !== 0) {
writer.uint32(176).int32(message.field22);
@@ -115,7 +120,7 @@ export const VideoPlaybackAbrRequest: MessageFns = {
writer.uint32(184).int32(message.field23);
}
for (const v of message.field1000) {
- Pqa.encode(v!, writer.uint32(8002).fork()).join();
+ UnknownMessage3.encode(v!, writer.uint32(8002).fork()).join();
}
return writer;
},
@@ -167,21 +172,28 @@ export const VideoPlaybackAbrRequest: MessageFns = {
break;
}
- message.lo = Lo.decode(reader, reader.uint32());
+ message.field6 = UnknownMessage1.decode(reader, reader.uint32());
continue;
case 16:
if (tag !== 130) {
break;
}
- message.selectedAudioFormatIds.push(FormatId.decode(reader, reader.uint32()));
+ message.preferredAudioFormatIds.push(FormatId.decode(reader, reader.uint32()));
continue;
case 17:
if (tag !== 138) {
break;
}
- message.selectedVideoFormatIds.push(FormatId.decode(reader, reader.uint32()));
+ message.preferredVideoFormatIds.push(FormatId.decode(reader, reader.uint32()));
+ continue;
+ case 18:
+ if (tag !== 146) {
+ break;
+ }
+
+ message.preferredSubtitleFormatIds.push(FormatId.decode(reader, reader.uint32()));
continue;
case 19:
if (tag !== 154) {
@@ -195,7 +207,7 @@ export const VideoPlaybackAbrRequest: MessageFns = {
break;
}
- message.field21 = OQa.decode(reader, reader.uint32());
+ message.field21 = UnknownMessage2.decode(reader, reader.uint32());
continue;
case 22:
if (tag !== 176) {
@@ -216,7 +228,7 @@ export const VideoPlaybackAbrRequest: MessageFns = {
break;
}
- message.field1000.push(Pqa.decode(reader, reader.uint32()));
+ message.field1000.push(UnknownMessage3.decode(reader, reader.uint32()));
continue;
}
if ((tag & 7) === 4 || tag === 0) {
@@ -228,34 +240,34 @@ export const VideoPlaybackAbrRequest: MessageFns = {
},
};
-function createBaseLo(): Lo {
- return { formatId: undefined, Lj: 0, sequenceNumber: 0, field4: undefined, MZ: 0 };
+function createBaseUnknownMessage1(): UnknownMessage1 {
+ return { formatId: undefined, lmt: 0, sequenceNumber: 0, timeRange: undefined, field5: 0 };
}
-export const Lo: MessageFns = {
- encode(message: Lo, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+export const UnknownMessage1: MessageFns = {
+ encode(message: UnknownMessage1, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
if (message.formatId !== undefined) {
FormatId.encode(message.formatId, writer.uint32(10).fork()).join();
}
- if (message.Lj !== undefined && message.Lj !== 0) {
- writer.uint32(16).int32(message.Lj);
+ if (message.lmt !== undefined && message.lmt !== 0) {
+ writer.uint32(16).sint64(message.lmt);
}
if (message.sequenceNumber !== undefined && message.sequenceNumber !== 0) {
writer.uint32(24).int32(message.sequenceNumber);
}
- if (message.field4 !== undefined) {
- Lo_Field4.encode(message.field4, writer.uint32(34).fork()).join();
+ if (message.timeRange !== undefined) {
+ TimeRange.encode(message.timeRange, writer.uint32(34).fork()).join();
}
- if (message.MZ !== undefined && message.MZ !== 0) {
- writer.uint32(40).int32(message.MZ);
+ if (message.field5 !== undefined && message.field5 !== 0) {
+ writer.uint32(40).int32(message.field5);
}
return writer;
},
- decode(input: BinaryReader | Uint8Array, length?: number): Lo {
+ decode(input: BinaryReader | Uint8Array, length?: number): UnknownMessage1 {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
let end = length === undefined ? reader.len : reader.pos + length;
- const message = createBaseLo();
+ const message = createBaseUnknownMessage1();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
@@ -271,7 +283,7 @@ export const Lo: MessageFns = {
break;
}
- message.Lj = reader.int32();
+ message.lmt = longToNumber(reader.sint64());
continue;
case 3:
if (tag !== 24) {
@@ -285,14 +297,14 @@ export const Lo: MessageFns = {
break;
}
- message.field4 = Lo_Field4.decode(reader, reader.uint32());
+ message.timeRange = TimeRange.decode(reader, reader.uint32());
continue;
case 5:
if (tag !== 40) {
break;
}
- message.MZ = reader.int32();
+ message.field5 = reader.int32();
continue;
}
if ((tag & 7) === 4 || tag === 0) {
@@ -304,68 +316,12 @@ export const Lo: MessageFns = {
},
};
-function createBaseLo_Field4(): Lo_Field4 {
- return { field1: 0, field2: 0, field3: 0 };
-}
-
-export const Lo_Field4: MessageFns = {
- encode(message: Lo_Field4, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
- if (message.field1 !== undefined && message.field1 !== 0) {
- writer.uint32(8).int32(message.field1);
- }
- if (message.field2 !== undefined && message.field2 !== 0) {
- writer.uint32(16).int32(message.field2);
- }
- if (message.field3 !== undefined && message.field3 !== 0) {
- writer.uint32(24).int32(message.field3);
- }
- return writer;
- },
-
- decode(input: BinaryReader | Uint8Array, length?: number): Lo_Field4 {
- const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
- let end = length === undefined ? reader.len : reader.pos + length;
- const message = createBaseLo_Field4();
- while (reader.pos < end) {
- const tag = reader.uint32();
- switch (tag >>> 3) {
- case 1:
- if (tag !== 8) {
- break;
- }
-
- message.field1 = reader.int32();
- continue;
- case 2:
- if (tag !== 16) {
- break;
- }
-
- message.field2 = reader.int32();
- continue;
- case 3:
- if (tag !== 24) {
- break;
- }
-
- message.field3 = reader.int32();
- continue;
- }
- if ((tag & 7) === 4 || tag === 0) {
- break;
- }
- reader.skip(tag & 7);
- }
- return message;
- },
-};
-
-function createBaseOQa(): OQa {
+function createBaseUnknownMessage2(): UnknownMessage2 {
return { field1: [], field2: new Uint8Array(0), field3: "", field4: 0, field5: 0, field6: "" };
}
-export const OQa: MessageFns = {
- encode(message: OQa, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+export const UnknownMessage2: MessageFns = {
+ encode(message: UnknownMessage2, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
for (const v of message.field1) {
writer.uint32(10).string(v!);
}
@@ -387,10 +343,10 @@ export const OQa: MessageFns = {
return writer;
},
- decode(input: BinaryReader | Uint8Array, length?: number): OQa {
+ decode(input: BinaryReader | Uint8Array, length?: number): UnknownMessage2 {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
let end = length === undefined ? reader.len : reader.pos + length;
- const message = createBaseOQa();
+ const message = createBaseUnknownMessage2();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
@@ -446,13 +402,13 @@ export const OQa: MessageFns = {
},
};
-function createBasePqa(): Pqa {
- return { formats: [], ud: [], clipId: "" };
+function createBaseUnknownMessage3(): UnknownMessage3 {
+ return { formatIds: [], ud: [], clipId: "" };
}
-export const Pqa: MessageFns = {
- encode(message: Pqa, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
- for (const v of message.formats) {
+export const UnknownMessage3: MessageFns = {
+ encode(message: UnknownMessage3, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ for (const v of message.formatIds) {
FormatId.encode(v!, writer.uint32(10).fork()).join();
}
for (const v of message.ud) {
@@ -464,10 +420,10 @@ export const Pqa: MessageFns = {
return writer;
},
- decode(input: BinaryReader | Uint8Array, length?: number): Pqa {
+ decode(input: BinaryReader | Uint8Array, length?: number): UnknownMessage3 {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
let end = length === undefined ? reader.len : reader.pos + length;
- const message = createBasePqa();
+ const message = createBaseUnknownMessage3();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
@@ -476,7 +432,7 @@ export const Pqa: MessageFns = {
break;
}
- message.formats.push(FormatId.decode(reader, reader.uint32()));
+ message.formatIds.push(FormatId.decode(reader, reader.uint32()));
continue;
case 2:
if (tag !== 18) {
diff --git a/protos/misc/common.proto b/protos/misc/common.proto
index 3610ca1..b9b4b87 100644
--- a/protos/misc/common.proto
+++ b/protos/misc/common.proto
@@ -12,14 +12,22 @@ message FormatId {
optional string xtags = 3;
}
-message InitRange {
- optional int32 start = 1;
- optional int32 end = 2;
+message Range {
+ optional int32 legacy_start = 1;
+ optional int32 legacy_end = 2;
+ optional int32 start = 3;
+ optional int32 end = 4;
}
-message IndexRange {
- optional int32 start = 1;
- optional int32 end = 2;
+enum CompressionType {
+ UNKNOWN = 0;
+ GZIP = 1;
+ BROTLI = 2;
+}
+
+message IdentifierToken {
+ optional int32 request_number = 1;
+ optional int32 field5 = 5;
}
message KeyValuePair {
@@ -181,4 +189,14 @@ enum OnesieRequestTarget {
ONESIE_REQUEST_TARGET_ENCRYPTED_WATCH_SERVICE_DEPRECATED = 2;
ONESIE_REQUEST_TARGET_ENCRYPTED_WATCH_SERVICE = 3;
ONESIE_REQUEST_TARGET_INNERTUBE_ENCRYPTED_SERVICE = 4;
+}
+
+message AuthorizedFormat {
+ optional int32 track_type = 1;
+ optional bool is_hdr = 2;
+}
+
+message PlaybackAuthorization {
+ repeated AuthorizedFormat authorized_formats = 1;
+ optional bytes sabr_license_constraint = 2;
}
\ No newline at end of file
diff --git a/protos/video_streaming/buffered_range.proto b/protos/video_streaming/buffered_range.proto
index 46c718e..6303da3 100644
--- a/protos/video_streaming/buffered_range.proto
+++ b/protos/video_streaming/buffered_range.proto
@@ -5,27 +5,27 @@ import "misc/common.proto";
import "video_streaming/time_range.proto";
message BufferedRange {
+ message UnknownMessage1 {
+ message UnknownInnerMessage {
+ optional string video_id = 1;
+ optional uint64 lmt = 2;
+ }
+ repeated UnknownInnerMessage field1 = 1;
+ }
+
+ message UnknownMessage2 {
+ optional int32 field1 = 1;
+ optional int32 field2 = 2;
+ optional int32 field3 = 3;
+ }
+
required .misc.FormatId format_id = 1;
required int64 start_time_ms = 2;
required int64 duration_ms = 3;
required int32 start_segment_index = 4;
required int32 end_segment_index = 5;
optional TimeRange time_range = 6;
- optional Kob field9 = 9;
- optional YPa field11 = 11;
- optional YPa field12 = 12;
-}
-
-message Kob {
- message Pa {
- optional string video_id = 1;
- optional uint64 lmt = 2;
- }
- repeated Pa EW = 1;
-}
-
-message YPa {
- optional int32 field1 = 1;
- optional int32 field2 = 2;
- optional int32 field3 = 3;
-}
+ optional UnknownMessage1 field9 = 9;
+ optional UnknownMessage2 field11 = 11;
+ optional UnknownMessage2 field12 = 12;
+}
\ No newline at end of file
diff --git a/protos/video_streaming/client_abr_state.proto b/protos/video_streaming/client_abr_state.proto
index a02482e..bd22dcd 100644
--- a/protos/video_streaming/client_abr_state.proto
+++ b/protos/video_streaming/client_abr_state.proto
@@ -2,6 +2,7 @@ syntax = "proto2";
package video_streaming;
import "misc/common.proto";
+import "video_streaming/media_capabilities.proto";
message ClientAbrState {
optional int64 time_since_last_manual_format_selection_ms = 13;
@@ -25,27 +26,29 @@ message ClientAbrState {
optional int32 visibility = 34;
optional float playback_rate = 35;
optional int64 elapsed_wall_time_ms = 36;
- optional bytes media_capabilities = 38;
+ optional MediaCapabilities media_capabilities = 38;
optional int64 time_since_last_action_ms = 39;
optional int32 enabled_track_types_bitfield = 40;
optional int32 max_pacing_rate = 43;
optional int64 player_state = 44;
optional bool drc_enabled = 46;
- optional int32 Jda = 48;
- optional int32 qw = 50;
- optional int32 Ky = 51;
+ optional int32 field48 = 48;
+ optional int32 field50 = 50;
+ optional int32 field51 = 51;
optional int32 sabr_report_request_cancellation_info = 54;
- optional bool l = 56;
- optional int64 G7 = 57;
+ optional bool disable_streaming_xhr = 56;
+ optional int64 field57 = 57;
optional bool prefer_vp9 = 58;
- optional int32 qj = 59;
- optional int32 Hx = 60;
+ optional int32 av1_quality_threshold = 59; // 2160
+ optional int32 field60 = 60;
optional bool is_prefetch = 61;
- optional int32 sabr_support_quality_constraints = 62;
+ optional bool sabr_support_quality_constraints = 62;
optional bytes sabr_license_constraint = 63;
optional int32 allow_proxima_live_latency = 64;
optional int32 sabr_force_proxima = 66;
- optional int32 Tqb = 67;
+ optional int32 field67 = 67;
optional int64 sabr_force_max_network_interruption_duration_ms = 68;
optional string audio_track_id = 69;
+ optional bool enable_voice_boost = 76;
+ optional .misc.PlaybackAuthorization playback_authorization = 79;
}
diff --git a/protos/video_streaming/crypto_params.proto b/protos/video_streaming/crypto_params.proto
index f70276c..0851b10 100644
--- a/protos/video_streaming/crypto_params.proto
+++ b/protos/video_streaming/crypto_params.proto
@@ -1,14 +1,10 @@
syntax = "proto2";
package video_streaming;
-message CryptoParams {
- enum CompressionType {
- NONE = 0;
- GZIP = 1;
- BROTLI = 2;
- }
+import "misc/common.proto";
+message CryptoParams {
optional bytes hmac = 4;
optional bytes iv = 5;
- optional CompressionType compression_type = 6;
+ optional .misc.CompressionType compression_type = 6;
}
diff --git a/protos/video_streaming/format_initialization_metadata.proto b/protos/video_streaming/format_initialization_metadata.proto
index 86c34d7..ff5d487 100644
--- a/protos/video_streaming/format_initialization_metadata.proto
+++ b/protos/video_streaming/format_initialization_metadata.proto
@@ -6,12 +6,12 @@ import "misc/common.proto";
message FormatInitializationMetadata {
optional string video_id = 1;
optional .misc.FormatId format_id = 2;
- optional int32 end_time_ms = 3;
+ optional int64 end_time_ms = 3;
optional int64 end_segment_number = 4;
optional string mime_type = 5;
- optional .misc.InitRange init_range = 6;
- optional .misc.IndexRange index_range = 7;
- optional int32 field8 = 8;
- optional int32 duration_ms = 9;
- optional int32 field10 = 10;
-}
+ optional .misc.Range init_range = 6;
+ optional .misc.Range index_range = 7;
+ optional int64 field8 = 8;
+ optional int64 duration_units = 9;
+ optional int64 duration_timescale = 10;
+}
\ No newline at end of file
diff --git a/protos/video_streaming/format_selection_config.proto b/protos/video_streaming/format_selection_config.proto
new file mode 100644
index 0000000..9f14eef
--- /dev/null
+++ b/protos/video_streaming/format_selection_config.proto
@@ -0,0 +1,9 @@
+syntax = "proto2";
+
+package video_streaming;
+
+message FormatSelectionConfig {
+ repeated int32 itags = 2;
+ optional string video_id = 3;
+ optional int32 resolution = 4;
+}
\ No newline at end of file
diff --git a/protos/video_streaming/encrypted_player_request.proto b/protos/video_streaming/innertube_request.proto
similarity index 67%
rename from protos/video_streaming/encrypted_player_request.proto
rename to protos/video_streaming/innertube_request.proto
index a9fc9ca..a152845 100644
--- a/protos/video_streaming/encrypted_player_request.proto
+++ b/protos/video_streaming/innertube_request.proto
@@ -1,9 +1,9 @@
syntax = "proto2";
package video_streaming;
-message EncryptedPlayerRequest {
+message InnertubeRequest {
optional bytes context = 1;
- optional bytes encrypted_onesie_player_request = 2;
+ optional bytes encrypted_onesie_innertube_request = 2;
optional bytes encrypted_client_key = 5;
optional bytes iv = 6;
optional bytes hmac = 7;
@@ -11,13 +11,11 @@ message EncryptedPlayerRequest {
optional bool serialize_response_as_json = 10;
optional bool enable_ad_placements_preroll = 13;
optional bool enable_compression = 14;
-
- message UstreamerFlags {
- optional bool send_video_playback_config = 2;
- }
-
optional UstreamerFlags ustreamer_flags = 15;
-
- optional bytes unencrypted_onesie_player_request = 16;
+ optional bytes unencrypted_onesie_innertube_request = 16;
optional bool use_jsonformatter_to_parse_player_response = 17;
}
+
+message UstreamerFlags {
+ optional bool send_video_playback_config = 2;
+}
\ No newline at end of file
diff --git a/protos/video_streaming/media_header.proto b/protos/video_streaming/media_header.proto
index b05d852..9be1719 100644
--- a/protos/video_streaming/media_header.proto
+++ b/protos/video_streaming/media_header.proto
@@ -11,19 +11,14 @@ message MediaHeader {
optional uint64 lmt = 4;
optional string xtags = 5;
optional int64 start_range = 6;
- optional CompressionAlgorithm compression_algorithm = 7;
+ optional .misc.CompressionType compression_algorithm = 7;
optional bool is_init_seg = 8;
optional int64 sequence_number = 9;
- optional int64 field10 = 10;
+ optional int64 bitrate_bps = 10;
optional int64 start_ms = 11;
optional int64 duration_ms = 12;
optional .misc.FormatId format_id = 13;
optional int64 content_length = 14;
optional TimeRange time_range = 15;
-
- enum CompressionAlgorithm {
- UNKNOWN = 0;
- NONE = 1;
- GZIP = 2;
- }
+ optional uint64 sequence_lmt = 16;
}
diff --git a/protos/video_streaming/next_request_policy.proto b/protos/video_streaming/next_request_policy.proto
index 6556912..adbe792 100644
--- a/protos/video_streaming/next_request_policy.proto
+++ b/protos/video_streaming/next_request_policy.proto
@@ -6,7 +6,10 @@ import "video_streaming/playback_cookie.proto";
message NextRequestPolicy {
optional int32 target_audio_readahead_ms = 1;
optional int32 target_video_readahead_ms = 2;
+ optional int32 max_time_since_last_request_ms = 3;
optional int32 backoff_time_ms = 4;
+ optional int32 min_audio_readahead_ms = 5;
+ optional int32 min_video_readahead_ms = 6;
optional .video_streaming.PlaybackCookie playback_cookie = 7;
optional string video_id = 8;
}
diff --git a/protos/video_streaming/onesie_header.proto b/protos/video_streaming/onesie_header.proto
index 91efde3..3c7737e 100644
--- a/protos/video_streaming/onesie_header.proto
+++ b/protos/video_streaming/onesie_header.proto
@@ -5,11 +5,11 @@ import "video_streaming/crypto_params.proto";
import "video_streaming/onesie_header_type.proto";
message OnesieHeader {
- message Field23 {
+ message UnknownMessage1 {
optional string video_id = 2;
}
- message Field34 {
+ message UnknownMessage2 {
repeated string itag_denylist = 1;
}
@@ -22,6 +22,6 @@ message OnesieHeader {
repeated string restricted_formats = 11;
optional string xtags = 15;
optional int64 sequence_number = 18;
- optional Field23 field23 = 23;
- optional Field34 field34 = 34;
+ optional UnknownMessage1 field23 = 23;
+ optional UnknownMessage2 field34 = 34;
}
diff --git a/protos/video_streaming/onesie_player_request.proto b/protos/video_streaming/onesie_innertube_request.proto
similarity index 88%
rename from protos/video_streaming/onesie_player_request.proto
rename to protos/video_streaming/onesie_innertube_request.proto
index c2198d8..bf8ad4a 100644
--- a/protos/video_streaming/onesie_player_request.proto
+++ b/protos/video_streaming/onesie_innertube_request.proto
@@ -3,7 +3,7 @@ package video_streaming;
import "misc/common.proto";
-message OnesiePlayerRequest {
+message OnesieInnertubeRequest {
optional string url = 1;
repeated misc.HttpHeader headers = 2;
optional string body = 3;
diff --git a/protos/video_streaming/onesie_player_response.proto b/protos/video_streaming/onesie_innertube_response.proto
similarity index 89%
rename from protos/video_streaming/onesie_player_response.proto
rename to protos/video_streaming/onesie_innertube_response.proto
index 1914c81..9cb1b24 100644
--- a/protos/video_streaming/onesie_player_response.proto
+++ b/protos/video_streaming/onesie_innertube_response.proto
@@ -4,7 +4,7 @@ package video_streaming;
import "misc/common.proto";
import "video_streaming/onesie_proxy_status.proto";
-message OnesiePlayerResponse {
+message OnesieInnertubeResponse {
optional OnesieProxyStatus onesie_proxy_status = 1;
optional int32 http_status = 2;
repeated .misc.HttpHeader headers = 3;
diff --git a/protos/video_streaming/onesie_proxy_status.proto b/protos/video_streaming/onesie_proxy_status.proto
index a9e9784..51ed5ec 100644
--- a/protos/video_streaming/onesie_proxy_status.proto
+++ b/protos/video_streaming/onesie_proxy_status.proto
@@ -2,18 +2,18 @@ syntax = "proto2";
package video_streaming;
enum OnesieProxyStatus {
- ONESIE_PROXY_STATUS_UNKNOWN = 0;
- ONESIE_PROXY_STATUS_OK = 1;
- ONESIE_PROXY_STATUS_DECRYPTION_FAILED = 2;
- ONESIE_PROXY_STATUS_PARSING_FAILED = 3;
- ONESIE_PROXY_STATUS_MISSING_X_FORWARDED_FOR = 4;
- ONESIE_PROXY_STATUS_INVALID_X_FORWARDED_FOR = 5;
- ONESIE_PROXY_STATUS_INVALID_CONTENT_TYPE = 6;
- ONESIE_PROXY_STATUS_BACKEND_ERROR = 7;
- ONESIE_PROXY_STATUS_CLIENT_ERROR = 8;
- ONESIE_PROXY_STATUS_MISSING_CRYPTER = 9;
- ONESIE_PROXY_STATUS_RESPONSE_JSON_SERIALIZATION_FAILED = 10;
- ONESIE_PROXY_STATUS_DECOMPRESSION_FAILED = 11;
- ONESIE_PROXY_STATUS_JSON_PARSING_FAILED = 12;
- ONESIE_PROXY_STATUS_UNKNOWN_COMPRESSION_TYPE = 13;
+ UNKNOWN = 0;
+ OK = 1;
+ DECRYPTION_FAILED = 2;
+ PARSING_FAILED = 3;
+ MISSING_X_FORWARDED_FOR = 4;
+ INVALID_X_FORWARDED_FOR = 5;
+ INVALID_CONTENT_TYPE = 6;
+ BACKEND_ERROR = 7;
+ CLIENT_ERROR = 8;
+ MISSING_CRYPTER = 9;
+ RESPONSE_JSON_SERIALIZATION_FAILED = 10;
+ DECOMPRESSION_FAILED = 11;
+ JSON_PARSING_FAILED = 12;
+ UNKNOWN_COMPRESSION_TYPE = 13;
}
diff --git a/protos/video_streaming/onesie_request.proto b/protos/video_streaming/onesie_request.proto
index ccba816..afe07c5 100644
--- a/protos/video_streaming/onesie_request.proto
+++ b/protos/video_streaming/onesie_request.proto
@@ -4,17 +4,19 @@ package video_streaming;
import "misc/common.proto";
import "video_streaming/buffered_range.proto";
import "video_streaming/client_abr_state.proto";
-import "video_streaming/encrypted_player_request.proto";
+import "video_streaming/innertube_request.proto";
import "video_streaming/streamer_context.proto";
+import "video_streaming/reload_player_response.proto";
message OnesieRequest {
repeated string urls = 1;
optional ClientAbrState client_abr_state = 2;
- optional EncryptedPlayerRequest player_request = 3;
+ optional InnertubeRequest innertube_request = 3;
optional bytes onesie_ustreamer_config = 4;
optional int32 max_vp9_height = 5;
optional int32 client_display_height = 6;
optional StreamerContext streamer_context = 10;
optional .misc.OnesieRequestTarget request_target = 13; // MLOnesieRequestTarget
repeated BufferedRange buffered_ranges = 14;
-}
+ optional ReloadPlaybackParams reload_playback_params = 15;
+}
\ No newline at end of file
diff --git a/protos/video_streaming/playback_cookie.proto b/protos/video_streaming/playback_cookie.proto
index eb8ca09..e900775 100644
--- a/protos/video_streaming/playback_cookie.proto
+++ b/protos/video_streaming/playback_cookie.proto
@@ -4,7 +4,7 @@ package video_streaming;
import "misc/common.proto";
message PlaybackCookie {
- optional int32 field1 = 1; // Always 999999??
+ optional int32 resolution = 1; // Always 999999 when resolution is set manually, or if the auto selected one is the max available resolution.
optional int32 field2 = 2;
optional .misc.FormatId video_fmt = 7;
optional .misc.FormatId audio_fmt = 8;
diff --git a/protos/video_streaming/reload_player_response.proto b/protos/video_streaming/reload_player_response.proto
new file mode 100644
index 0000000..54473e7
--- /dev/null
+++ b/protos/video_streaming/reload_player_response.proto
@@ -0,0 +1,11 @@
+syntax = "proto2";
+
+package video_streaming;
+
+message ReloadPlaybackParams {
+ optional string token = 1;
+}
+
+message ReloadPlaybackContext {
+ optional ReloadPlaybackParams reload_playback_params = 1;
+}
\ No newline at end of file
diff --git a/protos/video_streaming/request_identifier.proto b/protos/video_streaming/request_identifier.proto
new file mode 100644
index 0000000..c5e76fb
--- /dev/null
+++ b/protos/video_streaming/request_identifier.proto
@@ -0,0 +1,7 @@
+syntax = "proto2";
+
+package video_streaming;
+
+message RequestIdentifier {
+ optional string token = 1;
+}
\ No newline at end of file
diff --git a/protos/video_streaming/sabr_context_sending_policy.proto b/protos/video_streaming/sabr_context_sending_policy.proto
new file mode 100644
index 0000000..efdac05
--- /dev/null
+++ b/protos/video_streaming/sabr_context_sending_policy.proto
@@ -0,0 +1,8 @@
+syntax = "proto2";
+package video_streaming;
+
+message SabrContextSendingPolicy {
+ repeated int32 start_policy = 1;
+ repeated int32 stop_policy = 2;
+ repeated int32 discard_policy = 3;
+}
\ No newline at end of file
diff --git a/protos/video_streaming/sabr_context_update.proto b/protos/video_streaming/sabr_context_update.proto
new file mode 100644
index 0000000..d82bcdf
--- /dev/null
+++ b/protos/video_streaming/sabr_context_update.proto
@@ -0,0 +1,43 @@
+syntax = "proto2";
+package video_streaming;
+
+message SabrContextUpdate {
+ enum SabrContextScope {
+ UNKNOWN = 0;
+ PLAYBACK = 1;
+ REQUEST = 2;
+ WATCH_ENDPOINT = 3;
+ CONTENT_ADS = 4;
+ }
+
+ enum SabrContextWritePolicy {
+ UNSPECIFIED = 0;
+ OVERWRITE = 1;
+ KEEP_EXISTING = 2;
+ }
+
+ optional int32 type = 1;
+ optional SabrContextScope scope = 2;
+
+ optional bytes value = 3;
+ optional bool send_by_default = 4;
+ optional SabrContextWritePolicy write_policy = 5;
+}
+
+// For debugging
+message SabrContextValue {
+ message ContentInfo {
+ optional string content_id = 1; // Looks like a content identifier of some sort "mQxOaLekHJ2f-LAPtq3hwQ4"
+ optional int32 content_type = 2; // Value of 1 observed (unsure what it truly means/is)
+ }
+
+ message TimingInfo {
+ optional int64 timestamp_ms = 1;
+ optional int32 duration_ms = 2;
+ optional ContentInfo content = 3;
+ }
+
+ optional TimingInfo timing = 1;
+ optional bytes signature = 2;
+ optional int32 field5 = 5;
+}
diff --git a/protos/video_streaming/snackbar_message.proto b/protos/video_streaming/snackbar_message.proto
new file mode 100644
index 0000000..2ba4e02
--- /dev/null
+++ b/protos/video_streaming/snackbar_message.proto
@@ -0,0 +1,6 @@
+syntax = "proto2";
+package video_streaming;
+
+message SnackbarMessage {
+ optional int32 id = 1;
+}
\ No newline at end of file
diff --git a/protos/video_streaming/stream_protection_status.proto b/protos/video_streaming/stream_protection_status.proto
index 2f2cba4..772806a 100644
--- a/protos/video_streaming/stream_protection_status.proto
+++ b/protos/video_streaming/stream_protection_status.proto
@@ -3,5 +3,5 @@ package video_streaming;
message StreamProtectionStatus {
optional int32 status = 1;
- optional int32 field2 = 2;
+ optional int32 max_retries = 2;
}
diff --git a/protos/video_streaming/streamer_context.proto b/protos/video_streaming/streamer_context.proto
index d83c39f..08f8460 100644
--- a/protos/video_streaming/streamer_context.proto
+++ b/protos/video_streaming/streamer_context.proto
@@ -40,27 +40,27 @@ message StreamerContext {
optional int32 gl_es_version_minor = 3;
}
- message Fqa {
+ message SabrContext {
optional int32 type = 1;
optional bytes value = 2;
}
- message Gqa {
- message Hqa {
+ message UnknownMessage1 {
+ message UnknownInnerMessage1 {
optional int32 code = 1;
optional string message = 2;
}
optional bytes field1 = 1;
- optional Hqa field2 = 2;
+ optional UnknownInnerMessage1 field2 = 2;
}
optional ClientInfo client_info = 1;
optional bytes po_token = 2;
optional bytes playback_cookie = 3;
- optional bytes gp = 4;
- repeated Fqa field5 = 5;
- repeated int32 field6 = 6;
+ optional bytes field4 = 4;
+ repeated SabrContext sabr_contexts = 5;
+ repeated int32 unsent_sabr_contexts = 6;
optional string field7 = 7;
- optional Gqa field8 = 8;
-}
+ optional UnknownMessage1 field8 = 8;
+}
\ No newline at end of file
diff --git a/protos/video_streaming/time_range.proto b/protos/video_streaming/time_range.proto
index e85d2f3..62f100d 100644
--- a/protos/video_streaming/time_range.proto
+++ b/protos/video_streaming/time_range.proto
@@ -2,7 +2,7 @@ syntax = "proto2";
package video_streaming;
message TimeRange {
- optional int64 start = 1;
- optional int64 duration = 2;
+ optional int64 start_ticks = 1;
+ optional int64 duration_ticks = 2;
optional int32 timescale = 3;
}
diff --git a/protos/video_streaming/ump_part_id.proto b/protos/video_streaming/ump_part_id.proto
new file mode 100644
index 0000000..154233a
--- /dev/null
+++ b/protos/video_streaming/ump_part_id.proto
@@ -0,0 +1,60 @@
+syntax = "proto2";
+
+package video_streaming;
+
+enum UMPPartId {
+ UNKNOWN = 0;
+ ONESIE_HEADER = 10;
+ ONESIE_DATA = 11;
+ ONESIE_ENCRYPTED_MEDIA = 12;
+ // Header for a media segment; includes sequence and timing information.
+ MEDIA_HEADER = 20;
+ // Chunk of media segment data.
+ MEDIA = 21;
+ // Indicates end of media segment; finalizes segment processing.
+ MEDIA_END = 22;
+ CONFIG = 30;
+ LIVE_METADATA = 31;
+ HOSTNAME_CHANGE_HINT_DEPRECATED = 32;
+ LIVE_METADATA_PROMISE = 33;
+ LIVE_METADATA_PROMISE_CANCELLATION = 34;
+ // Server's policy for the next request; includes backoff time and playback cookie.
+ NEXT_REQUEST_POLICY = 35;
+ USTREAMER_VIDEO_AND_FORMAT_METADATA = 36;
+ FORMAT_SELECTION_CONFIG = 37;
+ USTREAMER_SELECTED_MEDIA_STREAM = 38;
+ // Metadata for format initialization; contains total number of segments, duration, etc.
+ FORMAT_INITIALIZATION_METADATA = 42;
+ // Indicates a redirect to a different streaming URL.
+ SABR_REDIRECT = 43;
+ // Indicates a SABR error; happens when the payload is invalid or the server cannot process the request.
+ SABR_ERROR = 44;
+ SABR_SEEK = 45;
+ // Directive to reload the player with new parameters.
+ RELOAD_PLAYER_RESPONSE = 46;
+ PLAYBACK_START_POLICY = 47;
+ ALLOWED_CACHED_FORMATS = 48;
+ START_BW_SAMPLING_HINT = 49;
+ PAUSE_BW_SAMPLING_HINT = 50;
+ SELECTABLE_FORMATS = 51;
+ REQUEST_IDENTIFIER = 52;
+ REQUEST_CANCELLATION_POLICY = 53;
+ ONESIE_PREFETCH_REJECTION = 54;
+ TIMELINE_CONTEXT = 55;
+ REQUEST_PIPELINING = 56;
+ // Updates SABR context data; usually used for ads.
+ SABR_CONTEXT_UPDATE = 57;
+ // Status of stream protection; indicates whether attestation is required.
+ STREAM_PROTECTION_STATUS = 58;
+ // Policy indicating which SABR contexts to send or discard in future requests.
+ SABR_CONTEXT_SENDING_POLICY = 59;
+ LAWNMOWER_POLICY = 60;
+ SABR_ACK = 61;
+ END_OF_TRACK = 62;
+ CACHE_LOAD_POLICY = 63;
+ LAWNMOWER_MESSAGING_POLICY = 64;
+ PREWARM_CONNECTION = 65;
+ PLAYBACK_DEBUG_INFO = 66;
+ // Directive to show the user a notification message.
+ SNACKBAR_MESSAGE = 67;
+}
\ No newline at end of file
diff --git a/protos/video_streaming/video_playback_abr_request.proto b/protos/video_streaming/video_playback_abr_request.proto
index 71b10eb..77ed2f3 100644
--- a/protos/video_streaming/video_playback_abr_request.proto
+++ b/protos/video_streaming/video_playback_abr_request.proto
@@ -5,37 +5,34 @@ import "misc/common.proto";
import "video_streaming/buffered_range.proto";
import "video_streaming/client_abr_state.proto";
import "video_streaming/streamer_context.proto";
+import "video_streaming/time_range.proto";
message VideoPlaybackAbrRequest {
optional ClientAbrState client_abr_state = 1;
repeated .misc.FormatId selected_format_ids = 2;
repeated BufferedRange buffered_ranges = 3;
- optional int64 player_time_ms = 4;
+ optional int64 player_time_ms = 4; // `osts` (Onesie Start Time Seconds) param on Onesie requests.
optional bytes video_playback_ustreamer_config = 5;
- optional Lo lo = 6;
- repeated .misc.FormatId selected_audio_format_ids = 16;
- repeated .misc.FormatId selected_video_format_ids = 17;
+ optional UnknownMessage1 field6 = 6;
+ repeated .misc.FormatId preferred_audio_format_ids = 16; // `pai` (Preferred Audio Itags) param on Onesie requests.
+ repeated .misc.FormatId preferred_video_format_ids = 17; // `pvi` (Preferred Video Itags) param on Onesie requests.
+ repeated .misc.FormatId preferred_subtitle_format_ids = 18;
optional StreamerContext streamer_context = 19;
- optional OQa field21 = 21;
+ optional UnknownMessage2 field21 = 21;
optional int32 field22 = 22;
optional int32 field23 = 23;
- repeated Pqa field1000 = 1000;
+ repeated UnknownMessage3 field1000 = 1000;
}
-message Lo {
- message Field4 {
- optional int32 field1 = 1;
- optional int32 field2 = 2;
- optional int32 field3 = 3;
- }
+message UnknownMessage1 {
optional .misc.FormatId format_id = 1;
- optional int32 Lj = 2;
+ optional sint64 lmt = 2;
optional int32 sequence_number = 3;
- optional Field4 field4 = 4;
- optional int32 MZ = 5;
+ optional TimeRange time_range = 4;
+ optional int32 field5 = 5;
}
-message OQa {
+message UnknownMessage2 {
repeated string field1 = 1;
optional bytes field2 = 2;
optional string field3 = 3;
@@ -44,8 +41,8 @@ message OQa {
optional string field6 = 6;
}
-message Pqa {
- repeated .misc.FormatId formats = 1;
+message UnknownMessage3 {
+ repeated .misc.FormatId format_ids = 1;
repeated BufferedRange ud = 2;
optional string clip_id = 3;
-}
+}
\ No newline at end of file
diff --git a/src/core/ChunkedDataBuffer.ts b/src/core/CompositeBuffer.ts
similarity index 63%
rename from src/core/ChunkedDataBuffer.ts
rename to src/core/CompositeBuffer.ts
index 911704f..7b0aa1b 100644
--- a/src/core/ChunkedDataBuffer.ts
+++ b/src/core/CompositeBuffer.ts
@@ -1,4 +1,4 @@
-export class ChunkedDataBuffer {
+export class CompositeBuffer {
public chunks: Uint8Array[];
public currentChunkOffset: number;
public currentChunkIndex: number;
@@ -10,33 +10,31 @@ export class ChunkedDataBuffer {
this.currentChunkOffset = this.currentChunkIndex = 0;
this.currentDataView = undefined;
this.totalLength = 0;
- chunks.forEach((chunk) => {
- this.append(chunk);
- });
+ chunks.forEach((chunk) => this.append(chunk));
}
- getLength(): number {
- return this.totalLength;
- }
-
- append(chunk: Uint8Array): void {
- if (this.canMergeWithLastChunk(chunk)) {
- const lastChunk = this.chunks[this.chunks.length - 1];
- this.chunks[this.chunks.length - 1] = new Uint8Array(
- lastChunk.buffer,
- lastChunk.byteOffset,
- lastChunk.length + chunk.length
- );
- this.resetFocus();
+ public append(chunk: Uint8Array | CompositeBuffer): void {
+ if (chunk instanceof Uint8Array) {
+ if (this.canMergeWithLastChunk(chunk)) {
+ const lastChunk = this.chunks[this.chunks.length - 1];
+ this.chunks[this.chunks.length - 1] = new Uint8Array(
+ lastChunk.buffer,
+ lastChunk.byteOffset,
+ lastChunk.length + chunk.length
+ );
+ this.resetFocus();
+ } else {
+ this.chunks.push(chunk);
+ }
+ this.totalLength += chunk.length;
} else {
- this.chunks.push(chunk);
+ chunk.chunks.forEach((c) => this.append(c));
}
- this.totalLength += chunk.length;
}
- split(position: number): { extractedBuffer: ChunkedDataBuffer; remainingBuffer: ChunkedDataBuffer } {
- const extractedBuffer = new ChunkedDataBuffer();
- const remainingBuffer = new ChunkedDataBuffer();
+ public split(position: number): { extractedBuffer: CompositeBuffer; remainingBuffer: CompositeBuffer } {
+ const extractedBuffer = new CompositeBuffer();
+ const remainingBuffer = new CompositeBuffer();
const iterator = this.chunks[Symbol.iterator]();
let item = iterator.next();
@@ -60,11 +58,20 @@ export class ChunkedDataBuffer {
return { extractedBuffer, remainingBuffer };
}
- isFocused(position: number): boolean {
- return position >= this.currentChunkOffset && position < this.currentChunkOffset + this.chunks[this.currentChunkIndex].length;
+ public getLength(): number {
+ return this.totalLength;
}
- focus(position: number): void {
+ public canReadBytes(position: number, length: number): boolean {
+ return position + length <= this.totalLength;
+ }
+
+ public getUint8(position: number): number {
+ this.focus(position);
+ return this.chunks[this.currentChunkIndex][position - this.currentChunkOffset];
+ }
+
+ public focus(position: number): void {
if (!this.isFocused(position)) {
if (position < this.currentChunkOffset) this.resetFocus();
@@ -80,13 +87,17 @@ export class ChunkedDataBuffer {
}
}
- canReadBytes(position: number, length: number): boolean {
- return position + length <= this.totalLength;
+ public isFocused(position: number): boolean {
+ return (
+ position >= this.currentChunkOffset &&
+ position < this.currentChunkOffset + this.chunks[this.currentChunkIndex].length
+ );
}
- getUint8(position: number): number {
- this.focus(position);
- return this.chunks[this.currentChunkIndex][position - this.currentChunkOffset];
+ private resetFocus(): void {
+ this.currentDataView = undefined;
+ this.currentChunkIndex = 0;
+ this.currentChunkOffset = 0;
}
private canMergeWithLastChunk(chunk: Uint8Array): boolean {
@@ -97,10 +108,4 @@ export class ChunkedDataBuffer {
lastChunk.byteOffset + lastChunk.length === chunk.byteOffset
);
}
-
- private resetFocus(): void {
- this.currentDataView = undefined;
- this.currentChunkIndex = 0;
- this.currentChunkOffset = 0;
- }
}
\ No newline at end of file
diff --git a/src/core/SabrStream.ts b/src/core/SabrStream.ts
new file mode 100644
index 0000000..0d1b8b2
--- /dev/null
+++ b/src/core/SabrStream.ts
@@ -0,0 +1,1289 @@
+import {
+ FormatInitializationMetadata,
+ MediaHeader,
+ NextRequestPolicy,
+ PlaybackCookie,
+ ReloadPlaybackContext,
+ SabrContextSendingPolicy,
+ SabrContextUpdate,
+ SabrContextWritePolicy,
+ SabrError,
+ SabrRedirect,
+ StreamProtectionStatus,
+ VideoPlaybackAbrRequest,
+ UMPPartId
+} from '../utils/Protos.js';
+
+import type {
+ BufferedRange,
+ ClientAbrState,
+ ClientInfo
+} from '../utils/Protos.js';
+
+import type {
+ SabrPlaybackOptions,
+ SabrStreamConfig
+} from '../types/sabrStreamTypes.js';
+
+import type { FetchFunction, Part, SabrFormat } from '../types/shared.js';
+
+import {
+ MAX_INT32_VALUE,
+ EnabledTrackTypes,
+ base64ToU8,
+ EventEmitterLike,
+ Logger,
+ wait
+} from '../utils/index.js';
+
+import * as FormatKeyUtils from '../utils/formatKeyUtils.js';
+import { chooseFormat, getMediaType, getTotalDownloadedDuration } from '../utils/sabrStreamUtils.js';
+import { CompositeBuffer } from './CompositeBuffer.js';
+import { UmpReader } from './UmpReader.js';
+
+const TAG = 'SabrStream';
+const DEFAULT_MAX_RETRIES = 10;
+const MAX_BACKOFF_MS = 8000;
+const BACKOFF_MULTIPLIER = 500;
+const DEFAULT_STALL_DETECTION_MS = 30000;
+const MAX_STALLS = 5;
+
+type UmpPartHandler = (part: Part) => void;
+
+export interface InitializedFormat {
+ formatInitializationMetadata: FormatInitializationMetadata;
+ downloadedSegments: Map;
+ lastMediaHeaders: MediaHeader[];
+}
+
+export interface SabrStreamState {
+ durationMs: number;
+ requestNumber: number;
+ playerTimeMs: number;
+ activeSabrContexts: number[];
+ sabrContextUpdates: Array<[ number, SabrContextUpdate ]>;
+ formatToDiscard?: string;
+ cachedBufferedRanges: BufferedRange[];
+ nextRequestPolicy?: NextRequestPolicy;
+ initializedFormats: Array<{
+ formatKey: string;
+ formatInitializationMetadata: FormatInitializationMetadata;
+ downloadedSegments: Array<[ number, Segment ]>;
+ lastMediaHeaders: MediaHeader[];
+ }>;
+}
+
+interface SelectedFormats {
+ videoFormat: SabrFormat;
+ audioFormat: SabrFormat;
+}
+
+interface Segment {
+ formatIdKey: string;
+ segmentNumber: number;
+ durationMs?: number;
+ mediaHeader: MediaHeader;
+ bufferedChunks: Uint8Array[];
+}
+
+interface ProgressTracker {
+ lastProgressTime: number;
+ lastDownloadedDuration: number;
+ stallCount: number;
+}
+
+/**
+ * Manages the download and processing of YouTube's Server-Adaptive Bitrate (SABR) streams.
+ *
+ * This class handles the entire lifecycle of a SABR stream:
+ * - Selecting appropriate video and audio formats.
+ * - Making network requests to fetch media segments.
+ * - Processing UMP parts in real-time.
+ * - Handling server-side directives like redirects, context updates, and backoff policies.
+ * - Emitting events for key stream updates, such as format initialization and errors.
+ * - Providing separate `ReadableStream` instances for video and audio data.
+ */
+export class SabrStream extends EventEmitterLike {
+ private readonly logger = Logger.getInstance();
+ private readonly fetchFunction: FetchFunction;
+ private readonly formatIds: SabrFormat[] = [];
+ private readonly videoStream: ReadableStream;
+ private readonly audioStream: ReadableStream;
+ private readonly umpPartHandlers = new Map([
+ [ UMPPartId.FORMAT_INITIALIZATION_METADATA, this.handleFormatInitializationMetadata.bind(this) ],
+ [ UMPPartId.NEXT_REQUEST_POLICY, this.handleNextRequestPolicy.bind(this) ],
+ [ UMPPartId.SABR_ERROR, this.handleSabrError.bind(this) ],
+ [ UMPPartId.SABR_REDIRECT, this.handleSabrRedirect.bind(this) ],
+ [ UMPPartId.SABR_CONTEXT_UPDATE, this.handleSabrContextUpdate.bind(this) ],
+ [ UMPPartId.SABR_CONTEXT_SENDING_POLICY, this.handleSabrContextSendingPolicy.bind(this) ],
+ [ UMPPartId.STREAM_PROTECTION_STATUS, this.handleStreamProtectionStatus.bind(this) ],
+ [ UMPPartId.RELOAD_PLAYER_RESPONSE, this.handleReloadPlayerResponse.bind(this) ],
+ [ UMPPartId.MEDIA_HEADER, this.handleMediaHeader.bind(this) ],
+ [ UMPPartId.MEDIA, this.handleMedia.bind(this) ],
+ [ UMPPartId.MEDIA_END, this.handleMediaEnd.bind(this) ]
+ ]);
+
+ private serverAbrStreamingUrl?: string;
+ private videoPlaybackUstreamerConfig?: string;
+ private clientInfo?: ClientInfo;
+ private poToken?: string;
+
+ private nextRequestPolicy?: NextRequestPolicy;
+ private streamProtectionStatus?: StreamProtectionStatus;
+ private sabrContexts = new Map();
+ private activeSabrContextTypes = new Set();
+ private initializedFormatsMap = new Map();
+ private abortController?: AbortController;
+ private partialSegmentQueue = new Map();
+ private requestNumber = 0;
+ private durationMs = Infinity;
+ private cachedBufferedRanges: BufferedRange[] | undefined;
+ private formatToDiscard?: string;
+ private mediaHeadersProcessed = false;
+ private mainFormat?: InitializedFormat;
+ private _errored = false;
+ private _aborted = false;
+
+ private progressTracker: ProgressTracker = {
+ lastProgressTime: Date.now(),
+ lastDownloadedDuration: 0,
+ stallCount: 0
+ };
+
+ private videoController?: ReadableStreamDefaultController;
+ private audioController?: ReadableStreamDefaultController;
+
+ /**
+ * Fired when the server sends initialization metadata for a media format.
+ * @event
+ */
+ public on(event: 'formatInitialization', listener: (initializedFormat: InitializedFormat) => void): void;
+ /**
+ * Fired when the server provides an update on the stream's content protection status.
+ * @event
+ */
+ public on(event: 'streamProtectionStatusUpdate', listener: (data: StreamProtectionStatus) => void): void;
+ /**
+ * Fired when the server directs the client to reload the player, usually indicating the current session is invalid.
+ * @event
+ */
+ public on(event: 'reloadPlayerResponse', listener: (reloadPlaybackContext: ReloadPlaybackContext) => void): void;
+ /**
+ * Fired when the entire stream has been successfully downloaded.
+ * @event
+ */
+ public on(event: 'finish', listener: () => void): void;
+ /**
+ * Fired when the download process is manually aborted via the `abort()` method.
+ * @event
+ */
+ public on(event: 'abort', listener: () => void): void;
+ public on(event: string, listener: (...data: any[]) => void): void {
+ super.on(event, listener);
+ }
+
+ public once(event: 'formatInitialization', listener: (initializedFormat: InitializedFormat) => void): void;
+ public once(event: 'streamProtectionStatusUpdate', listener: (data: StreamProtectionStatus) => void): void;
+ public once(event: 'reloadPlayerResponse', listener: (reloadPlaybackContext: ReloadPlaybackContext) => void): void;
+ public once(event: 'finish', listener: () => void): void;
+ public once(event: 'abort', listener: () => void): void;
+ public once(event: string, listener: (...args: any[]) => void): void {
+ super.once(event, listener);
+ }
+
+ constructor(config: SabrStreamConfig = {}) {
+ super();
+ this.fetchFunction = config?.fetch || fetch;
+ this.serverAbrStreamingUrl = config.serverAbrStreamingUrl;
+ this.videoPlaybackUstreamerConfig = config.videoPlaybackUstreamerConfig;
+ this.clientInfo = config.clientInfo;
+ this.poToken = config.poToken;
+ this.durationMs = config.durationMs || Infinity;
+ this.formatIds = config.formats || [];
+
+ this.videoStream = new ReadableStream({
+ start: (controller) => {
+ this.videoController = controller;
+ }
+ });
+
+ this.audioStream = new ReadableStream({
+ start: (controller) => {
+ this.audioController = controller;
+ }
+ });
+ }
+
+ /**
+ * Sets Proof of Origin (PO) token.
+ * @param poToken - The base64-encoded token string.
+ */
+ public setPoToken(poToken: string): void {
+ this.poToken = poToken;
+ }
+
+ /**
+ * Sets the available server ABR formats.
+ * @param formats - An array of available SabrFormat objects.
+ */
+ public setServerAbrFormats(formats: SabrFormat[]): void {
+ this.formatIds.push(...formats);
+ }
+
+ /**
+ * Sets the total duration of the stream in milliseconds.
+ * This is optional as duration is often determined automatically from format metadata.
+ * @param durationMs - The duration in milliseconds.
+ */
+ public setDurationMs(durationMs: number): void {
+ this.durationMs = durationMs;
+ }
+
+ /**
+ * Sets the server ABR streaming URL for media requests.
+ * @param url - The streaming URL.
+ */
+ public setStreamingURL(url: string): void {
+ this.serverAbrStreamingUrl = url;
+ }
+
+ /**
+ * Sets the Ustreamer configuration string.
+ * @param config - The Ustreamer configuration.
+ */
+ public setUstreamerConfig(config: string): void {
+ this.videoPlaybackUstreamerConfig = config;
+ }
+
+ /**
+ * Sets the client information used in SABR requests.
+ * @param clientInfo - The client information object.
+ */
+ public setClientInfo(clientInfo: ClientInfo): void {
+ this.clientInfo = clientInfo;
+ }
+
+ /**
+ * Aborts the download process, closing all streams and cleaning up resources.
+ * Emits an 'abort' event.
+ */
+ public abort(): void {
+ this.logger.debug(TAG, 'Aborting download process');
+
+ this._aborted = true;
+
+ this.abortController?.abort();
+
+ this.videoController?.error(new Error('Download aborted.'));
+ this.audioController?.error(new Error('Download aborted.'));
+
+ this.resetState();
+
+ this.emit('abort');
+ }
+
+ //#region --- Stream Initialization and Lifecycle Control ---
+
+ /**
+ * Returns a serializable state object that can be used to restore the stream later.
+ * @throws {Error} If the main format is not initialized.
+ * @returns The current state of the stream.
+ */
+ public getState(): SabrStreamState {
+ if (!this.mainFormat)
+ throw new Error('Main format is not initialized, cannot get state.');
+
+ const playerTimeMs = getTotalDownloadedDuration(this.mainFormat);
+ const initializedFormats: SabrStreamState['initializedFormats'] = [];
+
+ for (const [ formatKey, format ] of this.initializedFormatsMap.entries()) {
+ initializedFormats.push({
+ formatKey,
+ formatInitializationMetadata: format.formatInitializationMetadata,
+ downloadedSegments: Array.from(format.downloadedSegments.entries()),
+ lastMediaHeaders: format.lastMediaHeaders
+ });
+ }
+
+ return {
+ durationMs: this.durationMs,
+ requestNumber: this.requestNumber,
+ activeSabrContexts: Array.from(this.activeSabrContextTypes),
+ sabrContextUpdates: Array.from(this.sabrContexts.entries()),
+ formatToDiscard: this.formatToDiscard,
+ cachedBufferedRanges: this.cachedBufferedRanges || [],
+ nextRequestPolicy: this.nextRequestPolicy,
+ initializedFormats,
+ playerTimeMs
+ };
+ }
+
+ /**
+ * Initiates the streaming process for the selected formats.
+ * @param options - Playback options, including format preferences and initial state.
+ * @throws {Error} If no suitable formats are found or streaming fails.
+ * @returns A promise that resolves with the video/audio streams and selected formats.
+ */
+ public async start(options: SabrPlaybackOptions): Promise<{
+ videoStream: ReadableStream;
+ audioStream: ReadableStream;
+ selectedFormats: SelectedFormats;
+ }> {
+ const { videoFormat, audioFormat } = this.selectFormats(options);
+ this.setupStreamingProcess(videoFormat, audioFormat, options).then();
+ return {
+ videoStream: this.videoStream,
+ audioStream: this.audioStream,
+ selectedFormats: { videoFormat, audioFormat }
+ };
+ }
+
+ /**
+ * Sets up and manages the main streaming loop.
+ * @param videoFormat - The selected video format.
+ * @param audioFormat - The selected audio format.
+ * @param options - Playback options.
+ * @private
+ */
+ private async setupStreamingProcess(
+ videoFormat: SabrFormat,
+ audioFormat: SabrFormat,
+ options: SabrPlaybackOptions
+ ): Promise {
+ try {
+ this._errored = false;
+ this._aborted = false;
+
+ let playerTimeMs = 0;
+
+ if (options.state && this.restoreState(videoFormat, audioFormat, options.state)) {
+ playerTimeMs = options.state.playerTimeMs || 0;
+ }
+
+ const maxRetries = options.maxRetries !== undefined ? options.maxRetries : DEFAULT_MAX_RETRIES;
+ const enabledTrackTypesBitfield = options.enabledTrackTypes ?? EnabledTrackTypes.VIDEO_AND_AUDIO;
+
+ const abrState = {
+ playerTimeMs,
+ audioTrackId: audioFormat.audioTrackId,
+ playbackRate: 1,
+ stickyResolution: videoFormat.height || 360,
+ drcEnabled: audioFormat.isDrc,
+ clientViewportIsFlexible: false,
+ visibility: 1,
+ enabledTrackTypesBitfield
+ };
+
+ // NOTE: 0 - video & audio, 1 - audio only, 2 - video only
+ if (abrState.enabledTrackTypesBitfield === 1 || abrState.enabledTrackTypesBitfield === 2) {
+ this.formatToDiscard = abrState.enabledTrackTypesBitfield === 1 ?
+ FormatKeyUtils.fromFormat(videoFormat) :
+ FormatKeyUtils.fromFormat(audioFormat);
+ }
+
+ while (abrState.playerTimeMs < this.durationMs) {
+ if (this._aborted) {
+ this.logger.debug(TAG, 'Download process aborted, exiting streaming loop.');
+ break;
+ }
+
+ this.logger.debug(TAG, `Starting new segment fetch at playback position: ${abrState.playerTimeMs}ms`);
+
+ this.mainFormat = abrState.enabledTrackTypesBitfield === 1 ?
+ this.initializedFormatsMap.get(FormatKeyUtils.fromFormat(audioFormat) || '') :
+ this.initializedFormatsMap.get(FormatKeyUtils.fromFormat(videoFormat) || '');
+
+ if (this.mainFormat)
+ this.validateAndCorrectDuration(this.mainFormat.formatInitializationMetadata);
+
+ abrState.playerTimeMs = this.mainFormat ? getTotalDownloadedDuration(this.mainFormat) : 0;
+
+ this.checkForStall({
+ playerTimeMs: abrState.playerTimeMs,
+ stallDetectionMs: options.stallDetectionMs
+ });
+
+ const success = await this.executeWithRetry(
+ () => this.fetchAndProcessSegments(
+ abrState,
+ audioFormat,
+ videoFormat
+ ),
+ maxRetries
+ );
+
+ if (!success) break;
+ }
+ } catch (error) {
+ if (!this._aborted) {
+ this.errorHandler(error as Error, true);
+ }
+ } finally {
+ if (!this._aborted) {
+ this.validateDownloadedSegments();
+ if (!this._errored) {
+ this.videoController?.close();
+ this.audioController?.close();
+ }
+ this.resetState();
+ this.emit('finish');
+ }
+ }
+ }
+
+ /**
+ * Restores the stream state from a previously saved state object.
+ * @param videoFormat - The selected video format.
+ * @param audioFormat - The selected audio format.
+ * @param state - The saved state object.
+ * @returns `true` if the state was restored successfully, `false` otherwise.
+ * @private
+ */
+ private restoreState(
+ videoFormat: SabrFormat,
+ audioFormat: SabrFormat,
+ state: SabrStreamState
+ ): boolean {
+ this.resetState();
+
+ if (!state || typeof state !== 'object' || !state.initializedFormats || !Array.isArray(state.initializedFormats) || !state.durationMs || !state.playerTimeMs) {
+ this.logger.warn(TAG, 'Invalid or corrupt state object provided. Starting fresh.');
+ return false;
+ }
+
+ const expectedVideoFormatKey = FormatKeyUtils.fromFormat(videoFormat) || '';
+ const expectedAudioFormatKey = FormatKeyUtils.fromFormat(audioFormat) || '';
+
+ for (const format of state.initializedFormats) {
+ const { formatKey, formatInitializationMetadata, downloadedSegments, lastMediaHeaders } = format;
+
+ if (formatKey !== expectedVideoFormatKey && formatKey !== expectedAudioFormatKey) {
+ this.logger.warn(TAG, `State contains an unexpected format key "${formatKey}". It will be ignored.`);
+ continue;
+ }
+
+ this.initializedFormatsMap.set(formatKey, {
+ formatInitializationMetadata,
+ downloadedSegments: new Map(downloadedSegments),
+ lastMediaHeaders: lastMediaHeaders || []
+ });
+ }
+
+ if (!this.initializedFormatsMap.has(expectedVideoFormatKey) || !this.initializedFormatsMap.has(expectedAudioFormatKey)) {
+ this.logger.warn(TAG, 'State is missing required format data for the selected video/audio formats. Starting fresh.');
+ this.resetState();
+ return false;
+ }
+
+ this.durationMs = state.durationMs;
+ this.requestNumber = state.requestNumber || 0;
+ this.activeSabrContextTypes = new Set(state.activeSabrContexts || []);
+ this.sabrContexts = new Map(state.sabrContextUpdates || []);
+ this.formatToDiscard = state.formatToDiscard;
+ this.cachedBufferedRanges = state.cachedBufferedRanges || [];
+ this.nextRequestPolicy = state.nextRequestPolicy;
+
+ return true;
+ }
+
+ /**
+ * Checks if the download has stalled by tracking progress over time.
+ * @param options - Configuration for stall detection.
+ * @returns `true` if a stall was detected but is within the retry limit, `false` otherwise.
+ * @throws {Error} If the maximum number of consecutive stalls is reached.
+ * @private
+ */
+ private checkForStall(options: {
+ stallDetectionMs?: number,
+ playerTimeMs: number
+ }): boolean {
+ const currentTime = Date.now();
+ const currentProgress = options.playerTimeMs;
+ const stallThreshold = options.stallDetectionMs || DEFAULT_STALL_DETECTION_MS;
+
+ if (currentProgress > this.progressTracker.lastDownloadedDuration) {
+ this.progressTracker.lastProgressTime = currentTime;
+ this.progressTracker.lastDownloadedDuration = currentProgress;
+ this.progressTracker.stallCount = 0;
+ return false;
+ } else if (currentTime - this.progressTracker.lastProgressTime > stallThreshold) {
+ this.progressTracker.stallCount++;
+ this.logger.warn(TAG, `Stream stalled for ${stallThreshold}ms (stall #${this.progressTracker.stallCount})`);
+
+ if (this.progressTracker.stallCount >= MAX_STALLS) {
+ throw new Error(`Stream stalled ${MAX_STALLS} times, aborting`);
+ }
+
+ this.progressTracker.lastProgressTime = currentTime;
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Selects the best video and audio formats based on provided options.
+ * @param options - Format selection options and quality preferences.
+ * @throws {Error} If no suitable formats are found or the duration is invalid.
+ * @returns The selected video and audio formats.
+ * @private
+ */
+ private selectFormats(options: SabrPlaybackOptions): SelectedFormats {
+ const videoFormat = chooseFormat(this.formatIds, options.videoFormat, {
+ quality: options.videoQuality,
+ preferWebM: options.preferWebM,
+ preferH264: options.preferH264,
+ preferMP4: options.preferMP4,
+ isAudio: false
+ });
+
+ const audioFormat = chooseFormat(this.formatIds, options.audioFormat, {
+ quality: options.audioQuality,
+ language: options.audioLanguage,
+ preferOpus: options.preferOpus,
+ preferMP4: options.preferMP4,
+ preferWebM: options.preferWebM,
+ isAudio: true
+ });
+
+ if (this.durationMs < 0) {
+ throw new Error('Invalid duration');
+ }
+
+ if (!videoFormat || !audioFormat) {
+ throw new Error('No suitable formats found for download');
+ }
+
+ return { videoFormat, audioFormat };
+ }
+ //#endregion
+
+ //#region --- Segment Fetching and Network Communication ---
+
+ /**
+ * Fetches and processes media segments from the server for the current ABR state.
+ * @param abrState - The current client adaptive bitrate state.
+ * @param selectedAudioFormat - The selected audio format.
+ * @param selectedVideoFormat - The selected video format.
+ * @throws {Error} If the server returns an error or no valid data.
+ * @private
+ */
+ private async fetchAndProcessSegments(
+ abrState: ClientAbrState,
+ selectedAudioFormat: SabrFormat,
+ selectedVideoFormat: SabrFormat
+ ): Promise {
+ const initializedVideoFormat = this.initializedFormatsMap.get(FormatKeyUtils.fromFormat(selectedVideoFormat) || '');
+ const initializedAudioFormat = this.initializedFormatsMap.get(FormatKeyUtils.fromFormat(selectedAudioFormat) || '');
+
+ // Cache buffered ranges in case the request fails, allowing retries to use the same values.
+ if (!this.cachedBufferedRanges?.length) {
+ this.cachedBufferedRanges = this.buildBufferedRanges(initializedVideoFormat, initializedAudioFormat);
+ }
+
+ const requestBody = this.buildRequestBody(abrState, selectedAudioFormat, selectedVideoFormat);
+
+ this.mediaHeadersProcessed = false;
+ const response = await this.makeStreamingRequest(requestBody);
+ const processedParts = await this.processStreamingResponse(response);
+
+ if (!processedParts.length) {
+ throw new Error('No valid parts received from server.');
+ } else if ((this.streamProtectionStatus?.status || 0) >= 2 && !processedParts.includes(UMPPartId.MEDIA)) {
+ throw new Error('No media parts or protocol updates received from server.');
+ }
+
+ if (
+ processedParts.includes(UMPPartId.MEDIA_HEADER) &&
+ (initializedVideoFormat?.lastMediaHeaders?.length && initializedAudioFormat?.lastMediaHeaders?.length) ||
+ (abrState.enabledTrackTypesBitfield !== 0 && this.mainFormat?.lastMediaHeaders?.length)
+ ) {
+ this.mediaHeadersProcessed = true;
+ }
+ }
+
+ /**
+ * Constructs an array of `BufferedRange` objects from initialized formats.
+ * @param initializedVideoFormat - The initialized video format, if available.
+ * @param initializedAudioFormat - The initialized audio format, if available.
+ * @returns An array of `BufferedRange` objects.
+ * @private
+ */
+ private buildBufferedRanges(
+ initializedVideoFormat?: InitializedFormat,
+ initializedAudioFormat?: InitializedFormat
+ ): BufferedRange[] {
+ const bufferedRanges: BufferedRange[] = [];
+ const formats = [ initializedVideoFormat, initializedAudioFormat ];
+
+ for (const initializedFormat of formats) {
+ if (!initializedFormat?.lastMediaHeaders.length) {
+ continue;
+ }
+
+ if (
+ // Skip formats marked for discarding; a dummy range will be created for them later.
+ FormatKeyUtils.fromFormatInitializationMetadata(initializedFormat.formatInitializationMetadata) === this.formatToDiscard
+ ) {
+ continue;
+ }
+
+ const mediaHeaders = initializedFormat.lastMediaHeaders;
+ const durationMs = mediaHeaders.reduce((sum, header) => sum + (header.durationMs || 0), 0);
+
+ bufferedRanges.push({
+ durationMs,
+ formatId: initializedFormat.formatInitializationMetadata.formatId,
+ startTimeMs: mediaHeaders[0].startMs || 0,
+ startSegmentIndex: mediaHeaders[0].sequenceNumber || 1,
+ endSegmentIndex: mediaHeaders[mediaHeaders.length - 1].sequenceNumber || 1,
+ timeRange: {
+ durationTicks: durationMs,
+ startTicks: mediaHeaders[0].startMs,
+ timescale: mediaHeaders[0].timeRange?.timescale
+ }
+ });
+
+ initializedFormat.lastMediaHeaders = [];
+ }
+
+ return bufferedRanges;
+ }
+
+ /**
+ * Builds the protobuf request body for a `VideoPlaybackAbrRequest`.
+ * @param abrState - The current client adaptive bitrate state.
+ * @param selectedAudioFormat - The selected audio format.
+ * @param selectedVideoFormat - The selected video format.
+ * @returns The encoded request body as a `Uint8Array`.
+ * @throws {Error} If required configuration (ustreamer config, client info) is missing.
+ * @private
+ */
+ private buildRequestBody(
+ abrState: ClientAbrState,
+ selectedAudioFormat: SabrFormat,
+ selectedVideoFormat: SabrFormat
+ ): Uint8Array {
+ if (!this.videoPlaybackUstreamerConfig)
+ throw new Error('Video playback ustreamer config must be set before starting.');
+ if (!this.clientInfo)
+ throw new Error('Client info must be set before starting.');
+
+ const bufferedRanges = this.cachedBufferedRanges || [];
+ const { sabrContexts, unsentSabrContexts } = this.prepareSabrContexts();
+
+ const { selectedFormatIds, updatedBufferedRanges } = this.prepareFormatSelections(
+ [ selectedVideoFormat, selectedAudioFormat ],
+ bufferedRanges
+ );
+
+ return VideoPlaybackAbrRequest.encode({
+ clientAbrState: abrState,
+ preferredAudioFormatIds: [ selectedAudioFormat ],
+ preferredVideoFormatIds: [ selectedVideoFormat ],
+ preferredSubtitleFormatIds: [],
+ selectedFormatIds,
+ videoPlaybackUstreamerConfig: base64ToU8(this.videoPlaybackUstreamerConfig),
+ streamerContext: {
+ sabrContexts,
+ unsentSabrContexts,
+ poToken: this.poToken ? base64ToU8(this.poToken) : undefined,
+ playbackCookie: this.nextRequestPolicy?.playbackCookie ? PlaybackCookie.encode(this.nextRequestPolicy.playbackCookie).finish() : undefined,
+ clientInfo: this.clientInfo
+ },
+ bufferedRanges: updatedBufferedRanges,
+ field1000: []
+ }).finish();
+ }
+
+ /**
+ * Prepares SABR context data for the request body.
+ * @returns An object containing active and unsent SABR contexts.
+ * @private
+ */
+ private prepareSabrContexts() {
+ const sabrContexts: SabrContextUpdate[] = [];
+ const unsentSabrContexts: number[] = [];
+
+ for (const ctxUpdate of this.sabrContexts.values()) {
+ if (this.activeSabrContextTypes.has(ctxUpdate.type)) {
+ sabrContexts.push(ctxUpdate);
+ } else {
+ unsentSabrContexts.push(ctxUpdate.type);
+ }
+ }
+
+ return { sabrContexts, unsentSabrContexts };
+ }
+
+ /**
+ * Prepares format selections and buffered ranges for the request body.
+ * @param formats - An array of formats to process.
+ * @param currentBufferedRanges - The current buffered ranges to update.
+ * @returns An object with selected format IDs and updated buffered ranges.
+ * @private
+ */
+ private prepareFormatSelections(
+ formats: SabrFormat[],
+ currentBufferedRanges: BufferedRange[]
+ ): { selectedFormatIds: SabrFormat[], updatedBufferedRanges: BufferedRange[] } {
+ const selectedFormatIds: SabrFormat[] = [];
+ const updatedBufferedRanges = [ ...currentBufferedRanges ];
+ const formatsInitialized = this.initializedFormatsMap.size > 0;
+
+ for (const format of formats) {
+ const formatKey = FormatKeyUtils.fromFormat(format);
+ const shouldDiscard = this.formatToDiscard && formatKey === this.formatToDiscard;
+
+ if (shouldDiscard) {
+ updatedBufferedRanges.push({
+ formatId: format,
+ durationMs: MAX_INT32_VALUE,
+ startTimeMs: 0,
+ startSegmentIndex: MAX_INT32_VALUE,
+ endSegmentIndex: MAX_INT32_VALUE,
+ timeRange: {
+ durationTicks: MAX_INT32_VALUE,
+ startTicks: 0,
+ timescale: 1000
+ }
+ });
+ }
+
+ // Only add format to selectedFormatIds when either:
+ // 1. Formats have been initialized (indicating we've received their metadata).
+ // 2. This format should be discarded (we want the server to acknowledge it's fully buffered).
+ if (formatsInitialized || shouldDiscard) {
+ selectedFormatIds.push(format);
+ }
+ }
+
+ return { selectedFormatIds, updatedBufferedRanges };
+ }
+
+ /**
+ * Executes a streaming POST request to the server.
+ * @param body - The request body payload.
+ * @returns A `Promise` that resolves with the server `Response`.
+ * @throws {Error} If the server ABR streaming URL is not configured or the request fails.
+ * @private
+ */
+ private async makeStreamingRequest(body: Uint8Array): Promise {
+ if (!this.serverAbrStreamingUrl) {
+ throw new Error('Server ABR streaming URL not configured.');
+ }
+
+ const url = new URL(this.serverAbrStreamingUrl);
+ url.searchParams.set('rn', this.requestNumber.toString());
+
+ this.abortController = new AbortController();
+
+ const timeoutId = setTimeout(() => this.abortController?.abort(), 60000);
+
+ try {
+ return await this.fetchFunction(url, {
+ method: 'POST',
+ headers: {
+ 'content-type': 'application/x-protobuf',
+ 'accept-encoding': 'identity',
+ 'accept': 'application/vnd.yt-ump'
+ },
+ body,
+ signal: this.abortController.signal
+ });
+ } finally {
+ clearTimeout(timeoutId);
+ this.requestNumber += 1;
+ }
+ }
+
+ /**
+ * Reads the response body as a stream and processes each UMP part.
+ * @param response - The server response to process.
+ * @returns A promise that resolves to an array of processed UMP part types.
+ * @throws {Error} If the response is invalid, empty, or aborted.
+ * @private
+ */
+ private async processStreamingResponse(response: Response): Promise {
+ if (!response.ok)
+ throw new Error(`Server returned ${response.status} ${response.statusText}`);
+
+ if (response.headers.get('content-type') !== 'application/vnd.yt-ump')
+ throw new Error(`Unexpected content type from server: ${response.headers.get('content-type')}`);
+
+ const reader = response.body!.getReader();
+
+ let dataReceived = false;
+ let partialPart: Part | undefined;
+
+ const processedParts: number[] = [];
+
+ while (true) {
+ if (this.abortController?.signal?.aborted && !this._aborted)
+ throw new Error('Stream was aborted.');
+
+ const { done, value } = await reader.read();
+
+ if (done) {
+ if (!dataReceived) {
+ throw new Error('Received empty response from server.');
+ }
+ break;
+ }
+
+ dataReceived = true;
+
+ let chunk;
+
+ if (partialPart) {
+ chunk = partialPart.data;
+ chunk.append(value);
+ } else {
+ chunk = new CompositeBuffer([ value ]);
+ }
+
+ const ump = new UmpReader(chunk);
+
+ partialPart = ump.read((part) => {
+ processedParts.push(part.type);
+ const handler = this.umpPartHandlers.get(part.type);
+ if (handler) {
+ handler(part);
+ }
+ });
+ }
+
+ this.partialSegmentQueue.clear();
+
+ return processedParts;
+ }
+
+ /**
+ * Executes a function with automatic retries and exponential backoff.
+ * Respects server-specified backoff times from `nextRequestPolicy`.
+ * @param fetchFn - The function to execute.
+ * @param maxRetries - The maximum number of retry attempts.
+ * @returns A promise that resolves to `true` on success, or `false` if all retries fail.
+ * @private
+ */
+ private async executeWithRetry(
+ fetchFn: () => Promise,
+ maxRetries: number
+ ): Promise {
+ const backoffTimeMs = this.nextRequestPolicy?.backoffTimeMs || 0;
+
+ if (backoffTimeMs > 0) {
+ this.logger.debug(TAG, `Respecting server backoff policy: waiting ${backoffTimeMs}ms before request`);
+ await wait(backoffTimeMs);
+ }
+
+ for (let attempt = 1; attempt <= maxRetries + 1; attempt++) {
+ try {
+ await fetchFn();
+ if (this.mediaHeadersProcessed) {
+ this.cachedBufferedRanges = undefined;
+ }
+ return true;
+ } catch (e) {
+ const error = e as Error;
+ if (this._aborted) {
+ this.logger.debug(TAG, 'Download process aborted, skipping retry.');
+ return false;
+ }
+
+ if (attempt > maxRetries) {
+ this.logger.error(TAG, `Maximum retries (${maxRetries}) exceeded while fetching segment: ${error.message}`);
+ this.errorHandler(error, true);
+ break;
+ }
+
+ const retryBackoffMs = Math.min(BACKOFF_MULTIPLIER * Math.pow(2, attempt - 1), MAX_BACKOFF_MS);
+ this.logger.warn(TAG, `Segment fetch attempt ${attempt}/${maxRetries + 1} failed: ${error.message} - retrying in ${retryBackoffMs}ms`);
+ await wait(retryBackoffMs);
+ }
+ }
+ return false;
+ }
+ //#endregion
+
+ //#region --- UMP Part Handlers ---
+
+ /**
+ * Handles `FORMAT_INITIALIZATION_METADATA` parts.
+ * Creates and stores a new `InitializedFormat` entry.
+ * @private
+ */
+ private handleFormatInitializationMetadata(part: Part): void {
+ const formatInitMetadata = FormatInitializationMetadata.decode(part.data.chunks[0]);
+
+ const formatIdKey = FormatKeyUtils.fromFormatInitializationMetadata(formatInitMetadata);
+
+ const initializedFormat: InitializedFormat = {
+ formatInitializationMetadata: formatInitMetadata,
+ downloadedSegments: new Map(),
+ lastMediaHeaders: []
+ };
+
+ this.initializedFormatsMap.set(formatIdKey, initializedFormat);
+
+ this.logger.debug(TAG, `Initialized format: ${formatIdKey}`);
+
+ this.emit('formatInitialization', initializedFormat);
+ }
+
+ /**
+ * Handles `NEXT_REQUEST_POLICY` parts.
+ * Stores the server's policy for backoff time and playback cookies.
+ * @private
+ */
+ private handleNextRequestPolicy(part: Part): void {
+ this.nextRequestPolicy = NextRequestPolicy.decode(part.data.chunks[0]);
+ }
+
+ /**
+ * Handles `SABR_ERROR` parts.
+ * Throws an error to terminate the current request attempt.
+ * @throws {Error} Always throws with the SABR error details.
+ * @private
+ */
+ private handleSabrError(part: Part): void {
+ const sabrError = SabrError.decode(part.data.chunks[0]);
+ throw new Error(`SABR Error: ${sabrError.type} - ${sabrError.code}`);
+ }
+
+ /**
+ * Handles `SABR_REDIRECT` parts.
+ * Updates the streaming URL to the new location provided by the server.
+ * @private
+ */
+ private handleSabrRedirect(part: Part): void {
+ const sabrRedirect = SabrRedirect.decode(part.data.chunks[0]);
+ this.serverAbrStreamingUrl = sabrRedirect.url!;
+ this.logger.debug(TAG, `Redirecting to ${this.serverAbrStreamingUrl}`);
+ }
+
+ /**
+ * Handles `SABR_CONTEXT_UPDATE` parts.
+ * Updates the client's context state based on server instructions.
+ * @private
+ */
+ private handleSabrContextUpdate(part: Part): void {
+ const sabrContextUpdate = SabrContextUpdate.decode(part.data.chunks[0]);
+ if (sabrContextUpdate.type !== undefined && sabrContextUpdate.value?.length) {
+ if (
+ sabrContextUpdate.writePolicy === SabrContextWritePolicy.KEEP_EXISTING &&
+ this.sabrContexts.has(sabrContextUpdate.type)
+ ) {
+ this.logger.debug(TAG, `Skipping SABR context update for type ${sabrContextUpdate.type}`);
+ return;
+ }
+
+ this.sabrContexts.set(sabrContextUpdate.type, sabrContextUpdate);
+
+ if (sabrContextUpdate.sendByDefault) {
+ this.activeSabrContextTypes.add(sabrContextUpdate.type);
+ }
+
+ this.logger.debug(TAG, `Received SABR context update (type: ${sabrContextUpdate.type}, sendByDefault: ${sabrContextUpdate.sendByDefault})`);
+ }
+ }
+
+ /**
+ * Handles `SABR_CONTEXT_SENDING_POLICY` parts.
+ * Updates which contexts should be sent in future requests.
+ * @private
+ */
+ private handleSabrContextSendingPolicy(part: Part): void {
+ const sabrContextSendingPolicy = SabrContextSendingPolicy.decode(part.data.chunks[0]);
+
+ for (const startPolicy of sabrContextSendingPolicy.startPolicy) {
+ if (!this.activeSabrContextTypes.has(startPolicy)) {
+ this.activeSabrContextTypes.add(startPolicy);
+ this.logger.debug(TAG, `Activated SABR context for type ${startPolicy}`);
+ }
+ }
+
+ for (const stopPolicy of sabrContextSendingPolicy.stopPolicy) {
+ if (this.activeSabrContextTypes.has(stopPolicy)) {
+ this.activeSabrContextTypes.delete(stopPolicy);
+ this.logger.debug(TAG, `Deactivated SABR context for type ${stopPolicy}`);
+ }
+ }
+
+ for (const discardPolicy of sabrContextSendingPolicy.discardPolicy) {
+ if (this.sabrContexts.has(discardPolicy)) {
+ this.sabrContexts.delete(discardPolicy);
+ this.logger.debug(TAG, `Discarded SABR context for type ${discardPolicy}`);
+ }
+ }
+ }
+
+ /**
+ * Handles `STREAM_PROTECTION_STATUS` parts.
+ * Emits updates and handles critical statuses like required attestation.
+ * @throws {Error} If attestation is required (status 3).
+ * @private
+ */
+ private handleStreamProtectionStatus(part: Part): void {
+ this.streamProtectionStatus = StreamProtectionStatus.decode(part.data.chunks[0]);
+ this.emit('streamProtectionStatusUpdate', this.streamProtectionStatus);
+ if (this.streamProtectionStatus.status === 3) {
+ throw new Error('Cannot proceed with stream: attestation required');
+ } else if (this.streamProtectionStatus.status === 2) {
+ this.logger.warn(TAG, 'Attestation pending.');
+ }
+ }
+
+ /**
+ * Handles `RELOAD_PLAYER_RESPONSE` parts.
+ * Emits an event with reload parameters and terminates the session.
+ * @throws {Error} Always throws to terminate the current streaming session.
+ * @private
+ */
+ private handleReloadPlayerResponse(part: Part) {
+ const reloadPlaybackContext = ReloadPlaybackContext.decode(part.data.chunks[0]);
+ const errorMessage = 'Player response reload requested by server';
+ this.logger.debug(TAG, `${errorMessage} (token: ${reloadPlaybackContext.reloadPlaybackParams?.token}`);
+ this.emit('reloadPlayerResponse', reloadPlaybackContext);
+ throw new Error(errorMessage);
+ }
+
+ /**
+ * Handles `MEDIA_HEADER` parts.
+ * Creates an entry in the `partialSegmentQueue` for the upcoming media chunks.
+ * @private
+ */
+ private handleMediaHeader(part: Part): void {
+ const mediaHeader = MediaHeader.decode(part.data.chunks[0]);
+
+ const headerId = mediaHeader.headerId || 0;
+ const formatIdKey = FormatKeyUtils.fromMediaHeader(mediaHeader);
+ const segmentNumber = mediaHeader.isInitSeg ? 0 : mediaHeader.sequenceNumber!;
+ const durationMs = mediaHeader.timeRange ? Math.ceil(((mediaHeader.timeRange.durationTicks || 0) / (mediaHeader.timeRange.timescale || 0)) * 1000) : mediaHeader.durationMs || 0;
+
+ const initializedFormat = this.initializedFormatsMap.get(formatIdKey);
+ if (!initializedFormat) {
+ this.logger.warn(TAG, `No initialized format found for key: ${formatIdKey} (segment ${segmentNumber})`);
+ return;
+ }
+
+ const mediaType = getMediaType(initializedFormat);
+
+ if (initializedFormat.downloadedSegments.has(segmentNumber)) {
+ this.logger.debug(TAG, `Segment ${formatIdKey} (segment: ${segmentNumber}) already downloaded. Ignoring.`);
+ return;
+ }
+
+ this.partialSegmentQueue.set(headerId, {
+ formatIdKey,
+ segmentNumber,
+ durationMs,
+ mediaHeader,
+ bufferedChunks: []
+ });
+
+ this.logger.debug(TAG, `Enqueued ${mediaType} segment ${segmentNumber} (Header ID: ${headerId}, key: ${formatIdKey}, duration: ${durationMs}ms)`);
+ }
+
+ /**
+ * Handles `MEDIA` parts.
+ * Buffers media data chunks associated with a specific header ID.
+ * @private
+ */
+ private handleMedia(part: Part): void {
+ const headerId = part.data.getUint8(0);
+ const segment = this.partialSegmentQueue.get(headerId);
+
+ if (!segment) {
+ this.logger.debug(TAG, `Received Media part for an unknown Header ID: ${headerId}`);
+ return;
+ }
+
+ const initializedFormat = this.initializedFormatsMap.get(segment.formatIdKey);
+
+ if (!initializedFormat) {
+ this.logger.warn(TAG, `No initialized format found for key ${segment.formatIdKey} (segment ${segment.segmentNumber})`);
+ return;
+ }
+
+ const dataBuffer = part.data.split(1).remainingBuffer;
+
+ for (const chunk of dataBuffer.chunks) {
+ segment.bufferedChunks.push(chunk);
+ }
+ }
+
+ /**
+ * Handles `MEDIA_END` parts.
+ * Finalizes a segment, enqueues its data to the appropriate stream, and updates tracking.
+ * @private
+ */
+ private handleMediaEnd(part: Part): void {
+ const headerId = part.data.getUint8(0);
+ const segment = this.partialSegmentQueue.get(headerId);
+
+ if (!segment) {
+ this.logger.debug(TAG, `Received MediaEnd for an unknown Header ID: ${headerId}`);
+ return;
+ }
+
+ const loadedBytes = segment.bufferedChunks.reduce((sum, chunk) => sum + chunk.length, 0);
+
+ if (loadedBytes !== segment.mediaHeader.contentLength) {
+ this.logger.warn(TAG, `Content length mismatch for segment ${segment.segmentNumber} (Header ID: ${headerId}, key: ${segment.formatIdKey}, expected: ${segment.mediaHeader.contentLength}, received: ${loadedBytes})`);
+ this.partialSegmentQueue.delete(headerId);
+ return;
+ }
+
+ const initializedFormat = this.initializedFormatsMap.get(segment.formatIdKey);
+
+ if (initializedFormat) {
+ const mediaType = getMediaType(initializedFormat);
+
+ if (segment.bufferedChunks.length) {
+ for (const chunk of segment.bufferedChunks) {
+ if (mediaType === 'audio') {
+ this.audioController?.enqueue(chunk);
+ } else {
+ this.videoController?.enqueue(chunk);
+ }
+ }
+ }
+
+ this.logger.debug(TAG, `Received MediaEnd for ${mediaType} segment ${segment.segmentNumber} (Header ID: ${headerId}, key: ${segment.formatIdKey})`);
+
+ segment.bufferedChunks.length = 0; // Avoid weird mem leaks...
+ segment.bufferedChunks = [];
+
+ initializedFormat.lastMediaHeaders.push(segment.mediaHeader);
+ initializedFormat.downloadedSegments.set(segment.segmentNumber, segment);
+ this.partialSegmentQueue.delete(headerId);
+ }
+ }
+ //#endregion
+
+ //#region --- Stream Validation and Integrity Checks ---
+
+ /**
+ * Validates and corrects the stream duration based on format initialization metadata.
+ * @param formatInitializationMetadata - The metadata from an initialized format.
+ * @private
+ */
+ private validateAndCorrectDuration(formatInitializationMetadata: FormatInitializationMetadata): void {
+ const durationUnits = formatInitializationMetadata.durationUnits || 0;
+ const durationTimescale = formatInitializationMetadata.durationTimescale || 0;
+
+ if (durationTimescale === 0) {
+ this.logger.warn(TAG, 'Invalid timescale (0) in format initialization metadata');
+ return;
+ }
+
+ const expectedDuration = Math.trunc(durationUnits / (durationTimescale / 1000));
+
+ if (this.durationMs !== expectedDuration) {
+ this.durationMs = expectedDuration;
+ this.logger.debug(TAG, `Corrected stream duration to ${this.durationMs}ms based on format initialization metadata`);
+ }
+ }
+
+ /**
+ * Validates downloaded segments for completeness and consistency after the stream finishes.
+ * Checks for duration coverage, missing segments, and duplicates.
+ * @private
+ */
+ private validateDownloadedSegments(): void {
+ for (const [ formatIdKey, initializedFormat ] of this.initializedFormatsMap.entries()) {
+ if (formatIdKey === this.formatToDiscard) {
+ this.logger.debug(TAG, `Skipping validation for discarded format: ${formatIdKey}`);
+ continue;
+ }
+
+ const totalDuration = getTotalDownloadedDuration(initializedFormat);
+ const durationUnits = initializedFormat.formatInitializationMetadata.durationUnits || 0;
+ const durationTimescale = initializedFormat.formatInitializationMetadata.durationTimescale || 0;
+ const expectedDuration = durationTimescale ? durationUnits / (durationTimescale / 1000) : 0;
+
+ const durationMismatch = Math.abs(totalDuration - expectedDuration);
+ if (expectedDuration > 0 && durationMismatch > expectedDuration * 0.01) {
+ const durationCoverage = Math.round((totalDuration / expectedDuration) * 100);
+ this.logger.warn(TAG, `Incomplete stream for format ${formatIdKey}: downloaded ${totalDuration}ms (${durationCoverage}%), expected ${expectedDuration}ms`);
+ }
+
+ const segments = Array.from(initializedFormat.downloadedSegments.entries());
+ if (segments.length === 0) continue;
+
+ segments.sort(([ numA ], [ numB ]) => numA - numB);
+
+ const expectedSegmentCount = initializedFormat.formatInitializationMetadata.endSegmentNumber!;
+ const missingSegments = [];
+
+ // Find all missing segments in the expected range.
+ for (let i = 0; i <= expectedSegmentCount; i++) {
+ if (!initializedFormat.downloadedSegments.has(i)) {
+ missingSegments.push(i);
+ }
+ }
+
+ // Check for duplicate segments (should not happen, but good to validate).
+ const uniqueSegmentCount = new Set(segments.map(([ num ]) => num)).size;
+ const hasDuplicates = uniqueSegmentCount !== segments.length;
+
+ if (missingSegments.length > 0) {
+ const message = `Format ${formatIdKey}: Missing segments: ${missingSegments.join(', ')}. ` +
+ `Expected range: 0-${expectedSegmentCount}. `;
+ this.logger.warn(TAG, message);
+ this.errorHandler(new Error(message), true);
+ } else {
+ this.logger.debug(TAG, `Format ${formatIdKey}: All ${expectedSegmentCount} segments present (100% coverage)`);
+ }
+
+ if (hasDuplicates) {
+ const message = `Format ${formatIdKey}: Found duplicate segment numbers (${segments.length} segments but ${uniqueSegmentCount} unique numbers)`;
+ this.logger.warn(TAG, message);
+ this.errorHandler(new Error(message), true);
+ }
+ }
+ }
+ //#endregion
+
+ /**
+ * Resets the internal state of the stream.
+ * Clears all maps, resets counters, and re-initializes the progress tracker.
+ * @private
+ */
+ private resetState(): void {
+ this.initializedFormatsMap.clear();
+ this.partialSegmentQueue.clear();
+ this.activeSabrContextTypes.clear();
+ this.sabrContexts.clear();
+ this.nextRequestPolicy = undefined;
+ this.mainFormat = undefined;
+ this.requestNumber = 0;
+ this.cachedBufferedRanges = undefined;
+ this.mediaHeadersProcessed = false;
+ this.streamProtectionStatus = undefined;
+ this.formatToDiscard = undefined;
+ this.abortController = undefined;
+ this.progressTracker = {
+ lastProgressTime: Date.now(),
+ lastDownloadedDuration: 0,
+ stallCount: 0
+ };
+ }
+
+ /**
+ * Handles errors during the streaming process.
+ * @param error - The error that occurred.
+ * @param notifyControllers - Whether to propagate the error to the stream controllers.
+ * @private
+ */
+ private errorHandler(error: Error, notifyControllers: boolean = true): void {
+ this.resetState();
+ this.logger.error(TAG, `Stream error: ${error.message}`);
+ if (notifyControllers) {
+ this._errored = true;
+ this.videoController?.error(error);
+ this.audioController?.error(error);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/core/SabrStreamingAdapter.ts b/src/core/SabrStreamingAdapter.ts
new file mode 100644
index 0000000..16890aa
--- /dev/null
+++ b/src/core/SabrStreamingAdapter.ts
@@ -0,0 +1,652 @@
+import { MAX_INT32_VALUE, base64ToU8, EnabledTrackTypes, parseRangeHeader } from '../utils/shared.js';
+import { fromFormat, fromMediaHeader } from '../utils/formatKeyUtils.js';
+import { Logger } from '../utils/Logger.js';
+
+import {
+ CacheManager,
+ RequestMetadataManager,
+ SabrAdapterError
+} from '../utils/index.js';
+
+import {
+ PlaybackCookie,
+ SabrContextWritePolicy,
+ VideoPlaybackAbrRequest,
+ type BufferedRange,
+ type FormatId,
+ type ReloadPlaybackContext,
+ type SabrContextUpdate,
+ type SnackbarMessage,
+ type StreamerContext
+} from '../utils/Protos.js';
+
+import type {
+ PlayerHttpRequest,
+ PlayerHttpResponse,
+ SabrOptions,
+ SabrPlayerAdapter
+} from '../types/sabrStreamingAdapterTypes.js';
+
+import type { SabrFormat } from '../types/shared.js';
+
+interface InitializedFormat {
+ lastSegmentMetadata: {
+ formatId: FormatId;
+ startSequenceNumber: number;
+ endSequenceNumber: number;
+ startTimeMs: number;
+ durationMs: number;
+ timescale: number;
+ };
+}
+
+type OnSnackbarMessageCb = (snackbarMessage: SnackbarMessage) => void;
+type OnReloadPlayerResponseCb = (reloadPlaybackContext: ReloadPlaybackContext) => Promise;
+type OnMintPoTokenCallback = () => Promise;
+
+const TAG = 'SabrStreamingAdapter';
+
+export const SABR_CONSTANTS = {
+ PROTOCOL: 'sabr:',
+ KEY_PARAM: 'key',
+ DEFAULT_OPTIONS: {
+ enableCaching: true,
+ enableVerboseRequestLogging: false,
+ maxCacheSizeMB: 3,
+ maxCacheAgeSeconds: 300
+ }
+} as const;
+
+/**
+ * Standard UMP request body bytes.
+ * These bytes represent a minimal valid protobuf message for UMP.
+ */
+const UMP_REQUEST_BODY = new Uint8Array([ 120, 0 ]);
+
+/**
+ * Adapter class that handles YouTube SABR integration with media players (e.g., Shaka Player).
+ *
+ * What it does:
+ * - Sets up request/response interceptors so we can send proper SABR requests (UMP response parsing must be done in the player adapter).
+ * - Keeps track of initialized formats and their metadata.
+ * - Handles SABR-specific things, such as redirects, context updates, and playback cookies.
+ */
+export class SabrStreamingAdapter {
+ private readonly playerAdapter: SabrPlayerAdapter;
+ private readonly requestMetadataManager: RequestMetadataManager;
+ private readonly initializedFormats = new Map();
+ private readonly logger = Logger.getInstance();
+
+ private options: SabrOptions;
+ private ustreamerConfig?: string;
+ private serverAbrStreamingUrl?: string;
+
+ private sabrFormats: SabrFormat[] = [];
+ private sabrContexts = new Map();
+ private activeSabrContextTypes = new Set();
+ private lastPlaybackCookie?: PlaybackCookie;
+ private cacheManager: CacheManager | null = null;
+ private requestNumber = 0;
+
+ private activeDelayPromise: Promise | null = null;
+ private onReloadPlayerResponseCallback?: OnReloadPlayerResponseCb;
+ private onSnackbarMessageCallback?: OnSnackbarMessageCb;
+ private onMintPoTokenCallback?: OnMintPoTokenCallback;
+
+ public isDisposed = false;
+
+ /**
+ * Registers a callback function to handle snackbar messages.
+ */
+ public onSnackbarMessage(cb: OnSnackbarMessageCb) {
+ this.onSnackbarMessageCallback = cb;
+ }
+
+ /**
+ * Handles server requests to reload the player with new parameters.
+ * @param cb
+ */
+ public onReloadPlayerResponse(cb: OnReloadPlayerResponseCb) {
+ this.onReloadPlayerResponseCallback = cb;
+ }
+
+ /**
+ * Registers a callback function to mint a new PoToken.
+ * @param cb
+ */
+ public onMintPoToken(cb: OnMintPoTokenCallback) {
+ this.onMintPoTokenCallback = cb;
+ }
+
+ /**
+ * @param options - Configuration options for the adapter.
+ * @throws SabrAdapterError if a player adapter is not provided.
+ */
+ constructor(options: SabrOptions) {
+ this.options = {
+ ...SABR_CONSTANTS.DEFAULT_OPTIONS,
+ ...options
+ };
+
+ if (options.playerAdapter) {
+ this.playerAdapter = options.playerAdapter;
+ } else throw new SabrAdapterError('A player adapter is required.');
+
+ if (this.options.enableCaching) {
+ this.cacheManager = new CacheManager(
+ this.options.maxCacheSizeMB,
+ this.options.maxCacheAgeSeconds
+ );
+ }
+
+ this.requestMetadataManager = new RequestMetadataManager();
+ }
+
+ /**
+ * Initializes the player adapter and sets up request/response interceptors.
+ * @throws SabrAdapterError if the adapter has been disposed.
+ */
+ public attach(player: any): void {
+ this.checkDisposed();
+ this.playerAdapter.initialize(player, this.requestMetadataManager, this.cacheManager);
+ this.setupInterceptors();
+ }
+
+ /**
+ * Sets the initial server abr streaming URL.
+ * @throws SabrAdapterError if the adapter has been disposed.
+ */
+ public setStreamingURL(url?: string) {
+ this.checkDisposed();
+ this.serverAbrStreamingUrl = url;
+ }
+
+ /**
+ * Sets the ustreamer configuration for SABR requests.
+ * @throws SabrAdapterError if the adapter has been disposed.
+ */
+ public setUstreamerConfig(ustreamerConfig?: string) {
+ this.checkDisposed();
+ this.ustreamerConfig = ustreamerConfig;
+ }
+
+ /**
+ * Sets the available SABR formats for streaming.
+ * @throws SabrAdapterError if the adapter has been disposed.
+ */
+ public setServerAbrFormats(sabrFormats: SabrFormat[]) {
+ this.checkDisposed();
+ this.sabrFormats = sabrFormats;
+ }
+
+ /**
+ * Returns the cache manager instance, if caching is enabled.
+ */
+ public getCacheManager(): CacheManager | null {
+ return this.cacheManager;
+ }
+
+ private setupInterceptors(): void {
+ this.playerAdapter.registerRequestInterceptor(this.handleRequest.bind(this));
+ this.playerAdapter.registerResponseInterceptor(this.handleResponse.bind(this));
+ }
+
+ /**
+ * Processes incoming requests and modifies them to conform to SABR protocol requirements.
+ * For SABR protocol URIs, prepares a VideoPlaybackAbrRequest with current state information.
+ * For regular URIs with UMP requirements, adds necessary query parameters.
+ * @returns Modified request with SABR-specific changes.
+ */
+ private async handleRequest(request: PlayerHttpRequest) {
+ const originalUri = new URL(request.url);
+
+ if (originalUri.protocol === SABR_CONSTANTS.PROTOCOL) {
+ if (this.activeDelayPromise)
+ await this.activeDelayPromise;
+
+ if (!this.serverAbrStreamingUrl) {
+ throw new SabrAdapterError('Server ABR URL not set.');
+ }
+
+ if (!this.sabrFormats.length) {
+ throw new SabrAdapterError('No SABR formats available.');
+ }
+
+ const requestNumber = String(this.requestNumber++);
+
+ // Set the request number in the URL so we can identify it later (and also for the server).
+ const sabrUrl = new URL(this.serverAbrStreamingUrl || '');
+ sabrUrl.searchParams.set('rn', requestNumber);
+ request.url = sabrUrl.toString();
+
+ const currentFormat = this.sabrFormats.find(
+ (format) => fromFormat(format) === (originalUri.searchParams.get(SABR_CONSTANTS.KEY_PARAM) || '')
+ );
+
+ if (!currentFormat)
+ throw new SabrAdapterError(`Could not determine current format from URL: ${request.url}`);
+
+ const activeFormats = this.playerAdapter.getActiveTrackFormats(currentFormat, this.sabrFormats);
+ const videoPlaybackAbrRequest = await this.createVideoPlaybackAbrRequest(request, currentFormat, activeFormats);
+
+ if (currentFormat.height) {
+ videoPlaybackAbrRequest.clientAbrState!.stickyResolution = currentFormat.height;
+ videoPlaybackAbrRequest.clientAbrState!.lastManualSelectedResolution = currentFormat.height;
+ }
+
+ const formatToDiscard = this.addBufferingInfoToAbrRequest(videoPlaybackAbrRequest, currentFormat, activeFormats);
+
+ if (formatToDiscard) {
+ videoPlaybackAbrRequest.selectedFormatIds.push(formatToDiscard);
+ }
+
+ if (!request.segment.isInit()) {
+ videoPlaybackAbrRequest.selectedFormatIds.push(currentFormat);
+ }
+
+ if (this.options.enableVerboseRequestLogging)
+ this.logger.debug(TAG, `Created VideoPlaybackAbrRequest (${requestNumber}):`, videoPlaybackAbrRequest);
+
+ request.body = VideoPlaybackAbrRequest.encode(videoPlaybackAbrRequest).finish();
+
+ this.requestMetadataManager.metadataMap.set(requestNumber, {
+ format: currentFormat,
+ isUMP: true,
+ isSABR: true,
+ isInit: request.segment.isInit(),
+ byteRange: parseRangeHeader(request.headers.Range),
+ timestamp: Date.now()
+ });
+ } else {
+ const webPoToken = this.onMintPoTokenCallback ? await this.onMintPoTokenCallback() : undefined;
+
+ // Handle simple UMP requests.
+ if (originalUri.pathname.includes('videoplayback/expire') || originalUri.pathname.includes('source/yt_live_broadcast')) {
+ originalUri.pathname += '/ump/1';
+ originalUri.pathname += '/srfvp/1';
+ originalUri.pathname += '/alr/yes';
+ if (webPoToken)
+ originalUri.pathname += `/pot/${webPoToken}`;
+ if (request.headers.Range)
+ originalUri.pathname += `/range/${request.headers.Range?.split('=')[1]}`;
+ } else {
+ originalUri.searchParams.set('ump', '1');
+ originalUri.searchParams.set('srfvp', '1');
+ originalUri.searchParams.set('alr', 'yes');
+ if (webPoToken)
+ originalUri.searchParams.set('pot', webPoToken);
+ if (request.headers.Range)
+ originalUri.searchParams.set('range', request.headers.Range?.split('=')[1]);
+ }
+
+ const requestNumber = String(this.requestNumber++);
+ originalUri.searchParams.set('rn', requestNumber);
+
+ request.url = originalUri.toString();
+ request.body = UMP_REQUEST_BODY;
+
+ this.requestMetadataManager.metadataMap.set(requestNumber, {
+ isUMP: true,
+ isSABR: false,
+ timestamp: Date.now()
+ });
+ }
+
+ request.method = 'POST';
+
+ delete request.headers.Range;
+
+ return request;
+ }
+
+ /**
+ * Creates a VideoPlaybackAbrRequest object with current playback state information.
+ * @param request - The original player HTTP request.
+ * @param currentFormat - The format currently being fetched.
+ * @param activeFormats - Object containing references to active audio and video formats.
+ * @returns A populated VideoPlaybackAbrRequest object.
+ * @throws SabrAdapterError if ustreamer config is not set.
+ */
+ private async createVideoPlaybackAbrRequest(
+ request: PlayerHttpRequest,
+ currentFormat: SabrFormat,
+ activeFormats: {
+ audioFormat?: SabrFormat;
+ videoFormat?: SabrFormat;
+ }
+ ): Promise {
+ if (!this.ustreamerConfig) {
+ throw new SabrAdapterError('Ustreamer config not set');
+ }
+
+ const streamerContext: StreamerContext = {
+ poToken: this.onMintPoTokenCallback ? base64ToU8(await this.onMintPoTokenCallback()) : undefined,
+ playbackCookie: this.lastPlaybackCookie ? PlaybackCookie.encode(this.lastPlaybackCookie).finish() : undefined,
+ clientInfo: this.options.clientInfo,
+ sabrContexts: [],
+ unsentSabrContexts: []
+ };
+
+ for (const ctxUpdate of this.sabrContexts.values()) {
+ if (this.activeSabrContextTypes.has(ctxUpdate.type)) {
+ streamerContext.sabrContexts.push(ctxUpdate);
+ } else {
+ streamerContext.unsentSabrContexts.push(ctxUpdate.type);
+ }
+ }
+
+ return {
+ clientAbrState: {
+ playbackRate: this.playerAdapter.getPlaybackRate(),
+ playerTimeMs: Math.round((request.segment.getStartTime() ?? this.playerAdapter.getPlayerTime()) * 1000),
+ timeSinceLastManualFormatSelectionMs: 0,
+ clientViewportIsFlexible: false,
+ bandwidthEstimate: Math.round(this.playerAdapter.getBandwidthEstimate() || 0),
+ drcEnabled: currentFormat?.isDrc ?? false,
+ enabledTrackTypesBitfield: currentFormat.width ? EnabledTrackTypes.VIDEO_ONLY : EnabledTrackTypes.AUDIO_ONLY,
+ audioTrackId: currentFormat.audioTrackId
+ },
+ bufferedRanges: [],
+ selectedFormatIds: [],
+ preferredAudioFormatIds: [ activeFormats.audioFormat || {} ],
+ preferredVideoFormatIds: [ activeFormats.videoFormat || {} ],
+ preferredSubtitleFormatIds: [],
+ videoPlaybackUstreamerConfig: base64ToU8(this.ustreamerConfig),
+ streamerContext,
+ field1000: []
+ };
+ }
+
+ /**
+ * Adds buffering information to the ABR request for all active formats.
+ *
+ * NOTE:
+ * On the web, mobile, and TV clients, buffered ranges in combination to player time is what dictates the segments you get.
+ * In our case, we are cheating a bit by abusing the player time field (in clientAbrState), setting it to the exact start
+ * time value of the segment we want, while YouTube simply uses the actual player time.
+ *
+ * We don't have to fully replicate this behavior for two reasons:
+ * 1. The SABR server will only send so much segments for a given player time. That means players like Shaka would
+ * not be able to buffer more than what the server thinks is enough. It would behave like YouTube's.
+ * 2. We don't have to know what segment a buffered range starts/ends at. It is easy to do in Shaka, but not in other players.
+ *
+ * @param videoPlaybackAbrRequest - The ABR request to modify with buffering information.
+ * @param currentFormat - The format currently being requested.
+ * @param activeFormats - References to the currently active audio and video formats.
+ * @returns The format to discard (if any) - typically formats that are active but not currently requested.
+ */
+ private addBufferingInfoToAbrRequest(
+ videoPlaybackAbrRequest: VideoPlaybackAbrRequest,
+ currentFormat: SabrFormat,
+ activeFormats: { audioFormat?: SabrFormat; videoFormat?: SabrFormat }
+ ) {
+ let formatToDiscard: SabrFormat | undefined;
+
+ const currentFormatKey = fromFormat(currentFormat);
+
+ for (const activeFormat of Object.values(activeFormats)) {
+ if (!activeFormat) continue;
+
+ const activeFormatKey = fromFormat(activeFormat);
+ const shouldDiscard = currentFormatKey !== activeFormatKey;
+ const initializedFormat = this.initializedFormats.get(activeFormatKey || '');
+
+ const bufferedRange = shouldDiscard
+ ? this.createFullBufferRange(activeFormat)
+ : this.createPartialBufferRange(initializedFormat);
+
+ if (bufferedRange) {
+ videoPlaybackAbrRequest.bufferedRanges.push(bufferedRange);
+
+ if (shouldDiscard) {
+ formatToDiscard = activeFormat;
+ }
+ }
+ }
+
+ return formatToDiscard;
+ }
+
+ /**
+ * Creates a bogus buffered range for a format. Used when we want to signal to the server to not send any
+ * segments for this format.
+ * @param format - The format to create a full buffer range for.
+ * @returns A BufferedRange object indicating the entire format is buffered.
+ */
+ private createFullBufferRange(format: SabrFormat): BufferedRange {
+ return {
+ formatId: format,
+ durationMs: MAX_INT32_VALUE,
+ startTimeMs: 0,
+ startSegmentIndex: MAX_INT32_VALUE,
+ endSegmentIndex: MAX_INT32_VALUE,
+ timeRange: {
+ durationTicks: MAX_INT32_VALUE,
+ startTicks: 0,
+ timescale: 1000
+ }
+ };
+ }
+
+ /**
+ * Creates a buffered range representing a partially buffered format.
+ * @param initializedFormat - The format with initialization data.
+ * @returns A BufferedRange object with segment information, or null if no metadata is available.
+ */
+ private createPartialBufferRange(initializedFormat?: InitializedFormat): BufferedRange | null {
+ if (!initializedFormat?.lastSegmentMetadata) return null;
+
+ const { formatId, startSequenceNumber, timescale, durationMs, endSequenceNumber } =
+ initializedFormat.lastSegmentMetadata;
+
+ return {
+ formatId,
+ startSegmentIndex: startSequenceNumber,
+ durationMs,
+ startTimeMs: 0,
+ endSegmentIndex: endSequenceNumber,
+ timeRange: {
+ timescale,
+ startTicks: 0,
+ durationTicks: durationMs
+ }
+ };
+ }
+
+ /**
+ * Processes HTTP responses to extract SABR-specific information.
+ * @returns The response object.
+ */
+ private async handleResponse(response: PlayerHttpResponse) {
+ const requestMetadata = this.requestMetadataManager.getRequestMetadata(response.url, true);
+ if (!requestMetadata) return response;
+
+ const { streamInfo, format, byteRange, isSABR } = requestMetadata;
+ if (!streamInfo) return response;
+
+ const retry = async () => {
+ const formatType = format?.width ? 'video' : 'audio';
+ const formatKey = fromFormat(format) || '';
+ const url = new URL(`${SABR_CONSTANTS.PROTOCOL}//${formatType}?${SABR_CONSTANTS.KEY_PARAM}=${formatKey}`);
+ return await this.makeFollowupRequest(response, url.toString(), isSABR, byteRange);
+ };
+
+ if (streamInfo.snackbarMessage) {
+ this.logger.debug(TAG, 'Received snackbar message:', streamInfo.snackbarMessage);
+ if (this.onSnackbarMessageCallback) {
+ this.onSnackbarMessageCallback(streamInfo.snackbarMessage);
+ }
+ }
+
+ if (streamInfo.redirect?.url) {
+ let redirectUrl = new URL(streamInfo.redirect?.url);
+
+ this.logger.info(TAG, `Redirecting to ${redirectUrl}`);
+
+ if (isSABR) {
+ this.serverAbrStreamingUrl = streamInfo.redirect?.url;
+ const formatType = format?.width ? 'video' : 'audio';
+ const formatKey = fromFormat(format) || '';
+ redirectUrl = new URL(`${SABR_CONSTANTS.PROTOCOL}//${formatType}?${SABR_CONSTANTS.KEY_PARAM}=${formatKey}`);
+ }
+
+ // No media data = follow the redirect immediately.
+ if (!response.data?.byteLength) {
+ return await this.makeFollowupRequest(response, redirectUrl.toString(), isSABR, byteRange);
+ }
+ }
+
+ if (streamInfo.nextRequestPolicy) {
+ this.lastPlaybackCookie = streamInfo.nextRequestPolicy?.playbackCookie;
+ const delayMs = streamInfo.nextRequestPolicy.backoffTimeMs || 0;
+
+ if (delayMs > 0 && !this.activeDelayPromise) {
+ this.logger.info(TAG, `Delaying next requests by ${delayMs / 1000} seconds.`);
+ this.activeDelayPromise = new Promise((resolve) => {
+ setTimeout(() => {
+ this.logger.info(TAG, 'Delay completed, resuming requests.');
+ this.activeDelayPromise = null;
+ resolve();
+ }, delayMs);
+ });
+ }
+ }
+
+ if (streamInfo.sabrContextSendingPolicy) {
+ for (const startPolicy of streamInfo.sabrContextSendingPolicy.startPolicy) {
+ if (!this.activeSabrContextTypes.has(startPolicy)) {
+ this.activeSabrContextTypes.add(startPolicy);
+ this.logger.debug(TAG, `Activated SABR context for type ${startPolicy}`);
+ }
+ }
+
+ for (const stopPolicy of streamInfo.sabrContextSendingPolicy.stopPolicy) {
+ if (this.activeSabrContextTypes.has(stopPolicy)) {
+ this.activeSabrContextTypes.delete(stopPolicy);
+ this.logger.debug(TAG, `Deactivated SABR context for type ${stopPolicy}`);
+ }
+ }
+
+ for (const discardPolicy of streamInfo.sabrContextSendingPolicy.discardPolicy) {
+ if (this.sabrContexts.has(discardPolicy)) {
+ this.sabrContexts.delete(discardPolicy);
+ this.logger.debug(TAG, `Discarded SABR context for type ${discardPolicy}`);
+ }
+ }
+ }
+
+ if (streamInfo.sabrContextUpdate && (streamInfo.sabrContextUpdate.type !== undefined && streamInfo.sabrContextUpdate.value?.length)) {
+ if (!this.sabrContexts.has(streamInfo.sabrContextUpdate.type) || streamInfo.sabrContextUpdate.writePolicy === SabrContextWritePolicy.OVERWRITE) {
+ this.logger.debug(TAG, `Received SABR context update (type: ${streamInfo.sabrContextUpdate.type}, writePolicy: ${SabrContextWritePolicy[streamInfo.sabrContextUpdate.writePolicy]} sendByDefault: ${streamInfo.sabrContextUpdate.sendByDefault})`);
+ this.sabrContexts.set(streamInfo.sabrContextUpdate.type, streamInfo.sabrContextUpdate);
+ }
+
+ if (streamInfo.sabrContextUpdate.sendByDefault) {
+ this.activeSabrContextTypes.add(streamInfo.sabrContextUpdate.type);
+ }
+
+ // Retry if we got no media data.
+ if (!response.data?.byteLength) {
+ return retry();
+ }
+ }
+
+ // Try reloading the streaming data, if possible.
+ if (streamInfo.reloadPlaybackContext && this.onReloadPlayerResponseCallback) {
+ this.logger.info(TAG, 'Server requested player reload with new parameters:', streamInfo.reloadPlaybackContext);
+ await this.onReloadPlayerResponseCallback(streamInfo.reloadPlaybackContext);
+ return retry();
+ }
+
+ if (streamInfo.mediaHeader) {
+ const formatKey = fromMediaHeader(streamInfo.mediaHeader);
+
+ if (streamInfo.mediaHeader.isInitSeg)
+ return;
+
+ const initializedFormat = this.initializedFormats.get(formatKey) || {} as InitializedFormat;
+
+ initializedFormat.lastSegmentMetadata = {
+ formatId: streamInfo.mediaHeader.formatId!,
+ startSequenceNumber: streamInfo.mediaHeader.sequenceNumber || 1,
+ endSequenceNumber: streamInfo.mediaHeader.sequenceNumber || 1,
+ startTimeMs: streamInfo.mediaHeader.startMs || 0,
+ durationMs: streamInfo.mediaHeader.durationMs || 0,
+ timescale: streamInfo.mediaHeader.timeRange?.timescale || 1000
+ };
+
+ this.initializedFormats.set(formatKey, initializedFormat);
+ }
+
+ return response;
+ }
+
+ /**
+ * Makes a followup request and updates the original response object with the new data.
+ * @param originalResponse - The original HTTP response.
+ * @param url - The URL to request.
+ * @param isSABR - Whether this is a SABR request.
+ * @param byteRange - Optional byte range for the request.
+ * @returns The updated response.
+ */
+ private async makeFollowupRequest(
+ originalResponse: PlayerHttpResponse,
+ url: string,
+ isSABR?: boolean,
+ byteRange?: { start: number, end: number }
+ ): Promise {
+ if (this.activeDelayPromise)
+ await this.activeDelayPromise;
+
+ const headers = {} as Record;
+
+ // Keep range so we can slice the response (only used for init segments).
+ if (isSABR && byteRange) {
+ headers['Range'] = `bytes=${byteRange.start}-${byteRange.end}`;
+ }
+
+ const redirectResponse = await originalResponse.makeRequest(url, headers);
+ Object.assign(originalResponse, redirectResponse);
+
+ return originalResponse;
+ }
+
+ private checkDisposed() {
+ if (this.isDisposed) {
+ throw new SabrAdapterError('Adapter has been disposed.');
+ }
+ }
+
+ /**
+ * Releases resources and cleans up the adapter instance.
+ * After calling dispose, the adapter can no longer be used.
+ */
+ public dispose() {
+ if (this.isDisposed) return;
+
+ this.cacheManager?.dispose();
+ this.cacheManager = null;
+
+ this.initializedFormats.clear();
+ this.requestMetadataManager.metadataMap.clear();
+ this.sabrContexts.clear();
+ this.activeSabrContextTypes.clear();
+
+ this.lastPlaybackCookie = undefined;
+ this.sabrFormats = [];
+ this.serverAbrStreamingUrl = undefined;
+ this.ustreamerConfig = undefined;
+ this.activeDelayPromise = null;
+ this.playerAdapter.dispose();
+ this.requestNumber = 0;
+
+ this.onReloadPlayerResponseCallback = undefined;
+ this.onSnackbarMessageCallback = undefined;
+ this.onMintPoTokenCallback = undefined;
+
+ this.options = undefined as unknown as SabrOptions;
+ this.isDisposed = true;
+
+ this.logger.debug(TAG, 'Disposed');
+ }
+}
\ No newline at end of file
diff --git a/src/core/SabrUmpProcessor.ts b/src/core/SabrUmpProcessor.ts
new file mode 100644
index 0000000..79b287a
--- /dev/null
+++ b/src/core/SabrUmpProcessor.ts
@@ -0,0 +1,316 @@
+import { concatenateChunks, type CacheManager } from '../utils/index.js';
+
+import { createSegmentCacheKey, fromFormat, fromMediaHeader } from '../utils/formatKeyUtils.js';
+
+import { CompositeBuffer } from './CompositeBuffer.js';
+import { UmpReader } from './UmpReader.js';
+
+import {
+ FormatInitializationMetadata,
+ MediaHeader,
+ NextRequestPolicy,
+ ReloadPlaybackContext,
+ SabrContextSendingPolicy,
+ SabrContextUpdate,
+ SabrError,
+ SabrRedirect,
+ SnackbarMessage,
+ StreamProtectionStatus,
+ UMPPartId
+} from '../utils/Protos.js';
+
+import type { Part } from '../types/shared.js';
+import type { SabrRequestMetadata } from '../types/sabrStreamingAdapterTypes.js';
+
+interface Segment {
+ headerId?: number;
+ mediaHeader: MediaHeader;
+ complete?: boolean;
+ bufferedChunks: Uint8Array[];
+ lastChunkSize: number;
+}
+
+export interface UmpProcessingResult {
+ data?: Uint8Array;
+ done: boolean;
+}
+
+type UmpPartHandler = (part: Part) => UmpProcessingResult | undefined;
+
+/**
+ * This class is responsible for reading a UMP stream, handling different part types
+ * (like media headers, media data, and server directives), and populating a
+ * metadata object with the extracted information. It is supposed to be used
+ * in conjunction with a {@linkcode SabrPlayerAdapter} in video player
+ * implementations.
+ */
+export class SabrUmpProcessor {
+ public partialPart?: Part;
+ private readonly formatInitMetadata: FormatInitializationMetadata[] = [];
+ private desiredHeaderId?: number;
+ private partialSegments = new Map();
+
+ private readonly umpPartHandlers = new Map([
+ [ UMPPartId.FORMAT_INITIALIZATION_METADATA, this.handleFormatInitMetadata.bind(this) ],
+ [ UMPPartId.NEXT_REQUEST_POLICY, this.handleNextRequestPolicy.bind(this) ],
+ [ UMPPartId.SABR_ERROR, this.handleSabrError.bind(this) ],
+ [ UMPPartId.SABR_REDIRECT, this.handleSabrRedirect.bind(this) ],
+ [ UMPPartId.SABR_CONTEXT_UPDATE, this.handleSabrContextUpdate.bind(this) ],
+ [ UMPPartId.SABR_CONTEXT_SENDING_POLICY, this.handleSabrContextSendingPolicy.bind(this) ],
+ [ UMPPartId.SNACKBAR_MESSAGE, this.handleSnackbarMessage.bind(this) ],
+ [ UMPPartId.STREAM_PROTECTION_STATUS, this.handleStreamProtectionStatus.bind(this) ],
+ [ UMPPartId.RELOAD_PLAYER_RESPONSE, this.handleReloadPlayerResponse.bind(this) ],
+ [ UMPPartId.MEDIA_HEADER, this.handleMediaHeader.bind(this) ],
+ [ UMPPartId.MEDIA, this.handleMedia.bind(this) ],
+ [ UMPPartId.MEDIA_END, this.handleMediaEnd.bind(this) ]
+ ]);
+
+ constructor(
+ private requestMetadata: SabrRequestMetadata,
+ private cacheManager?: CacheManager
+ ) { }
+
+ /**
+ * Processes a chunk of data from a UMP stream and updates the request context.
+ * @returns A promise that resolves with a processing result if a terminal part is found (e.g., MediaEnd), or undefined otherwise.
+ * @param value
+ */
+ public processChunk(value: Uint8Array): Promise {
+ return new Promise((resolve) => {
+ let chunk;
+
+ if (this.partialPart) {
+ chunk = this.partialPart.data;
+ chunk.append(value);
+ } else {
+ chunk = new CompositeBuffer([ value ]);
+ }
+
+ const ump = new UmpReader(chunk);
+
+ this.partialPart = ump.read((part: Part) => {
+ const handler = this.umpPartHandlers.get(part.type);
+ const result = handler?.(part);
+ if (result) {
+ this.partialPart = undefined;
+ this.desiredHeaderId = undefined;
+ this.partialSegments.clear();
+ resolve(result);
+ }
+ });
+
+ resolve(undefined);
+ });
+ }
+
+ public getSegmentInfo() {
+ return this.partialSegments.get(this.desiredHeaderId || 0);
+ }
+
+ private decodePart(part: Part, decoder: { decode: (data: Uint8Array) => T }): T | undefined {
+ if (!part.data.chunks.length)
+ return undefined;
+
+ try {
+ return decoder.decode(concatenateChunks(part.data.chunks));
+ } catch {
+ return undefined;
+ }
+ }
+
+ private handleFormatInitMetadata(part: Part) {
+ const formatInitMetadata = this.decodePart(part, FormatInitializationMetadata);
+ if (formatInitMetadata) {
+ this.formatInitMetadata.push(formatInitMetadata);
+ }
+ return undefined;
+ }
+
+ private handleNextRequestPolicy(part: Part) {
+ const nextRequestPolicy = this.decodePart(part, NextRequestPolicy);
+ if (nextRequestPolicy) {
+ this.requestMetadata.streamInfo = {
+ ...this.requestMetadata.streamInfo,
+ nextRequestPolicy
+ };
+ }
+ return undefined;
+ }
+
+ private handleMediaHeader(part: Part) {
+ const mediaHeader = this.decodePart(part, MediaHeader);
+
+ if (!mediaHeader) {
+ return undefined;
+ }
+
+ const targetFormatKey = fromFormat(this.requestMetadata.format);
+ const segmentFormatKey = fromMediaHeader(mediaHeader);
+
+ if (!this.requestMetadata.isSABR || segmentFormatKey === targetFormatKey) {
+ const segmentObj = {
+ headerId: mediaHeader.headerId,
+ mediaHeader: mediaHeader,
+ bufferedChunks: [],
+ lastChunkSize: 0
+ };
+
+ if (this.desiredHeaderId === undefined) {
+ this.desiredHeaderId = mediaHeader.headerId;
+ }
+
+ this.partialSegments.set(mediaHeader.headerId, segmentObj);
+ }
+
+ return undefined;
+ }
+
+ private handleMedia(part: Part) {
+ const headerId = part.data.getUint8(0);
+ const buffer = part.data.split(1).remainingBuffer;
+
+ const segment = this.partialSegments.get(headerId);
+
+ if (segment) {
+ segment.lastChunkSize = buffer.getLength();
+ for (const chunk of buffer.chunks) {
+ segment.bufferedChunks.push(chunk);
+ }
+ }
+
+ return undefined;
+ }
+
+ private handleMediaEnd(part: Part): UmpProcessingResult | undefined {
+ const headerId = part.data.getUint8(0);
+ const segment = this.partialSegments.get(headerId);
+
+ if (segment && segment.headerId === this.desiredHeaderId) {
+ const segmentData = concatenateChunks(segment.bufferedChunks);
+
+ this.requestMetadata.streamInfo = {
+ ...this.requestMetadata.streamInfo,
+ formatInitMetadata: this.formatInitMetadata,
+ mediaHeader: segment.mediaHeader
+ };
+
+ /**
+ * Cache initialization segments to optimize performance. SABR responses contain larger payloads,
+ * and caching the init segment reduces latency when switching between different quality levels
+ * or initializing new streams.
+ */
+ if (this.cacheManager && this.requestMetadata.isInit && this.requestMetadata.byteRange && this.requestMetadata.format) {
+ this.cacheManager.setInitSegment(
+ createSegmentCacheKey(segment.mediaHeader, this.requestMetadata.format),
+ segmentData
+ );
+ return {
+ data: segmentData.slice(this.requestMetadata.byteRange.start, this.requestMetadata.byteRange.end + 1),
+ done: true
+ };
+ }
+
+ return {
+ data: segmentData,
+ done: true
+ };
+ }
+ }
+
+ private handleSnackbarMessage(part: Part) {
+ const snackbarMessage = this.decodePart(part, SnackbarMessage);
+ if (snackbarMessage) {
+ this.requestMetadata.streamInfo = {
+ ...this.requestMetadata.streamInfo,
+ snackbarMessage
+ };
+ }
+ return undefined;
+ }
+
+ private handleSabrError(part: Part): UmpProcessingResult {
+ const sabrError = this.decodePart(part, SabrError);
+ this.requestMetadata.error = { sabrError };
+ return { done: true };
+ }
+
+ private handleStreamProtectionStatus(part: Part): UmpProcessingResult | undefined {
+ const streamProtectionStatus = this.decodePart(part, StreamProtectionStatus);
+
+ if (!streamProtectionStatus) {
+ return undefined;
+ }
+
+ this.requestMetadata.streamInfo = {
+ ...this.requestMetadata.streamInfo,
+ streamProtectionStatus
+ };
+
+ if (streamProtectionStatus.status === 3) {
+ return {
+ done: true
+ };
+ }
+
+ return undefined;
+ }
+
+ private handleReloadPlayerResponse(part: Part): UmpProcessingResult | undefined {
+ const reloadPlaybackContext = this.decodePart(part, ReloadPlaybackContext);
+
+ if (!reloadPlaybackContext) {
+ return undefined;
+ }
+
+ this.requestMetadata.streamInfo = {
+ ...this.requestMetadata.streamInfo,
+ reloadPlaybackContext
+ };
+
+ return {
+ done: true
+ };
+ }
+
+ private handleSabrRedirect(part: Part): UmpProcessingResult | undefined {
+ const redirect = this.decodePart(part, SabrRedirect);
+
+ if (!redirect) {
+ return undefined;
+ }
+
+ this.requestMetadata.streamInfo = {
+ ...this.requestMetadata.streamInfo,
+ redirect
+ };
+
+ // With just UMP, redirects should be followed immediately.
+ if (this.requestMetadata.isUMP && !this.requestMetadata.isSABR) {
+ return { done: true };
+ }
+
+ return undefined;
+ }
+
+ private handleSabrContextUpdate(part: Part) {
+ const sabrContextUpdate = this.decodePart(part, SabrContextUpdate);
+ if (sabrContextUpdate) {
+ this.requestMetadata.streamInfo = {
+ ...this.requestMetadata.streamInfo,
+ sabrContextUpdate
+ };
+ }
+ return undefined;
+ }
+
+ private handleSabrContextSendingPolicy(part: Part): UmpProcessingResult | undefined {
+ const sabrContextSendingPolicy = this.decodePart(part, SabrContextSendingPolicy);
+ if (sabrContextSendingPolicy) {
+ this.requestMetadata.streamInfo = {
+ ...this.requestMetadata.streamInfo,
+ sabrContextSendingPolicy
+ };
+ }
+ return undefined;
+ }
+}
\ No newline at end of file
diff --git a/src/core/ServerAbrStream.ts b/src/core/ServerAbrStream.ts
deleted file mode 100644
index a6801e7..0000000
--- a/src/core/ServerAbrStream.ts
+++ /dev/null
@@ -1,321 +0,0 @@
-import { UMP } from './UMP.js';
-import { ChunkedDataBuffer } from './ChunkedDataBuffer.js';
-import { EventEmitterLike, PART, QUALITY, base64ToU8, getFormatKey } from '../utils/index.js';
-
-import { VideoPlaybackAbrRequest } from '../../protos/generated/video_streaming/video_playback_abr_request.js';
-import { MediaHeader } from '../../protos/generated/video_streaming/media_header.js';
-import { NextRequestPolicy } from '../../protos/generated/video_streaming/next_request_policy.js';
-import { FormatInitializationMetadata } from '../../protos/generated/video_streaming/format_initialization_metadata.js';
-import { SabrRedirect } from '../../protos/generated/video_streaming/sabr_redirect.js';
-import { SabrError } from '../../protos/generated/video_streaming/sabr_error.js';
-import { StreamProtectionStatus } from '../../protos/generated/video_streaming/stream_protection_status.js';
-import { PlaybackCookie } from '../../protos/generated/video_streaming/playback_cookie.js';
-
-import type { FormatId } from '../../protos/generated/misc/common.js';
-import type { ClientAbrState } from '../../protos/generated/video_streaming/client_abr_state.js';
-import type { FetchFunction, InitializedFormat, InitOptions, MediaArgs, ServerAbrResponse, ServerAbrStreamOptions } from '../utils/types.js';
-
-const DEFAULT_QUALITY = QUALITY.HD720;
-
-export class ServerAbrStream extends EventEmitterLike {
- private fetchFunction: FetchFunction;
- private serverAbrStreamingUrl: string;
- private videoPlaybackUstreamerConfig: string;
- private poToken?: string;
- private playbackCookie?: PlaybackCookie;
- private totalDurationMs: number;
- private initializedFormats: InitializedFormat[] = [];
- private formatsByKey: Map = new Map();
- private headerIdToFormatKeyMap: Map = new Map();
- private previousSequences: Map = new Map();
-
- constructor(args: ServerAbrStreamOptions) {
- super();
- this.fetchFunction = args.fetch || fetch;
- this.serverAbrStreamingUrl = args.serverAbrStreamingUrl;
- this.videoPlaybackUstreamerConfig = args.videoPlaybackUstreamerConfig;
- this.poToken = args.poToken;
- this.totalDurationMs = args.durationMs;
- }
-
- public on(event: 'end', listener: (streamData: ServerAbrResponse) => void): void;
- public on(event: 'data', listener: (streamData: ServerAbrResponse) => void): void;
- public on(event: 'error', listener: (error: Error) => void): void;
- public on(event: string, listener: (...data: any[]) => void): void {
- super.on(event, listener);
- }
-
- public once(event: 'end', listener: (streamData: ServerAbrResponse) => void): void;
- public once(event: 'data', listener: (streamData: ServerAbrResponse) => void): void;
- public once(event: 'error', listener: (error: Error) => void): void;
- public once(event: string, listener: (...args: any[]) => void): void {
- super.once(event, listener);
- }
-
- /**
- * Initializes the server ABR stream with the provided options.
- * @param args - The initialization options.
- */
- public async init(args: InitOptions) {
- const { audioFormats, videoFormats, clientAbrState: initialState } = args;
-
- const firstVideoFormat = videoFormats ? videoFormats[0] : undefined;
-
- const clientAbrState: ClientAbrState = {
- lastManualDirection: 0,
- timeSinceLastManualFormatSelectionMs: 0,
- lastManualSelectedResolution: videoFormats.length === 1 ? firstVideoFormat?.height : DEFAULT_QUALITY,
- stickyResolution: videoFormats.length === 1 ? firstVideoFormat?.height : DEFAULT_QUALITY,
- playerTimeMs: 0,
- visibility: 0,
- enabledTrackTypesBitfield: 0,
- ...initialState
- };
-
- const audioFormatIds = audioFormats.map((fmt) => ({
- itag: fmt.itag,
- lastModified: parseInt(fmt.lastModified),
- xtags: fmt.xtags
- }));
-
- const videoFormatIds = videoFormats.map((fmt) => ({
- itag: fmt.itag,
- lastModified: parseInt(fmt.lastModified),
- xtags: fmt.xtags
- }));
-
- if (typeof clientAbrState.playerTimeMs !== 'number')
- throw new Error('Invalid media start time');
-
- try {
- while (clientAbrState.playerTimeMs < this.totalDurationMs) {
- const data = await this.fetchMedia({ clientAbrState, audioFormatIds, videoFormatIds });
-
- this.emit('data', data);
-
- if (data.sabrError) break;
-
- const mainFormat =
- clientAbrState.enabledTrackTypesBitfield === 0
- ? data.initializedFormats.find((fmt) => fmt.mimeType?.includes('video'))
- : data.initializedFormats[0];
-
- for (const fmt of data.initializedFormats) {
- this.previousSequences.set(fmt.formatKey, fmt.sequenceList.map((seq) => seq.sequenceNumber || 0));
- }
-
- if (
- !mainFormat ||
- mainFormat.sequenceCount ===
- mainFormat.sequenceList[mainFormat.sequenceList.length - 1]?.sequenceNumber
- ) {
- this.emit('end', data);
- break;
- }
-
- clientAbrState.playerTimeMs += mainFormat.sequenceList.reduce((acc, seq) => acc + (seq.durationMs || 0), 0);
- }
- } catch (error) {
- this.emit('error', error);
- clientAbrState.playerTimeMs = Infinity;
- }
- }
-
- private async fetchMedia(args: MediaArgs): Promise {
- const { clientAbrState, audioFormatIds, videoFormatIds } = args;
-
- const body = VideoPlaybackAbrRequest.encode({
- clientAbrState: clientAbrState,
- selectedAudioFormatIds: audioFormatIds,
- selectedVideoFormatIds: videoFormatIds,
- selectedFormatIds: this.initializedFormats.map((fmt) => fmt.formatId),
- videoPlaybackUstreamerConfig: base64ToU8(this.videoPlaybackUstreamerConfig),
- streamerContext: {
- field5: [],
- field6: [],
- poToken: this.poToken ? base64ToU8(this.poToken) : undefined,
- playbackCookie: this.playbackCookie ? PlaybackCookie.encode(this.playbackCookie).finish() : undefined,
- clientInfo: {
- clientName: 1,
- clientVersion: '2.2040620.05.00',
- osName: 'Windows',
- osVersion: '10.0'
- }
- },
- bufferedRanges: this.initializedFormats.map((fmt) => fmt._state),
- field1000: []
- }).finish();
-
- const response = await this.fetchFunction(this.serverAbrStreamingUrl, { method: 'POST', body });
- const data = await response.arrayBuffer();
-
- if (response.status !== 200 || !data.byteLength)
- throw new Error(`Received an invalid response from the server: ${response.status}`);
-
- return this.parseUMPResponse(new Uint8Array(data));
- }
-
- /**
- * Parses the UMP response data and updates the initialized formats.
- * @param response - The UMP response data as a byte array.
- */
- public async parseUMPResponse(response: Uint8Array): Promise {
- this.headerIdToFormatKeyMap.clear();
-
- this.initializedFormats.forEach((format) => {
- format.sequenceList = [];
- format.mediaChunks = [];
- });
-
- let sabrError: SabrError | undefined;
- let sabrRedirect: SabrRedirect | undefined;
- let streamProtectionStatus: StreamProtectionStatus | undefined;
-
- const ump = new UMP(new ChunkedDataBuffer([ response ]));
-
- ump.parse((part) => {
- const data = part.data.chunks[0];
- switch (part.type) {
- case PART.MEDIA_HEADER:
- this.processMediaHeader(data);
- break;
- case PART.MEDIA:
- this.processMediaData(part.data);
- break;
- case PART.MEDIA_END:
- this.processEndOfMedia(part.data);
- break;
- case PART.NEXT_REQUEST_POLICY:
- this.processNextRequestPolicy(data);
- break;
- case PART.FORMAT_INITIALIZATION_METADATA:
- this.processFormatInitialization(data);
- break;
- case PART.SABR_ERROR:
- sabrError = SabrError.decode(data);
- break;
- case PART.SABR_REDIRECT:
- sabrRedirect = this.processSabrRedirect(data);
- break;
- case PART.STREAM_PROTECTION_STATUS:
- streamProtectionStatus = StreamProtectionStatus.decode(data);
- break;
- default:
- break;
- }
- });
-
- return {
- initializedFormats: this.initializedFormats,
- streamProtectionStatus,
- sabrRedirect,
- sabrError
- };
- }
-
- private processMediaHeader(data: Uint8Array) {
- const mediaHeader = MediaHeader.decode(data);
- if (!mediaHeader.formatId) return;
-
- const formatKey = getFormatKey(mediaHeader.formatId);
-
- const currentFormat = this.formatsByKey.get(formatKey) || this.registerFormat(mediaHeader);
- if (!currentFormat) return;
-
- // FIXME: This is a hacky workaround to prevent duplicate sequences from being added. This should be fixed in the future (preferably by figuring out how to make the server not send duplicates).
- if (mediaHeader.sequenceNumber !== undefined && this.previousSequences.get(formatKey)?.includes(mediaHeader.sequenceNumber))
- return;
-
- // Save the header's ID so we can identify its stream data later.
- if (mediaHeader.headerId !== undefined) {
- if (!this.headerIdToFormatKeyMap.has(mediaHeader.headerId)) {
- this.headerIdToFormatKeyMap.set(mediaHeader.headerId, formatKey);
- }
- }
-
- if (!currentFormat.sequenceList.some((seq) => seq.sequenceNumber === (mediaHeader.sequenceNumber || 0))) {
- currentFormat.sequenceList.push({
- itag: mediaHeader.itag,
- formatId: mediaHeader.formatId,
- isInitSegment: mediaHeader.isInitSeg,
- durationMs: mediaHeader.durationMs,
- startMs: mediaHeader.startMs,
- startDataRange: mediaHeader.startRange,
- sequenceNumber: mediaHeader.sequenceNumber,
- contentLength: mediaHeader.contentLength,
- timeRange: mediaHeader.timeRange
- });
-
- if (typeof mediaHeader.sequenceNumber === 'number') {
- currentFormat._state.durationMs += mediaHeader.durationMs || 0;
- currentFormat._state.endSegmentIndex += 1;
- }
- }
- }
-
- private processMediaData(data: ChunkedDataBuffer) {
- const headerId = data.getUint8(0);
- const streamData = data.split(1).remainingBuffer;
-
- const formatKey = this.headerIdToFormatKeyMap.get(headerId);
- if (!formatKey) return;
-
- const currentFormat = this.formatsByKey.get(formatKey);
- if (!currentFormat) return;
-
- currentFormat.mediaChunks.push(streamData.chunks[0]);
- }
-
- private processEndOfMedia(data: ChunkedDataBuffer) {
- const headerId = data.getUint8(0);
- this.headerIdToFormatKeyMap.delete(headerId);
- }
-
- private processNextRequestPolicy(data: Uint8Array) {
- const nextRequestPolicy = NextRequestPolicy.decode(data);
- this.playbackCookie = nextRequestPolicy.playbackCookie;
- }
-
- private processFormatInitialization(data: Uint8Array) {
- const formatInitializationMetadata = FormatInitializationMetadata.decode(data);
- this.registerFormat(formatInitializationMetadata);
- }
-
- private processSabrRedirect(data: Uint8Array): SabrRedirect {
- const sabrRedirect = SabrRedirect.decode(data);
- if (!sabrRedirect.url) throw new Error('Invalid SABR redirect');
- this.serverAbrStreamingUrl = sabrRedirect.url;
- return sabrRedirect;
- }
-
- private registerFormat(data: MediaHeader | FormatInitializationMetadata): InitializedFormat | undefined {
- if (!data.formatId)
- return;
-
- const formatKey = getFormatKey(data.formatId);
-
- if (!this.formatsByKey.has(formatKey)) {
- const format: InitializedFormat = {
- formatId: data.formatId,
- formatKey: formatKey,
- durationMs: data.durationMs,
- mimeType: 'mimeType' in data ? data.mimeType : undefined,
- sequenceCount: 'endSegmentNumber' in data ? data.endSegmentNumber : undefined,
- sequenceList: [],
- mediaChunks: [],
- _state: {
- formatId: data.formatId,
- startTimeMs: 0,
- durationMs: 0,
- startSegmentIndex: 1,
- endSegmentIndex: 0
- }
- };
-
- this.initializedFormats.push(format);
- this.formatsByKey.set(formatKey, this.initializedFormats[this.initializedFormats.length - 1]);
-
- return format;
- }
- }
-}
\ No newline at end of file
diff --git a/src/core/UMP.ts b/src/core/UmpReader.ts
similarity index 50%
rename from src/core/UMP.ts
rename to src/core/UmpReader.ts
index cd687c8..18ee8a5 100644
--- a/src/core/UMP.ts
+++ b/src/core/UmpReader.ts
@@ -1,23 +1,15 @@
-import type { Part } from '../index.js';
-import type { ChunkedDataBuffer } from './ChunkedDataBuffer.js';
+import type { CompositeBuffer } from './CompositeBuffer.js';
+import type { Part } from '../types/shared.js';
-export class UMP {
- private chunkedDataBuffer: ChunkedDataBuffer;
-
- /**
- * Creates a new UMP parser.
- * @param chunkedDataBuffer - Buffer containing UMP format data.
- */
- constructor(chunkedDataBuffer: ChunkedDataBuffer) {
- this.chunkedDataBuffer = chunkedDataBuffer;
- }
+export class UmpReader {
+ constructor(private compositeBuffer: CompositeBuffer) { }
/**
* Parses parts from the buffer and calls the handler for each complete part.
* @param handlePart - Function called with each complete part.
* @returns Partial part if parsing is incomplete, undefined otherwise.
*/
- public parse(handlePart: (part: Part) => void): Part | undefined {
+ public read(handlePart: (part: Part) => void): Part | undefined {
while (true) {
let offset = 0;
@@ -30,18 +22,18 @@ export class UMP {
if (partType < 0 || partSize < 0)
break;
- if (!this.chunkedDataBuffer.canReadBytes(offset, partSize)) {
- if (!this.chunkedDataBuffer.canReadBytes(offset, 1))
+ if (!this.compositeBuffer.canReadBytes(offset, partSize)) {
+ if (!this.compositeBuffer.canReadBytes(offset, 1))
break;
return {
type: partType,
size: partSize,
- data: this.chunkedDataBuffer
+ data: this.compositeBuffer
};
}
- const splitResult = this.chunkedDataBuffer.split(offset).remainingBuffer.split(partSize);
+ const splitResult = this.compositeBuffer.split(offset).remainingBuffer.split(partSize);
offset = 0;
handlePart({
@@ -50,7 +42,7 @@ export class UMP {
data: splitResult.extractedBuffer
});
- this.chunkedDataBuffer = splitResult.remainingBuffer;
+ this.compositeBuffer = splitResult.remainingBuffer;
}
}
@@ -63,14 +55,14 @@ export class UMP {
let byteLength: number;
// Determine the length of the val
- if (this.chunkedDataBuffer.canReadBytes(offset, 1)) {
- const firstByte = this.chunkedDataBuffer.getUint8(offset);
+ if (this.compositeBuffer.canReadBytes(offset, 1)) {
+ const firstByte = this.compositeBuffer.getUint8(offset);
byteLength = firstByte < 128 ? 1 : firstByte < 192 ? 2 : firstByte < 224 ? 3 : firstByte < 240 ? 4 : 5;
} else {
byteLength = 0;
}
- if (byteLength < 1 || !this.chunkedDataBuffer.canReadBytes(offset, byteLength)) {
+ if (byteLength < 1 || !this.compositeBuffer.canReadBytes(offset, byteLength)) {
return [ -1, offset ];
}
@@ -79,40 +71,40 @@ export class UMP {
// Now read it based on the length
switch (byteLength) {
case 1:
- value = this.chunkedDataBuffer.getUint8(offset++);
+ value = this.compositeBuffer.getUint8(offset++);
break;
case 2: {
- const byte1 = this.chunkedDataBuffer.getUint8(offset++);
- const byte2 = this.chunkedDataBuffer.getUint8(offset++);
+ const byte1 = this.compositeBuffer.getUint8(offset++);
+ const byte2 = this.compositeBuffer.getUint8(offset++);
value = (byte1 & 0x3f) + 64 * byte2;
break;
}
case 3: {
- const byte1 = this.chunkedDataBuffer.getUint8(offset++);
- const byte2 = this.chunkedDataBuffer.getUint8(offset++);
- const byte3 = this.chunkedDataBuffer.getUint8(offset++);
+ const byte1 = this.compositeBuffer.getUint8(offset++);
+ const byte2 = this.compositeBuffer.getUint8(offset++);
+ const byte3 = this.compositeBuffer.getUint8(offset++);
value = (byte1 & 0x1f) + 32 * (byte2 + 256 * byte3);
break;
}
case 4: {
- const byte1 = this.chunkedDataBuffer.getUint8(offset++);
- const byte2 = this.chunkedDataBuffer.getUint8(offset++);
- const byte3 = this.chunkedDataBuffer.getUint8(offset++);
- const byte4 = this.chunkedDataBuffer.getUint8(offset++);
+ const byte1 = this.compositeBuffer.getUint8(offset++);
+ const byte2 = this.compositeBuffer.getUint8(offset++);
+ const byte3 = this.compositeBuffer.getUint8(offset++);
+ const byte4 = this.compositeBuffer.getUint8(offset++);
value = (byte1 & 0x0f) + 16 * (byte2 + 256 * (byte3 + 256 * byte4));
break;
}
default: {
const tempOffset = offset + 1;
- this.chunkedDataBuffer.focus(tempOffset);
+ this.compositeBuffer.focus(tempOffset);
if (this.canReadFromCurrentChunk(tempOffset, 4)) {
- value = this.getCurrentDataView().getUint32(tempOffset - this.chunkedDataBuffer.currentChunkOffset, true);
+ value = this.getCurrentDataView().getUint32(tempOffset - this.compositeBuffer.currentChunkOffset, true);
} else {
- const byte3 = this.chunkedDataBuffer.getUint8(tempOffset + 2) + 256 * this.chunkedDataBuffer.getUint8(tempOffset + 3);
+ const byte3 = this.compositeBuffer.getUint8(tempOffset + 2) + 256 * this.compositeBuffer.getUint8(tempOffset + 3);
value =
- this.chunkedDataBuffer.getUint8(tempOffset) +
- 256 * (this.chunkedDataBuffer.getUint8(tempOffset + 1) + 256 * byte3);
+ this.compositeBuffer.getUint8(tempOffset) +
+ 256 * (this.compositeBuffer.getUint8(tempOffset + 1) + 256 * byte3);
}
offset += 5;
break;
@@ -129,7 +121,7 @@ export class UMP {
* @returns True if bytes can be read from current chunk, false otherwise.
*/
public canReadFromCurrentChunk(offset: number, length: number): boolean {
- return offset - this.chunkedDataBuffer.currentChunkOffset + length <= this.chunkedDataBuffer.chunks[this.chunkedDataBuffer.currentChunkIndex].length;
+ return offset - this.compositeBuffer.currentChunkOffset + length <= this.compositeBuffer.chunks[this.compositeBuffer.currentChunkIndex].length;
}
/**
@@ -137,10 +129,10 @@ export class UMP {
* @returns DataView for the current chunk.
*/
public getCurrentDataView(): DataView {
- if (!this.chunkedDataBuffer.currentDataView) {
- const currentChunk = this.chunkedDataBuffer.chunks[this.chunkedDataBuffer.currentChunkIndex];
- this.chunkedDataBuffer.currentDataView = new DataView(currentChunk.buffer, currentChunk.byteOffset, currentChunk.length);
+ if (!this.compositeBuffer.currentDataView) {
+ const currentChunk = this.compositeBuffer.chunks[this.compositeBuffer.currentChunkIndex];
+ this.compositeBuffer.currentDataView = new DataView(currentChunk.buffer, currentChunk.byteOffset, currentChunk.length);
}
- return this.chunkedDataBuffer.currentDataView;
+ return this.compositeBuffer.currentDataView;
}
}
\ No newline at end of file
diff --git a/src/core/UmpWriter.ts b/src/core/UmpWriter.ts
new file mode 100644
index 0000000..be9df94
--- /dev/null
+++ b/src/core/UmpWriter.ts
@@ -0,0 +1,56 @@
+import type { CompositeBuffer } from './CompositeBuffer.js';
+
+export class UmpWriter {
+ constructor(
+ private compositeBuffer: CompositeBuffer
+ ) { }
+
+ /**
+ * Writes a part to the buffer.
+ * @param partType - The type of the part.
+ * @param partData - The data of the part.
+ */
+ public write(partType: number, partData: Uint8Array): void {
+ const partSize = partData.length;
+ this.writeVarInt(partType);
+ this.writeVarInt(partSize);
+ this.compositeBuffer.append(partData);
+ }
+
+ /**
+ * Writes a variable-length integer to the buffer.
+ * @param value - The integer to write.
+ */
+ private writeVarInt(value: number): void {
+ if (value < 0)
+ throw new Error('VarInt value cannot be negative.');
+
+ if (value < 128) {
+ this.compositeBuffer.append(new Uint8Array([ value ]));
+ } else if (value < 16384) {
+ this.compositeBuffer.append(new Uint8Array([
+ (value & 0x3F) | 0x80,
+ value >> 6
+ ]));
+ } else if (value < 2097152) {
+ this.compositeBuffer.append(new Uint8Array([
+ (value & 0x1F) | 0xC0,
+ (value >> 5) & 0xFF,
+ value >> 13
+ ]));
+ } else if (value < 268435456) {
+ this.compositeBuffer.append(new Uint8Array([
+ (value & 0x0F) | 0xE0,
+ (value >> 4) & 0xFF,
+ (value >> 12) & 0xFF,
+ value >> 20
+ ]));
+ } else {
+ const data = new Uint8Array(5);
+ const view = new DataView(data.buffer);
+ data[0] = 0xF0;
+ view.setUint32(1, value, true);
+ this.compositeBuffer.append(data);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/core/index.ts b/src/core/index.ts
deleted file mode 100644
index 8a785f8..0000000
--- a/src/core/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export * from './ChunkedDataBuffer.js';
-export * from './UMP.js';
-export * from './ServerAbrStream.js';
\ No newline at end of file
diff --git a/src/exports/protos.ts b/src/exports/protos.ts
new file mode 100644
index 0000000..5bcbc5d
--- /dev/null
+++ b/src/exports/protos.ts
@@ -0,0 +1 @@
+export * from '../utils/Protos.js';
\ No newline at end of file
diff --git a/src/exports/sabr-stream.ts b/src/exports/sabr-stream.ts
new file mode 100644
index 0000000..9bb363a
--- /dev/null
+++ b/src/exports/sabr-stream.ts
@@ -0,0 +1,2 @@
+export * from '../core/SabrStream.js';
+export type * from '../types/sabrStreamTypes.js';
\ No newline at end of file
diff --git a/src/exports/sabr-streaming-adapter.ts b/src/exports/sabr-streaming-adapter.ts
new file mode 100644
index 0000000..7667199
--- /dev/null
+++ b/src/exports/sabr-streaming-adapter.ts
@@ -0,0 +1,3 @@
+export * from '../core/SabrStreamingAdapter.js';
+export * from '../core/SabrUmpProcessor.js';
+export type * from '../types/sabrStreamingAdapterTypes.js';
diff --git a/src/exports/ump.ts b/src/exports/ump.ts
new file mode 100644
index 0000000..d0738dc
--- /dev/null
+++ b/src/exports/ump.ts
@@ -0,0 +1,3 @@
+export * from '../core/UmpReader.js';
+export * from '../core/UmpWriter.js';
+export * from '../core/CompositeBuffer.js';
\ No newline at end of file
diff --git a/src/exports/utils.ts b/src/exports/utils.ts
new file mode 100644
index 0000000..99c0694
--- /dev/null
+++ b/src/exports/utils.ts
@@ -0,0 +1 @@
+export * from '../utils/index.js';
\ No newline at end of file
diff --git a/src/index.ts b/src/index.ts
deleted file mode 100644
index d24a65b..0000000
--- a/src/index.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import * as GoogleVideo from './core/index.js';
-export { GoogleVideo };
-export default GoogleVideo;
-export * from './utils/index.js';
\ No newline at end of file
diff --git a/src/types/sabrStreamTypes.ts b/src/types/sabrStreamTypes.ts
new file mode 100644
index 0000000..796980e
--- /dev/null
+++ b/src/types/sabrStreamTypes.ts
@@ -0,0 +1,123 @@
+import type { SabrStreamState } from '../core/SabrStream.js';
+import type { FetchFunction, SabrFormat } from './shared.js';
+import type { EnabledTrackTypes } from '../utils/index.js';
+import type { ClientInfo } from '../utils/Protos.js';
+
+export interface SabrStreamConfig {
+ /**
+ * Custom fetch implementation to use for HTTP requests.
+ * If not provided, the global `fetch` function will be used.
+ */
+ fetch?: FetchFunction;
+
+ /**
+ * The URL endpoint for server-side ABR streaming requests.
+ * This is typically obtained from the initial player response.
+ */
+ serverAbrStreamingUrl?: string;
+
+ /**
+ * Base64-encoded Ustreamer configuration obtained from the player response.
+ * Required for authorizing and configuring the streaming session.
+ */
+ videoPlaybackUstreamerConfig?: string;
+
+ /**
+ * Client information used to identify the requesting device/app.
+ * Contains details like client name, version, and capabilities.
+ */
+ clientInfo?: ClientInfo;
+
+ /**
+ * Proof of Origin token for content protection verification.
+ */
+ poToken?: string;
+
+ /**
+ * Total duration of the media content in milliseconds.
+ * If not provided, will be determined from format metadata.
+ */
+ durationMs?: number;
+
+ /**
+ * Array of available streaming formats obtained from the player response.
+ */
+ formats?: SabrFormat[];
+}
+
+export interface SabrPlaybackOptions {
+ /**
+ * Video format selection, can be a format ID number, a SabrFormat object,
+ * or a function that selects a format from the available formats array.
+ */
+ videoFormat?: number | SabrFormat | ((formats: SabrFormat[]) => SabrFormat | undefined);
+
+ /**
+ * Audio format selection, can be a format ID number, a SabrFormat object,
+ * or a function that selects a format from the available formats array.
+ */
+ audioFormat?: number | SabrFormat | ((formats: SabrFormat[]) => SabrFormat | undefined);
+
+ /**
+ * Preferred video quality (e.g., "1080p", "720p").
+ */
+ videoQuality?: string;
+
+ /**
+ * Preferred audio quality (e.g., "high", "medium").
+ */
+ audioQuality?: string;
+
+ /**
+ * Preferred video language code.
+ */
+ videoLanguage?: string;
+
+ /**
+ * Preferred audio language code.
+ */
+ audioLanguage?: string;
+
+ /**
+ * Whether to prefer WebM container format.
+ */
+ preferWebM?: boolean;
+
+ /**
+ * Whether to prefer MP4 container format.
+ */
+ preferMP4?: boolean;
+
+ /**
+ * Whether to prefer H.264 video codec.
+ */
+ preferH264?: boolean;
+
+ /**
+ * Whether to prefer Opus audio codec.
+ */
+ preferOpus?: boolean;
+
+ /**
+ * Maximum number of retry attempts when fetching segments.
+ * Default is 10.
+ */
+ maxRetries?: number;
+
+ /**
+ * Duration in milliseconds after which a stall is detected if no progress is made.
+ * Default is 30000 (30 seconds).
+ */
+ stallDetectionMs?: number;
+
+ /**
+ * Enabled track types for streaming (audio only, video only, or both).
+ * @see EnabledTrackTypes
+ */
+ enabledTrackTypes?: EnabledTrackTypes;
+
+ /**
+ * Previously saved state to resume a download.
+ */
+ state?: SabrStreamState;
+}
diff --git a/src/types/sabrStreamingAdapterTypes.ts b/src/types/sabrStreamingAdapterTypes.ts
new file mode 100644
index 0000000..91bebcc
--- /dev/null
+++ b/src/types/sabrStreamingAdapterTypes.ts
@@ -0,0 +1,115 @@
+import type {
+ ClientInfo,
+ FormatInitializationMetadata,
+ MediaHeader,
+ NextRequestPolicy,
+ PlaybackCookie,
+ ReloadPlaybackContext,
+ SabrContextSendingPolicy,
+ SabrContextUpdate,
+ SabrError,
+ SabrRedirect,
+ SnackbarMessage,
+ StreamProtectionStatus
+} from '../utils/Protos.js';
+
+import type { SabrFormat } from './shared.js';
+import type { CacheManager, RequestMetadataManager } from '../utils/index.js';
+
+export interface SabrRequestMetadata {
+ byteRange?: { start: number; end: number };
+ format?: SabrFormat;
+ isInit?: boolean;
+ isUMP?: boolean;
+ isSABR?: boolean;
+ streamInfo?: {
+ playbackCookie?: PlaybackCookie;
+ nextRequestPolicy?: NextRequestPolicy;
+ formatInitMetadata?: FormatInitializationMetadata[];
+ streamProtectionStatus?: StreamProtectionStatus;
+ reloadPlaybackContext?: ReloadPlaybackContext;
+ sabrContextSendingPolicy?: SabrContextSendingPolicy;
+ sabrContextUpdate?: SabrContextUpdate;
+ snackbarMessage?: SnackbarMessage;
+ mediaHeader?: MediaHeader;
+ redirect?: SabrRedirect;
+ };
+ error?: {
+ sabrError?: SabrError;
+ };
+ timestamp: number;
+}
+
+export interface SabrOptions {
+ /**
+ * Whether to enable caching of SABR segments.
+ * @default true
+ */
+ enableCaching?: boolean;
+ /**
+ * Enables verbose logging of all SABR requests made by the player.
+ * @NOTE: `DEBUG` level logging must be enabled for this to take effect.
+ * @default false
+ */
+ enableVerboseRequestLogging?: boolean;
+ /**
+ * Maximum size of the segment cache in megabytes.
+ * @default 3
+ */
+ maxCacheSizeMB?: number;
+ /**
+ * Maximum age of cached segments in seconds.
+ * @default 300 (5 minutes)
+ */
+ maxCacheAgeSeconds?: number;
+ /**
+ * Player adapter to use for SABR streaming.
+ */
+ playerAdapter?: SabrPlayerAdapter;
+ /**
+ * Client information to send with SABR requests.
+ */
+ clientInfo?: ClientInfo;
+}
+
+export interface PlayerHttpResponse {
+ url: string;
+ method: string;
+ headers: Record;
+ data?: ArrayBuffer | ArrayBufferView;
+ makeRequest: (url: string, headers: Record) => Promise>;
+}
+
+export interface PlayerHttpRequest {
+ url: string;
+ method: string;
+ headers: Record;
+ segment: RequestSegment;
+ body?: ArrayBuffer | ArrayBufferView | null;
+}
+
+export interface RequestSegment {
+ getStartTime: () => number | null;
+ isInit: () => boolean;
+}
+
+export type RequestFilter = (request: PlayerHttpRequest) => Promise | PlayerHttpRequest | undefined;
+export type ResponseFilter = (response: PlayerHttpResponse) => Promise | PlayerHttpResponse | undefined;
+
+export interface SabrPlayerAdapter {
+ initialize(
+ player: any,
+ requestMetadataManager: RequestMetadataManager,
+ cache: CacheManager | null
+ ): void;
+ getPlayerTime(): number;
+ getPlaybackRate(): number;
+ getBandwidthEstimate(): number;
+ getActiveTrackFormats(activeFormat: SabrFormat, sabrFormats: SabrFormat[]): {
+ audioFormat?: SabrFormat;
+ videoFormat?: SabrFormat;
+ };
+ registerRequestInterceptor(interceptor: RequestFilter): void;
+ registerResponseInterceptor(interceptor: ResponseFilter): void;
+ dispose(): void;
+}
\ No newline at end of file
diff --git a/src/types/shared.ts b/src/types/shared.ts
new file mode 100644
index 0000000..dde6556
--- /dev/null
+++ b/src/types/shared.ts
@@ -0,0 +1,66 @@
+import type { CompositeBuffer } from '../core/CompositeBuffer.js';
+
+export type Part = {
+ type: number;
+ size: number;
+ data: CompositeBuffer;
+};
+
+export interface SabrFormat {
+ itag: number;
+ lastModified: number;
+ xtags?: string;
+ width?: number;
+ height?: number;
+ contentLength?: number;
+ audioTrackId?: string;
+ mimeType?: string;
+ isDrc?: boolean;
+ quality?: string;
+ qualityLabel?: string;
+ averageBitrate?: number;
+ bitrate: number;
+ audioQuality?: string;
+ approxDurationMs: number;
+ language?: string | null;
+ isDubbed?: boolean;
+ isAutoDubbed?: boolean;
+ isDescriptive?: boolean;
+ isSecondary?: boolean;
+ isOriginal?: boolean;
+}
+
+export interface FormatStream {
+ itag: number;
+ last_modified_ms?: string;
+ lastModified?: string;
+ xtags?: string;
+ width?: number;
+ height?: number;
+ mime_type?: string;
+ mimeType?: string;
+ audio_quality?: string;
+ audioQuality?: string;
+ bitrate: number;
+ average_bitrate?: number;
+ averageBitrate?: number;
+ quality?: string;
+ quality_label?: string;
+ qualityLabel?: string;
+ audio_track?: { id: string };
+ audioTrackId?: string;
+ is_drc?: boolean;
+ isDrc?: boolean;
+ approx_duration_ms?: number;
+ approxDurationMs?: string;
+ content_length?: number;
+ contentLength?: string;
+ is_auto_dubbed?: boolean;
+ is_descriptive?: boolean;
+ is_dubbed?: boolean;
+ language?: string | null;
+ is_original?: boolean;
+ is_secondary?: boolean;
+}
+
+export type FetchFunction = typeof fetch;
\ No newline at end of file
diff --git a/src/utils/CacheManager.ts b/src/utils/CacheManager.ts
new file mode 100644
index 0000000..6921b57
--- /dev/null
+++ b/src/utils/CacheManager.ts
@@ -0,0 +1,168 @@
+import { Logger } from './Logger.js';
+
+export interface CacheEntry {
+ data: Uint8Array;
+ timestamp: number;
+ size: number;
+}
+
+const TAG = 'CacheManager';
+
+/**
+ * A "proper" cache for storing segments.
+ */
+export class CacheManager {
+ private initSegmentCache = new Map();
+ private segmentCache = new Map();
+ private currentSize = 0;
+ private timerId: any;
+ private readonly maxCacheSize: number;
+ private readonly maxAge: number;
+ private readonly logger = Logger.getInstance();
+
+ constructor(maxSizeMB = 50, maxAgeSeconds = 600) {
+ this.maxCacheSize = maxSizeMB * 1024 * 1024;
+ this.maxAge = maxAgeSeconds * 1000;
+ this.startGarbageCollection();
+ }
+
+ public getCacheEntries() {
+ return {
+ initSegmentCache: this.initSegmentCache,
+ segmentCache: this.segmentCache
+ };
+ }
+
+ public setInitSegment(key: string, data: Uint8Array): void {
+ const entry: CacheEntry = {
+ data,
+ timestamp: Date.now(),
+ size: data.byteLength
+ };
+
+ if (!this.initSegmentCache.has(key)) {
+ this.currentSize += entry.size;
+ this.enforceStorageLimit();
+ }
+
+ this.initSegmentCache.set(key, entry);
+ }
+
+ public setSegment(key: string, data: Uint8Array): void {
+ const entry: CacheEntry = {
+ data,
+ timestamp: Date.now(),
+ size: data.byteLength
+ };
+
+ this.currentSize += entry.size;
+ this.enforceStorageLimit();
+ this.segmentCache.set(key, entry);
+ }
+
+ public getInitSegment(key: string): Uint8Array | undefined {
+ const entry = this.initSegmentCache.get(key);
+
+ if (entry && !this.isExpired(entry)) {
+ this.logger.debug(TAG, `Cache hit for init segment: ${key}`);
+ entry.timestamp = Date.now();
+ return entry.data;
+ }
+
+ // Expired. Get rid of it.
+ if (entry) {
+ this.initSegmentCache.delete(key);
+ this.currentSize -= entry.size;
+ }
+
+ return undefined;
+ }
+
+ public getSegment(key: string): Uint8Array | undefined {
+ const entry = this.segmentCache.get(key);
+ if (entry && !this.isExpired(entry)) {
+ this.logger.debug(TAG, `Cache hit for segment: ${key}`);
+ const data = entry.data;
+ this.segmentCache.delete(key);
+ this.currentSize -= entry.size;
+ return data;
+ }
+
+ // Expired. Get rid of it.
+ if (entry) {
+ this.segmentCache.delete(key);
+ this.currentSize -= entry.size;
+ }
+
+ return undefined;
+ }
+
+ private isExpired(entry: CacheEntry): boolean {
+ return Date.now() - entry.timestamp > this.maxAge;
+ }
+
+ private enforceStorageLimit(): void {
+ if (this.currentSize <= this.maxCacheSize) return;
+
+ this.clearExpiredEntries();
+
+ // If still over limit, remove oldest entries.
+ if (this.currentSize > this.maxCacheSize) {
+ this.removeOldestEntries();
+ }
+ }
+
+ private clearExpiredEntries(): void {
+ const now = Date.now();
+
+ for (const [ key, entry ] of this.segmentCache.entries()) {
+ if (now - entry.timestamp > this.maxAge) {
+ this.logger.debug(TAG, `Removing expired segment from cache: ${key}`);
+ this.segmentCache.delete(key);
+ this.currentSize -= entry.size;
+ }
+ }
+
+ for (const [ key, entry ] of this.initSegmentCache.entries()) {
+ if (now - entry.timestamp > this.maxAge) {
+ this.logger.debug(TAG, `Removing expired init segment from cache: ${key}`);
+ this.initSegmentCache.delete(key);
+ this.currentSize -= entry.size;
+ }
+ }
+ }
+
+ private removeOldestEntries(): void {
+ const segments = Array.from(this.segmentCache.entries());
+ const initSegments = Array.from(this.initSegmentCache.entries());
+
+ const allEntries = [ ...segments, ...initSegments ]
+ .sort((a, b) => a[1].timestamp - b[1].timestamp);
+
+ while (this.currentSize > this.maxCacheSize && allEntries.length > 0) {
+ const [ key, entry ] = allEntries.shift()!;
+ this.segmentCache.delete(key);
+ this.initSegmentCache.delete(key);
+ this.currentSize -= entry.size;
+ }
+ }
+
+ private startGarbageCollection(): void {
+ this.timerId = setInterval(() => {
+ this.clearExpiredEntries();
+ }, 60000);
+ }
+
+ public dispose(): void {
+ this.initSegmentCache.clear();
+ this.segmentCache.clear();
+ this.currentSize = 0;
+
+ if (this.timerId) {
+ clearInterval(this.timerId);
+ this.timerId = undefined;
+ }
+
+ this.logger.debug(TAG, 'Disposed');
+ }
+}
\ No newline at end of file
diff --git a/src/utils/EventEmitterLike.ts b/src/utils/EventEmitterLike.ts
index ddb5363..e853286 100644
--- a/src/utils/EventEmitterLike.ts
+++ b/src/utils/EventEmitterLike.ts
@@ -1,7 +1,26 @@
-import { CustomEvent } from './index.js';
+// See https://github.com/nodejs/node/issues/40678#issuecomment-1126944677
+class CustomEvent extends Event {
+ #detail;
+
+ constructor(type: string, options?: CustomEventInit) {
+ super(type, options);
+ this.#detail = options?.detail ?? null;
+ }
+
+ get detail(): any[] | null {
+ return this.#detail;
+ }
+}
+
+export class SabrAdapterError extends Error {
+ constructor(message: string, public code?: string) {
+ super(`[SabrStreamingAdapter] ${message}`);
+ this.name = 'SabrAdapterError';
+ }
+}
export class EventEmitterLike extends EventTarget {
- #legacy_listeners = new Map<(...args: any[]) => void, EventListener>();
+ #legacyListeners = new Map<(...args: any[]) => void, { type: string, wrapper: EventListener }>();
constructor() {
super();
@@ -20,7 +39,7 @@ export class EventEmitterLike extends EventTarget {
listener(ev);
}
};
- this.#legacy_listeners.set(listener, wrapper);
+ this.#legacyListeners.set(listener, { type, wrapper });
this.addEventListener(type, wrapper);
}
@@ -33,15 +52,31 @@ export class EventEmitterLike extends EventTarget {
}
this.off(type, listener);
};
- this.#legacy_listeners.set(listener, wrapper);
+ this.#legacyListeners.set(listener, { type, wrapper });
this.addEventListener(type, wrapper);
}
off(type: string, listener: (...args: any[]) => void) {
- const wrapper = this.#legacy_listeners.get(listener);
- if (wrapper) {
- this.removeEventListener(type, wrapper);
- this.#legacy_listeners.delete(listener);
+ const listenerData = this.#legacyListeners.get(listener);
+ if (listenerData && listenerData.type === type) {
+ this.removeEventListener(type, listenerData.wrapper);
+ this.#legacyListeners.delete(listener);
+ }
+ }
+
+ removeAllListeners(type?: string) {
+ if (type) {
+ for (const [ listener, listenerData ] of this.#legacyListeners.entries()) {
+ if (listenerData.type === type) {
+ this.removeEventListener(type, listenerData.wrapper);
+ this.#legacyListeners.delete(listener);
+ }
+ }
+ } else {
+ for (const [ listener, listenerData ] of this.#legacyListeners.entries()) {
+ this.removeEventListener(listenerData.type, listenerData.wrapper);
+ this.#legacyListeners.delete(listener);
+ }
}
}
}
\ No newline at end of file
diff --git a/src/utils/Logger.ts b/src/utils/Logger.ts
new file mode 100644
index 0000000..cb90d15
--- /dev/null
+++ b/src/utils/Logger.ts
@@ -0,0 +1,85 @@
+export enum LogLevel {
+ NONE = 0,
+ ERROR = 1,
+ WARN = 2,
+ INFO = 3,
+ DEBUG = 4,
+ ALL = 99,
+}
+
+export class Logger {
+ private static instance: Logger;
+ private currentLogLevels: Set = new Set([ LogLevel.INFO, LogLevel.ERROR ]);
+
+ public static getInstance(): Logger {
+ if (!Logger.instance) {
+ Logger.instance = new Logger();
+ }
+ return Logger.instance;
+ }
+
+ /**
+ * Sets the active log levels.
+ * Call with LogLevel.NONE or no arguments to turn off all logging.
+ * Otherwise, specify one or more log levels to be active.
+ * Use LogLevel.ALL to enable all log levels.
+ */
+ public setLogLevels(...levels: LogLevel[]): void {
+ if (levels.length === 0 || levels.includes(LogLevel.NONE)) {
+ this.currentLogLevels = new Set(); // Turn off logging
+ } else if (levels.includes(LogLevel.ALL)) {
+ this.currentLogLevels = new Set([
+ LogLevel.ERROR,
+ LogLevel.WARN,
+ LogLevel.INFO,
+ LogLevel.DEBUG
+ ]);
+ } else {
+ this.currentLogLevels = new Set(levels.filter((level) => level !== LogLevel.NONE && level !== LogLevel.ALL));
+ }
+ }
+
+ /**
+ * Gets the current set of active log levels.
+ * @returns A new Set containing the active LogLevel enums.
+ */
+ public getLogLevels(): Set {
+ return new Set(this.currentLogLevels);
+ }
+
+ private log(level: LogLevel, tag: string, ...messages: any[]): void {
+ if (level !== LogLevel.NONE && this.currentLogLevels.has(level)) {
+ const prefix = `[${LogLevel[level]}] [${tag}]`;
+ switch (level) {
+ case LogLevel.ERROR:
+ console.error(prefix, ...messages);
+ break;
+ case LogLevel.WARN:
+ console.warn(prefix, ...messages);
+ break;
+ case LogLevel.INFO:
+ console.info(prefix, ...messages);
+ break;
+ case LogLevel.DEBUG:
+ console.debug(prefix, ...messages);
+ break;
+ }
+ }
+ }
+
+ public error(tag: string, ...messages: any[]): void {
+ this.log(LogLevel.ERROR, tag, ...messages);
+ }
+
+ public warn(tag: string, ...messages: any[]): void {
+ this.log(LogLevel.WARN, tag, ...messages);
+ }
+
+ public info(tag: string, ...messages: any[]): void {
+ this.log(LogLevel.INFO, tag, ...messages);
+ }
+
+ public debug(tag: string, ...messages: any[]): void {
+ this.log(LogLevel.DEBUG, tag, ...messages);
+ }
+}
\ No newline at end of file
diff --git a/src/utils/Protos.ts b/src/utils/Protos.ts
index 9003d43..af6cb07 100644
--- a/src/utils/Protos.ts
+++ b/src/utils/Protos.ts
@@ -1,25 +1,51 @@
export { FormatInitializationMetadata } from '../../protos/generated/video_streaming/format_initialization_metadata.js';
-export { BufferedRange } from '../../protos/generated/video_streaming/buffered_range.js';
export { MediaHeader } from '../../protos/generated/video_streaming/media_header.js';
-export { NextRequestPolicy } from '../../protos/generated/video_streaming/next_request_policy.js';
-export { PlaybackCookie } from '../../protos/generated/video_streaming/playback_cookie.js';
-export { PlaybackStartPolicy } from '../../protos/generated/video_streaming/playback_start_policy.js';
-export { RequestCancellationPolicy } from '../../protos/generated/video_streaming/request_cancellation_policy.js';
-export { SabrError } from '../../protos/generated/video_streaming/sabr_error.js';
-export { SabrRedirect } from '../../protos/generated/video_streaming/sabr_redirect.js';
-export { StreamProtectionStatus } from '../../protos/generated/video_streaming/stream_protection_status.js';
-export { VideoPlaybackAbrRequest } from '../../protos/generated/video_streaming/video_playback_abr_request.js';
-export { OnesieRequest } from '../../protos/generated/video_streaming/onesie_request.js';
-export { EncryptedPlayerRequest } from '../../protos/generated/video_streaming/encrypted_player_request.js';
-export { OnesieHeader } from '../../protos/generated/video_streaming/onesie_header.js';
-export { OnesieHeaderType } from '../../protos/generated/video_streaming/onesie_header_type.js';
-export { OnesiePlayerRequest } from '../../protos/generated/video_streaming/onesie_player_request.js';
-export { OnesiePlayerResponse } from '../../protos/generated/video_streaming/onesie_player_response.js';
-export { ClientAbrState } from '../../protos/generated/video_streaming/client_abr_state.js';
-export { StreamerContext } from '../../protos/generated/video_streaming/streamer_context.js';
-export { OnesieProxyStatus } from '../../protos/generated/video_streaming/onesie_proxy_status.js';
+export { BufferedRange } from '../../protos/generated/video_streaming/buffered_range.js';
export { MediaCapabilities } from '../../protos/generated/video_streaming/media_capabilities.js';
export { CryptoParams } from '../../protos/generated/video_streaming/crypto_params.js';
+
+export { PlaybackCookie } from '../../protos/generated/video_streaming/playback_cookie.js';
+export { PlaybackStartPolicy } from '../../protos/generated/video_streaming/playback_start_policy.js';
+export { VideoPlaybackAbrRequest } from '../../protos/generated/video_streaming/video_playback_abr_request.js';
+export { ClientAbrState } from '../../protos/generated/video_streaming/client_abr_state.js';
+
+export { NextRequestPolicy } from '../../protos/generated/video_streaming/next_request_policy.js';
+export { RequestCancellationPolicy } from '../../protos/generated/video_streaming/request_cancellation_policy.js';
+export { RequestIdentifier } from '../../protos/generated/video_streaming/request_identifier.js';
+export { SabrError } from '../../protos/generated/video_streaming/sabr_error.js';
+export { SabrRedirect } from '../../protos/generated/video_streaming/sabr_redirect.js';
+
+export { OnesieRequest } from '../../protos/generated/video_streaming/onesie_request.js';
+export { OnesieHeader } from '../../protos/generated/video_streaming/onesie_header.js';
+export { OnesieHeaderType } from '../../protos/generated/video_streaming/onesie_header_type.js';
+export { OnesieInnertubeRequest } from '../../protos/generated/video_streaming/onesie_innertube_request.js';
+export { OnesieInnertubeResponse } from '../../protos/generated/video_streaming/onesie_innertube_response.js';
+export { OnesieProxyStatus } from '../../protos/generated/video_streaming/onesie_proxy_status.js';
+export { InnertubeRequest, UstreamerFlags } from '../../protos/generated/video_streaming/innertube_request.js';
+export { StreamerContext, StreamerContext_ClientInfo as ClientInfo } from '../../protos/generated/video_streaming/streamer_context.js';
+export { SabrContextUpdate, SabrContextUpdate_SabrContextWritePolicy as SabrContextWritePolicy, SabrContextValue } from '../../protos/generated/video_streaming/sabr_context_update.js';
+export { SabrContextSendingPolicy } from '../../protos/generated/video_streaming/sabr_context_sending_policy.js';
+
+export { StreamProtectionStatus } from '../../protos/generated/video_streaming/stream_protection_status.js';
export { LiveMetadata } from '../../protos/generated/video_streaming/live_metadata.js';
-export { FormatId, KeyValuePair, InitRange, IndexRange, HttpHeader } from '../../protos/generated/misc/common.js';
-export { CryptoParams_CompressionType as CompressionType } from '../../protos/generated/video_streaming/crypto_params.js';
\ No newline at end of file
+export { SnackbarMessage } from '../../protos/generated/video_streaming/snackbar_message.js';
+export { UMPPartId } from '../../protos/generated/video_streaming/ump_part_id.js';
+export { ReloadPlaybackContext, ReloadPlaybackParams } from '../../protos/generated/video_streaming/reload_player_response.js';
+export { FormatSelectionConfig } from '../../protos/generated/video_streaming/format_selection_config.js';
+
+export {
+ FormatId,
+ KeyValuePair,
+ Range,
+ HttpHeader,
+ IdentifierToken,
+ CompressionType,
+ OnesieRequestTarget,
+ AuthorizedFormat,
+ AudioQuality,
+ PlaybackAuthorization,
+ NetworkMeteredState,
+ PlaybackAudioRouteOutputType,
+ SeekSource,
+ VideoQualitySetting
+} from '../../protos/generated/misc/common.js';
\ No newline at end of file
diff --git a/src/utils/RequestMetadataManager.ts b/src/utils/RequestMetadataManager.ts
new file mode 100644
index 0000000..a852b32
--- /dev/null
+++ b/src/utils/RequestMetadataManager.ts
@@ -0,0 +1,61 @@
+import type { SabrRequestMetadata } from '../types/sabrStreamingAdapterTypes.js';
+
+/**
+ * Manages request metadata objects.
+ */
+export class RequestMetadataManager {
+ public metadataMap: Map;
+ private lastCleanup: number;
+ private readonly CLEANUP_INTERVAL = 30000;
+ private readonly ENTRY_EXPIRATION_TIME = 1000 * 60 * 3;
+
+ constructor() {
+ this.metadataMap = new Map();
+ this.lastCleanup = Date.now();
+ }
+
+ public getRequestMetadata(url: string, del = false): SabrRequestMetadata | undefined {
+ const requestNumber = new URL(url).searchParams.get('rn') || '';
+
+ const streamingContext = this.metadataMap.get(requestNumber);
+
+ // Check if this specific entry is expired.
+ if (streamingContext && Date.now() - streamingContext.timestamp > this.ENTRY_EXPIRATION_TIME) {
+ this.metadataMap.delete(requestNumber);
+ return undefined;
+ }
+
+ if (del) {
+ this.metadataMap.delete(requestNumber);
+ }
+
+ this.conditionalCleanUp();
+
+ return streamingContext;
+ }
+
+ public setRequestMetadata(url: string, context: SabrRequestMetadata) {
+ const requestNumber = new URL(url).searchParams.get('rn');
+ if (requestNumber) {
+ this.metadataMap.set(requestNumber, context);
+ this.conditionalCleanUp();
+ }
+ }
+
+ private conditionalCleanUp() {
+ const now = Date.now();
+ if (now - this.lastCleanup > this.CLEANUP_INTERVAL) {
+ this.cleanUp();
+ this.lastCleanup = now;
+ }
+ }
+
+ private cleanUp() {
+ // Should rarely run. This is only for requests that fail and never get handled (e.g., network errors).
+ for (const [ key, context ] of this.metadataMap.entries()) {
+ if (Date.now() - context.timestamp > this.ENTRY_EXPIRATION_TIME) {
+ this.metadataMap.delete(key);
+ }
+ }
+ }
+}
diff --git a/src/utils/formatKeyUtils.ts b/src/utils/formatKeyUtils.ts
new file mode 100644
index 0000000..04d9b03
--- /dev/null
+++ b/src/utils/formatKeyUtils.ts
@@ -0,0 +1,100 @@
+import type { MediaHeader } from './Protos.js';
+import type { FormatInitializationMetadata } from './Protos.js';
+import type { SabrFormat } from '../types/shared.js';
+import type { SabrRequestMetadata } from '../types/sabrStreamingAdapterTypes.js';
+
+/**
+ * Creates a format key based on itag and xtags.
+ * @returns A string format key.
+ */
+export function createKey(itag: number | undefined, xtags: string | undefined): string {
+ return `${itag || ''}:${xtags || ''}`;
+}
+
+/**
+ * Creates a format key from a SabrFormat object.
+ * @returns A string format key or undefined if format is undefined.
+ */
+export function fromFormat(format?: { itag?: number; xtags?: string }): string | undefined {
+ if (!format) return undefined;
+ return createKey(format.itag, format.xtags);
+}
+
+/**
+ * Creates a format key from a MediaHeader object.
+ * @returns A string format key.
+ */
+export function fromMediaHeader(mediaHeader: MediaHeader): string {
+ return createKey(mediaHeader.itag, mediaHeader.xtags);
+}
+
+/**
+ * Creates a format key from FormatInitializationMetadata.
+ * @returns A string format key or undefined if formatId is undefined.
+ */
+export function fromFormatInitializationMetadata(
+ formatInitMetadata: FormatInitializationMetadata): string {
+ if (!formatInitMetadata.formatId) return '';
+ return createKey(formatInitMetadata.formatId.itag, formatInitMetadata.formatId.xtags);
+}
+
+/**
+ * Creates a segment cache key.
+ * @param mediaHeader - The MediaHeader object.
+ * @param format - Format object (needed for init segments.)
+ * @returns A string key for caching segments.
+ */
+export function createSegmentCacheKey(
+ mediaHeader: MediaHeader,
+ format?: SabrFormat
+): string {
+ if (mediaHeader.isInitSeg && format) {
+ return `${mediaHeader.itag}:${mediaHeader.xtags || ''}:${format.contentLength || ''}:${format.mimeType || ''}`;
+ }
+ return `${mediaHeader.startRange || '0'}-${mediaHeader.itag}-${mediaHeader.xtags || ''}`;
+}
+
+/**
+ * Creates a cache key from request metadata.
+ * @returns A string key for caching segments.
+ */
+export function createSegmentCacheKeyFromMetadata(
+ requestMetadata: SabrRequestMetadata
+): string {
+ if (!requestMetadata.byteRange || !requestMetadata.format)
+ throw new Error('Invalid metadata: byteRange or format is missing');
+
+ const pseudoMediaHeader = {
+ itag: requestMetadata.format.itag,
+ xtags: requestMetadata.format.xtags || '',
+ startRange: requestMetadata.byteRange.start,
+ isInitSeg: requestMetadata.isInit
+ };
+
+ return createSegmentCacheKey(
+ pseudoMediaHeader,
+ requestMetadata.isInit ? requestMetadata.format : undefined
+ );
+}
+
+/**
+ * Generates a unique format ID based on the SabrFormat properties.
+ * @param format - The SabrFormat object.
+ * @returns A unique string identifier for the format.
+ */
+export function getUniqueFormatId(format: SabrFormat) {
+ if (format.width)
+ return format.itag.toString();
+
+ const uidParts = [ format.itag.toString() ];
+
+ if (format.audioTrackId) {
+ uidParts.push(format.audioTrackId);
+ }
+
+ if (format.isDrc) {
+ uidParts.push('drc');
+ }
+
+ return uidParts.join('-');
+}
\ No newline at end of file
diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts
deleted file mode 100644
index 8fbd91b..0000000
--- a/src/utils/helpers.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-import type { FormatId } from '../../protos/generated/misc/common.js';
-
-export enum QUALITY {
- AUTO = 0,
- TINY = 144,
- SMALL = 240,
- MEDIUM = 360,
- // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
- LIGHT = 144,
- LARGE = 480,
- HD720 = 720,
- HD1080 = 1080,
- HD1440 = 1440,
- HD2160 = 2160,
- HD2880 = 2880,
- HIGHRES= 4320
-}
-
-export enum PART {
- ONESIE_HEADER = 10,
- ONESIE_DATA = 11,
- MEDIA_HEADER = 20,
- MEDIA = 21,
- MEDIA_END = 22,
- LIVE_METADATA = 31,
- HOSTNAME_CHANGE_HINT = 32,
- LIVE_METADATA_PROMISE = 33,
- LIVE_METADATA_PROMISE_CANCELLATION = 34,
- NEXT_REQUEST_POLICY = 35,
- USTREAMER_VIDEO_AND_FORMAT_DATA = 36,
- FORMAT_SELECTION_CONFIG = 37,
- USTREAMER_SELECTED_MEDIA_STREAM = 38,
- FORMAT_INITIALIZATION_METADATA = 42,
- SABR_REDIRECT = 43,
- SABR_ERROR = 44,
- SABR_SEEK = 45,
- RELOAD_PLAYER_RESPONSE = 46,
- PLAYBACK_START_POLICY = 47,
- ALLOWED_CACHED_FORMATS = 48,
- START_BW_SAMPLING_HINT = 49,
- PAUSE_BW_SAMPLING_HINT = 50,
- SELECTABLE_FORMATS = 51,
- REQUEST_IDENTIFIER = 52,
- REQUEST_CANCELLATION_POLICY = 53,
- ONESIE_PREFETCH_REJECTION = 54,
- TIMELINE_CONTEXT = 55,
- REQUEST_PIPELINING = 56,
- SABR_CONTEXT_UPDATE = 57,
- STREAM_PROTECTION_STATUS = 58,
- SABR_CONTEXT_SENDING_POLICY = 59,
- LAWNMOWER_POLICY = 60,
- SABR_ACK = 61,
- END_OF_TRACK = 62,
- CACHE_LOAD_POLICY = 63,
- LAWNMOWER_MESSAGING_POLICY = 64,
- PREWARM_CONNECTION = 65
-}
-
-export function u8ToBase64(u8: Uint8Array): string {
- return btoa(String.fromCharCode.apply(null, Array.from(u8)));
-}
-
-export function base64ToU8(base64: string): Uint8Array {
- const standard_base64 = base64.replace(/-/g, '+').replace(/_/g, '/');
- const padded_base64 = standard_base64.padEnd(standard_base64.length + (4 - standard_base64.length % 4) % 4, '=');
- return new Uint8Array(atob(padded_base64).split('').map((char) => char.charCodeAt(0)));
-}
-
-export function getFormatKey(formatId: FormatId): string {
- return `${formatId.itag};${formatId.lastModified};`;
-}
-
-export function concatenateChunks(chunks: Uint8Array[]): Uint8Array {
- const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
- const result = new Uint8Array(totalLength);
- let offset = 0;
- for (const chunk of chunks) {
- result.set(chunk, offset);
- offset += chunk.length;
- }
- return result;
-}
-
-// See https://github.com/nodejs/node/issues/40678#issuecomment-1126944677
-export class CustomEvent extends Event {
- #detail;
-
- constructor(type: string, options?: CustomEventInit) {
- super(type, options);
- this.#detail = options?.detail ?? null;
- }
-
- get detail(): any[] | null {
- return this.#detail;
- }
-}
\ No newline at end of file
diff --git a/src/utils/index.ts b/src/utils/index.ts
index cd211d2..c2be56f 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -1,4 +1,6 @@
-export * from './helpers.js';
+export * from './Logger.js';
+export * from './shared.js';
+export * from './CacheManager.js';
export * from './EventEmitterLike.js';
-export * as Protos from './Protos.js';
-export type * from './types.js';
\ No newline at end of file
+export * from './RequestMetadataManager.js';
+export * as FormatKeyUtils from './formatKeyUtils.js';
\ No newline at end of file
diff --git a/src/utils/sabrStreamUtils.ts b/src/utils/sabrStreamUtils.ts
new file mode 100644
index 0000000..7c351af
--- /dev/null
+++ b/src/utils/sabrStreamUtils.ts
@@ -0,0 +1,105 @@
+import type { InitializedFormat } from '../core/SabrStream.js';
+import type { SabrFormat } from '../types/shared.js';
+
+/**
+ * Determines the media type (video or audio) based on the format initialization metadata.
+ * @param initializedFormat
+ */
+export function getMediaType(initializedFormat: InitializedFormat): 'video' | 'audio' {
+ return initializedFormat.formatInitializationMetadata?.mimeType?.includes('video') ? 'video' : 'audio';
+}
+
+/**
+ * Calculates the total duration of downloaded segments in milliseconds.
+ * @param initializedFormat
+ */
+export function getTotalDownloadedDuration(
+ initializedFormat: InitializedFormat
+): number {
+ return Array.from(initializedFormat.downloadedSegments.values())
+ .reduce((sum, segment) => sum + (segment.durationMs || 0), 0);
+}
+
+/**
+ * Filters formats by media type (audio or video)
+ */
+export function filterFormatsByType(formats: SabrFormat[], isAudio: boolean): SabrFormat[] {
+ return formats.filter((format) => {
+ if (!format.mimeType) return false;
+ return isAudio
+ ? format.mimeType.includes('audio')
+ : format.mimeType.includes('video');
+ });
+}
+
+/**
+ * Choose the best format based on options
+ */
+export function chooseFormat(
+ formats: SabrFormat[],
+ formatOption: number | SabrFormat | ((formats: SabrFormat[]) => SabrFormat | undefined) | undefined,
+ preferences: {
+ quality?: string;
+ language?: string;
+ preferWebM?: boolean;
+ preferMP4?: boolean;
+ preferH264?: boolean;
+ preferOpus?: boolean;
+ isAudio: boolean;
+ }
+): SabrFormat | undefined {
+ if (!formats.length) return undefined;
+
+ const typeFormats = filterFormatsByType(formats, preferences.isAudio);
+ if (!typeFormats.length) return undefined;
+
+ if (typeof formatOption === 'number') {
+ return typeFormats.find((format) => format.itag === formatOption);
+ }
+
+ if (formatOption && typeof formatOption !== 'function') {
+ return formatOption;
+ }
+
+ if (typeof formatOption === 'function') {
+ return formatOption(typeFormats);
+ }
+
+ let filteredFormats = typeFormats;
+
+ if (preferences.language) {
+ filteredFormats = filteredFormats.filter((format) => format.language === preferences.language);
+ }
+
+ if (preferences.quality) {
+ filteredFormats = filteredFormats.filter((format) =>
+ preferences.isAudio
+ ? !!format.audioQuality?.toLowerCase().includes(preferences.quality?.toLowerCase() || '')
+ : !!format.qualityLabel?.toLowerCase().includes(preferences.quality?.toLowerCase() || '')
+ );
+ }
+
+ if (preferences.isAudio) {
+ if (preferences.preferOpus) {
+ filteredFormats = applyMimeTypeFilter(filteredFormats, 'opus');
+ }
+ } else if (preferences.preferH264) {
+ filteredFormats = filteredFormats.filter(
+ (format) => !!format.mimeType && format.mimeType.includes('mp4') && format.mimeType.includes('avc')
+ );
+ }
+
+ if (preferences.preferWebM) {
+ filteredFormats = applyMimeTypeFilter(filteredFormats, 'webm');
+ } else if (preferences.preferMP4) {
+ filteredFormats = applyMimeTypeFilter(filteredFormats, 'mp4');
+ }
+
+ return preferences.isAudio
+ ? filteredFormats.sort((a, b) => (b.bitrate || 0) - (a.bitrate || 0))[0]
+ : filteredFormats.sort((a, b) => (b.height || 0) - (a.height || 0))[0];
+}
+
+function applyMimeTypeFilter(formats: SabrFormat[], mimeTypePart: string): SabrFormat[] {
+ return formats.filter((format) => format.mimeType?.includes(mimeTypePart));
+}
\ No newline at end of file
diff --git a/src/utils/shared.ts b/src/utils/shared.ts
new file mode 100644
index 0000000..9b4b77d
--- /dev/null
+++ b/src/utils/shared.ts
@@ -0,0 +1,126 @@
+import type { FormatStream, SabrFormat } from '../types/shared.js';
+
+export const MAX_INT32_VALUE = 2147483647;
+
+export enum EnabledTrackTypes {
+ VIDEO_AND_AUDIO = 0,
+ AUDIO_ONLY = 1,
+ VIDEO_ONLY = 2,
+}
+
+export function isGoogleVideoURL(url: string): boolean {
+ if (url.startsWith('sabr://')) {
+ return true;
+ }
+
+ const urlParts = url.split('?');
+ const urlPart = urlParts[0];
+ const queryPart = urlParts[1] || '';
+
+ if (urlPart.endsWith('/videoplayback')) {
+ const params = new URLSearchParams(queryPart);
+ if (params.get('source') === 'youtube' || params.has('sabr') || params.has('lsig') || params.has('expire')) {
+ return true;
+ }
+ } else if (urlPart.includes('/videoplayback/')) { // For live content, post-live, etc.
+ const pathParts = urlPart.split('/');
+ return [ 'videoplayback', 'sabr', 'lsig', 'expire' ].some((part) => pathParts.includes(part));
+ }
+
+ return false;
+}
+
+interface Range {
+ start: number;
+ end: number;
+}
+
+/**
+ * Parses the Range header value to extract the start and end byte positions.
+ * @param rangeHeaderValue
+ */
+export function parseRangeHeader(rangeHeaderValue: string | undefined): Range | undefined {
+ if (!rangeHeaderValue) return undefined;
+
+ const parts = rangeHeaderValue.split('=')[1]?.split('-');
+ if (parts?.length) {
+ const start = Number(parts[0]);
+ const end = Number(parts[1]);
+ return { start, end };
+ }
+
+ return undefined;
+}
+
+/**
+ * Converts a Uint8Array to a Base64 string.
+ * @param u8
+ */
+export function u8ToBase64(u8: Uint8Array): string {
+ return btoa(String.fromCharCode.apply(null, Array.from(u8)));
+}
+
+/**
+ * Converts a Base64 string to a Uint8Array.
+ * @param base64
+ */
+export function base64ToU8(base64: string): Uint8Array {
+ const standard_base64 = base64.replace(/-/g, '+').replace(/_/g, '/');
+ const padded_base64 = standard_base64.padEnd(standard_base64.length + (4 - standard_base64.length % 4) % 4, '=');
+ return new Uint8Array(atob(padded_base64).split('').map((char) => char.charCodeAt(0)));
+}
+
+/**
+ * Concatenates multiple Uint8Array chunks into a single Uint8Array.
+ * @param chunks
+ */
+export function concatenateChunks(chunks: Uint8Array[]): Uint8Array {
+ const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
+ const result = new Uint8Array(totalLength);
+ let offset = 0;
+ for (const chunk of chunks) {
+ result.set(chunk, offset);
+ offset += chunk.length;
+ }
+ return result;
+}
+
+/**
+ * Converts a FormatStream object to a SabrFormat object.
+ * @param formatStream
+ */
+export function buildSabrFormat(formatStream: FormatStream): SabrFormat {
+ return {
+ itag: formatStream.itag,
+ lastModified: parseInt(formatStream.last_modified_ms || formatStream.lastModified || '0'),
+ xtags: formatStream.xtags,
+ width: formatStream.width,
+ height: formatStream.height,
+ mimeType: formatStream.mime_type || formatStream.mimeType,
+ audioQuality: formatStream.audio_quality || formatStream.audioQuality,
+ bitrate: formatStream.bitrate,
+ averageBitrate: formatStream.average_bitrate || formatStream.averageBitrate,
+ quality: formatStream.quality,
+ qualityLabel: formatStream.quality_label || formatStream.qualityLabel,
+ audioTrackId: formatStream.audio_track?.id || formatStream.audioTrackId,
+ approxDurationMs: formatStream.approx_duration_ms || parseInt(formatStream.approxDurationMs || '0'),
+ contentLength: parseInt(formatStream.contentLength || '0') || formatStream.content_length,
+
+ // YouTube.js-specific properties.
+ isDrc: formatStream.is_drc,
+ isAutoDubbed: formatStream.is_auto_dubbed,
+ isDescriptive: formatStream.is_descriptive,
+ isDubbed: formatStream.is_dubbed,
+ language: formatStream.language,
+ isOriginal: formatStream.is_original,
+ isSecondary: formatStream.is_secondary
+ };
+}
+
+/**
+ * Returns a promise that resolves after a specified number of milliseconds.
+ * @param ms - The number of milliseconds to wait.
+ */
+export function wait(ms: number): Promise {
+ return new Promise((resolve) => setTimeout(resolve, ms));
+}
\ No newline at end of file
diff --git a/src/utils/types.ts b/src/utils/types.ts
deleted file mode 100644
index 51bac60..0000000
--- a/src/utils/types.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-import type { FormatId } from '../../protos/generated/misc/common.js';
-import type { SabrError } from '../../protos/generated/video_streaming/sabr_error.js';
-import type { SabrRedirect } from '../../protos/generated/video_streaming/sabr_redirect.js';
-import type { StreamProtectionStatus } from '../../protos/generated/video_streaming/stream_protection_status.js';
-import type { TimeRange } from '../../protos/generated/video_streaming/time_range.js';
-import type { BufferedRange } from '../../protos/generated/video_streaming/buffered_range.js';
-import type { ClientAbrState } from '../../protos/generated/video_streaming/client_abr_state.js';
-import type { ChunkedDataBuffer } from '../core/index.js';
-
-export type Part = {
- type: number;
- size: number;
- data: ChunkedDataBuffer;
-};
-
-export type ServerAbrStreamOptions = {
- fetch: FetchFunction;
- serverAbrStreamingUrl: string;
- videoPlaybackUstreamerConfig: string;
- poToken?: string;
- durationMs: number;
-}
-
-export type ServerAbrResponse = {
- initializedFormats: InitializedFormat[];
- streamProtectionStatus?: StreamProtectionStatus;
- sabrRedirect?: SabrRedirect;
- sabrError?: SabrError;
-}
-
-export type Sequence = {
- itag?: number;
- formatId?: FormatId;
- isInitSegment?: boolean;
- durationMs?: number;
- startMs?: number;
- startDataRange?: number;
- sequenceNumber?: number;
- contentLength?: number;
- timeRange?: TimeRange;
-}
-
-export type InitializedFormat = {
- formatId: FormatId;
- formatKey: string;
- durationMs?: number;
- mimeType?: string;
- sequenceCount?: number;
- sequenceList: Sequence[];
- mediaChunks: Uint8Array[];
- _state: BufferedRange;
-}
-
-export type InitOptions = {
- audioFormats: Format[];
- videoFormats: Format[];
- clientAbrState?: ClientAbrState;
-};
-
-export type MediaArgs = {
- clientAbrState: ClientAbrState;
- audioFormatIds: FormatId[];
- videoFormatIds: FormatId[];
-}
-
-export type Format = {
- itag: number;
- width?: number;
- height?: number;
- lastModified: string;
- xtags?: string;
-}
-
-export type FetchFunction = typeof fetch;
\ No newline at end of file
diff --git a/tests/sabr.stream.test.ts b/tests/sabr.stream.test.ts
new file mode 100644
index 0000000..c305a3e
--- /dev/null
+++ b/tests/sabr.stream.test.ts
@@ -0,0 +1,304 @@
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+
+import { Logger, LogLevel, concatenateChunks, EnabledTrackTypes } from '../src/utils/index.js';
+import { SabrFormat } from '../src/types/shared.js';
+import { CompositeBuffer, UmpWriter } from '../src/exports/ump.js';
+import { SabrStream } from '../src/exports/sabr-stream.js';
+
+import {
+ UMPPartId,
+ FormatInitializationMetadata,
+ MediaHeader, NextRequestPolicy,
+ StreamProtectionStatus,
+ VideoPlaybackAbrRequest
+} from '../src/utils/Protos.js';
+
+Logger.getInstance().setLogLevels(LogLevel.NONE);
+
+const AUDIO_FORMAT = {
+ itag: 140,
+ lastModified: 1700000000,
+ contentLength: 117138,
+ mimeType: 'audio/mp4; codecs="mp4a.40.2"',
+ bitrate: 128000,
+ approxDurationMs: 120000
+};
+
+const VIDEO_FORMAT = {
+ itag: 137,
+ mimeType: 'video/mp4; codecs="avc1.640028"',
+ bitrate: 4337000,
+ lastModified: 1700000000,
+ height: 1080,
+ approxDurationMs: 120000,
+ qualityLabel: undefined,
+ language: null
+};
+
+const CLIENT_INFO = {
+ clientName: 1,
+ clientVersion: '2.20240101.00.00'
+};
+
+function createMediaHeader(
+ headerId: number,
+ sequenceNumber: number,
+ startMs: number,
+ durationMs: number,
+ startRange: number,
+ contentLength: number,
+ isInitSeg: boolean,
+ format: SabrFormat
+) {
+ return {
+ partType: UMPPartId.MEDIA_HEADER,
+ partData: MediaHeader.encode({
+ headerId,
+ videoId: '',
+ itag: format.itag,
+ lmt: format.lastModified,
+ startRange,
+ compressionAlgorithm: 0,
+ isInitSeg,
+ sequenceNumber,
+ bitrateBps: format.bitrate,
+ startMs,
+ durationMs: durationMs || 0,
+ formatId: format,
+ contentLength,
+ timeRange: {
+ startTicks: startMs,
+ durationTicks: durationMs || 0,
+ timescale: 1000
+ }
+ }).finish()
+ };
+}
+
+function createMediaPart(headerId: number, mockedSize: number) {
+ return {
+ partType: UMPPartId.MEDIA,
+ partData: new Uint8Array([ headerId, ...new Uint8Array(mockedSize).fill(0) ])
+ };
+}
+
+function createMediaEndPart(headerId: number) {
+ return {
+ partType: UMPPartId.MEDIA_END,
+ partData: new Uint8Array([ headerId ])
+ };
+}
+
+function createMockFetch(maxSegmentSize: number, maxSegmentDuration: number, streamProtectionStatus = 1) {
+ let startMs = 0;
+ let startRange = 0;
+ let segmentNumber = 0;
+
+ return vi.fn().mockImplementation(async (url, options) => {
+ const request = new Request(url, options);
+ const requestBodyData = await request.arrayBuffer();
+ const requestBody = VideoPlaybackAbrRequest.decode(new Uint8Array(requestBodyData));
+
+ const playerTimeMs = requestBody.clientAbrState?.playerTimeMs || 0;
+
+ const partsToWrite = [];
+
+ partsToWrite.push({
+ partType: UMPPartId.NEXT_REQUEST_POLICY,
+ partData: NextRequestPolicy.encode({
+ targetAudioReadaheadMs: 15011,
+ targetVideoReadaheadMs: 15011,
+ backoffTimeMs: 0,
+ playbackCookie: {
+ resolution: 999999,
+ field2: 0,
+ videoFmt: VIDEO_FORMAT,
+ audioFmt: AUDIO_FORMAT
+ },
+ videoId: ''
+ }).finish()
+ });
+
+ partsToWrite.push({
+ partType: UMPPartId.STREAM_PROTECTION_STATUS,
+ partData: StreamProtectionStatus.encode({ status: streamProtectionStatus }).finish()
+ });
+
+ if (playerTimeMs === 0) {
+ // Initialize the format.
+ partsToWrite.push({
+ partType: UMPPartId.FORMAT_INITIALIZATION_METADATA,
+ partData: FormatInitializationMetadata.encode({
+ formatId: AUDIO_FORMAT,
+ durationUnits: 120000,
+ durationTimescale: 1000,
+ endSegmentNumber: 5,
+ mimeType: AUDIO_FORMAT.mimeType,
+ endTimeMs: 120000,
+ videoId: ''
+ }).finish()
+ });
+
+ // Add the init segment.
+ const initHeaderId = 0;
+ partsToWrite.push(createMediaHeader(initHeaderId, segmentNumber, 0, 0, 0, maxSegmentSize, true, AUDIO_FORMAT));
+ partsToWrite.push(createMediaPart(initHeaderId, maxSegmentSize));
+ partsToWrite.push(createMediaEndPart(initHeaderId));
+ startRange += maxSegmentSize;
+
+ segmentNumber += 1;
+
+ // Send 1 segment to get the stream started.
+ const mediaHeaderId = 1;
+ partsToWrite.push(createMediaHeader(mediaHeaderId, segmentNumber, startMs, maxSegmentDuration, startRange, maxSegmentSize, false, AUDIO_FORMAT));
+ partsToWrite.push(createMediaPart(mediaHeaderId, maxSegmentSize));
+ partsToWrite.push(createMediaEndPart(mediaHeaderId));
+ startMs += maxSegmentDuration;
+ startRange += maxSegmentSize;
+ } else if (playerTimeMs < 120000) {
+ const mediaHeaderId = 0;
+ partsToWrite.push(createMediaHeader(mediaHeaderId, segmentNumber, startMs, maxSegmentDuration, startRange, maxSegmentSize, false, AUDIO_FORMAT));
+ partsToWrite.push(createMediaPart(mediaHeaderId, maxSegmentSize));
+ partsToWrite.push(createMediaEndPart(mediaHeaderId));
+ startMs += maxSegmentDuration;
+ startRange += maxSegmentSize;
+ }
+
+ segmentNumber += 1;
+
+ const buffer = new CompositeBuffer();
+ const umpWriter = new UmpWriter(buffer);
+
+ // Write all parts to the response.
+ for (const part of partsToWrite) {
+ umpWriter.write(part.partType, part.partData);
+ }
+
+ const responseBody = concatenateChunks(buffer.chunks);
+ return new Response(responseBody, {
+ status: 200,
+ headers: { 'Content-Type': 'application/vnd.yt-ump' }
+ });
+ });
+}
+
+async function collectStreamChunks(stream: ReadableStream): Promise {
+ const chunks = [];
+ const reader = stream.getReader();
+
+ while (true) {
+ const { done, value } = await reader.read();
+ if (done) break;
+ chunks.push(value);
+ }
+
+ return chunks;
+}
+
+function createSabrStream(mockFetch: typeof fetch) {
+ return new SabrStream({
+ fetch: mockFetch,
+ serverAbrStreamingUrl: 'https://test.com/sabr',
+ videoPlaybackUstreamerConfig: 'abc',
+ poToken: 'abc',
+ clientInfo: CLIENT_INFO,
+ formats: [ VIDEO_FORMAT, AUDIO_FORMAT ]
+ });
+}
+
+describe('SabrStream', { timeout: 80000 }, () => {
+ const maxSegmentSize = 19523; // 117138 / 6 = 19523 bytes
+ const maxSegmentDuration = 24000; // 120000 / 5 = 24000 ms
+
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it('should initialize, download, and finish a stream successfully', async () => {
+ const mockFetch = createMockFetch(maxSegmentSize, maxSegmentDuration);
+
+ const onFormatInitialization = vi.fn();
+ const onStreamProtectionStatusUpdate = vi.fn();
+ const onFinish = vi.fn();
+
+ const stream = createSabrStream(mockFetch);
+
+ stream.on('formatInitialization', onFormatInitialization);
+ stream.on('streamProtectionStatusUpdate', onStreamProtectionStatusUpdate);
+ stream.on('finish', onFinish);
+
+ const { audioStream, selectedFormats } = await stream.start({
+ videoFormat: VIDEO_FORMAT,
+ audioFormat: AUDIO_FORMAT,
+ enabledTrackTypes: EnabledTrackTypes.AUDIO_ONLY
+ });
+
+ const audioChunks = await collectStreamChunks(audioStream);
+
+ expect(selectedFormats.audioFormat).toEqual(AUDIO_FORMAT);
+ expect(selectedFormats.videoFormat).toEqual(VIDEO_FORMAT);
+ expect(onFinish).toHaveBeenCalled();
+ expect(onFormatInitialization).toHaveBeenCalled();
+ expect(onStreamProtectionStatusUpdate).toHaveBeenCalledWith({ status: 1, maxRetries: 0 });
+ expect(concatenateChunks(audioChunks).length).toBe(AUDIO_FORMAT.contentLength);
+ expect(mockFetch).toHaveBeenCalledTimes(6);
+ });
+
+ it('should abort the stream when abort() is called', async () => {
+ const mockFetch = createMockFetch(maxSegmentSize, maxSegmentDuration);
+
+ const stream = createSabrStream(mockFetch);
+
+ const onAbort = vi.fn();
+ stream.on('abort', onAbort);
+
+ const startPromise = stream.start({
+ videoFormat: VIDEO_FORMAT,
+ audioFormat: AUDIO_FORMAT,
+ enabledTrackTypes: EnabledTrackTypes.AUDIO_ONLY
+ });
+
+ stream.abort();
+
+ const { videoStream } = await startPromise;
+
+ await expect(videoStream.getReader().read()).rejects.toThrow('Download aborted.');
+ expect(onAbort).toHaveBeenCalledOnce();
+ expect(mockFetch).toHaveBeenCalledTimes(1);
+ });
+
+ it('should fail after exhausting all retry attempts when server returns an error', async () => {
+ const mockFetch = createMockFetch(maxSegmentSize, maxSegmentDuration);
+ mockFetch.mockResolvedValue(new Response(null, { status: 500, statusText: 'Internal Server Error' }));
+
+ const stream = createSabrStream(mockFetch);
+
+ const { audioStream } = await stream.start({
+ videoFormat: VIDEO_FORMAT,
+ audioFormat: AUDIO_FORMAT,
+ enabledTrackTypes: EnabledTrackTypes.AUDIO_ONLY,
+ maxRetries: 1
+ });
+
+ await expect(collectStreamChunks(audioStream)).rejects.toThrow('Server returned 500 ');
+
+ expect(mockFetch).toHaveBeenCalledTimes(2);
+ });
+
+ it('should terminate streaming when attestation is required by stream protection', async () => {
+ const mockFetch = createMockFetch(maxSegmentSize, maxSegmentDuration, 3);
+
+ const stream = createSabrStream(mockFetch);
+
+ const { audioStream } = await stream.start({
+ videoFormat: VIDEO_FORMAT,
+ audioFormat: AUDIO_FORMAT,
+ enabledTrackTypes: EnabledTrackTypes.AUDIO_ONLY,
+ maxRetries: 1
+ });
+
+ await expect(collectStreamChunks(audioStream)).rejects.toThrow('Cannot proceed with stream: attestation required');
+
+ expect(mockFetch).toHaveBeenCalledTimes(2);
+ });
+});
\ No newline at end of file
diff --git a/tests/ump.reader.test.ts b/tests/ump.reader.test.ts
new file mode 100644
index 0000000..dcd2166
--- /dev/null
+++ b/tests/ump.reader.test.ts
@@ -0,0 +1,156 @@
+import { describe, it, expect, vi } from 'vitest';
+
+import { Part } from '../src/types/shared.js';
+import { concatenateChunks } from '../src/utils/shared.js';
+import { CompositeBuffer, UmpReader, UmpWriter } from '../src/exports/ump.js';
+
+describe('UmpReader', () => {
+ it('should read a single small part correctly', () => {
+ const buffer = new CompositeBuffer();
+ const writer = new UmpWriter(buffer);
+
+ const partType = 1;
+ const partData = new Uint8Array([ 10, 20, 30 ]);
+ writer.write(partType, partData);
+
+ const reader = new UmpReader(buffer);
+ const handlePart = vi.fn();
+
+ const incompletePart = reader.read(handlePart);
+ const receivedPart = handlePart.mock.calls[0][0];
+
+ expect(handlePart).toHaveBeenCalledOnce();
+ expect(receivedPart.type).toBe(partType);
+ expect(receivedPart.size).toBe(partData.length);
+ expect(concatenateChunks(receivedPart.data.chunks)).toEqual(partData);
+ expect(incompletePart).toBeUndefined();
+ });
+
+ it('should read multiple parts sequentially', () => {
+ const buffer = new CompositeBuffer();
+ const writer = new UmpWriter(buffer);
+
+ const partsToWrite = [
+ { type: 20, data: new Uint8Array([ 1, 2 ]) },
+ { type: 21, data: new Uint8Array([ 3, 4, 5 ]) },
+ { type: 150, data: new Uint8Array([ 6, 7, 8, 9 ]) }
+ ];
+
+ for (const part of partsToWrite) {
+ writer.write(part.type, part.data);
+ }
+
+ const reader = new UmpReader(buffer);
+ const receivedParts: Part[] = [];
+
+ const handlePart = (part: Part) => {
+ receivedParts.push(part);
+ };
+
+ const incompletePart = reader.read(handlePart);
+
+ expect(receivedParts.length).toBe(partsToWrite.length);
+
+ for (let i = 0; i < partsToWrite.length; i++) {
+ expect(receivedParts[i].type).toBe(partsToWrite[i].type);
+ expect(receivedParts[i].size).toBe(partsToWrite[i].data.length);
+ expect(concatenateChunks(receivedParts[i].data.chunks)).toEqual(partsToWrite[i].data);
+ }
+
+ expect(incompletePart).toBeUndefined();
+ });
+
+ it('should return an incomplete part if data is not fully available', () => {
+ const buffer = new CompositeBuffer();
+ const writer = new UmpWriter(buffer);
+
+ const partType = 5;
+ const partData = new Uint8Array(100);
+ writer.write(partType, partData);
+
+ const headerSize = 2;
+ const partialBuffer = buffer.split(headerSize + 50).extractedBuffer;
+
+ const reader = new UmpReader(partialBuffer);
+ const handlePart = vi.fn();
+
+ const incompletePart = reader.read(handlePart);
+
+ expect(handlePart).not.toHaveBeenCalled();
+ expect(incompletePart).toBeDefined();
+ expect(incompletePart?.type).toBe(partType);
+ expect(incompletePart?.size).toBe(partData.length);
+ });
+
+ it('should return undefined if the buffer is empty', () => {
+ const buffer = new CompositeBuffer();
+ const reader = new UmpReader(buffer);
+ const handlePart = vi.fn();
+
+ const incompletePart = reader.read(handlePart);
+
+ expect(handlePart).not.toHaveBeenCalled();
+ expect(incompletePart).toBeUndefined();
+ });
+
+ it('should return undefined if part header is incomplete', () => {
+ const buffer = new CompositeBuffer();
+ buffer.append(new Uint8Array([ 0x96 ]));
+
+ const reader = new UmpReader(buffer);
+ const handlePart = vi.fn();
+
+ const incompletePart = reader.read(handlePart);
+
+ expect(handlePart).not.toHaveBeenCalled();
+ expect(incompletePart).toBeUndefined();
+ });
+
+ it('should correctly read a part with a 5-byte VarInt size', () => {
+ const buffer = new CompositeBuffer();
+ const writer = new UmpWriter(buffer);
+
+ const partType = 15;
+ const partData = new Uint8Array(300000000);
+ writer.write(partType, partData);
+
+ const reader = new UmpReader(buffer);
+ const handlePart = vi.fn();
+
+ const incompletePart = reader.read(handlePart);
+
+ expect(handlePart).toHaveBeenCalledOnce();
+ const receivedPart = handlePart.mock.calls[0][0];
+
+ expect(receivedPart.type).toBe(partType);
+ expect(receivedPart.size).toBe(partData.length);
+ expect(incompletePart).toBeUndefined();
+ });
+
+ it('should handle reading from multiple chunks', () => {
+ const buffer = new CompositeBuffer();
+ const writer = new UmpWriter(buffer);
+
+ const partType = 1;
+ const partData = new Uint8Array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]);
+ writer.write(partType, partData);
+
+ // Re-create the buffer from multiple smaller chunks.
+ const chunk1 = new Uint8Array([ 1, 10, 1, 2, 3 ]); // type, size, data...
+ const chunk2 = new Uint8Array([ 4, 5, 6 ]); // ...data...
+ const chunk3 = new Uint8Array([ 7, 8, 9, 10 ]); // ...data
+ const chunkedBuffer = new CompositeBuffer([ chunk1, chunk2, chunk3 ]);
+
+ const reader = new UmpReader(chunkedBuffer);
+ const handlePart = vi.fn();
+
+ reader.read(handlePart);
+
+ expect(handlePart).toHaveBeenCalledOnce();
+ const receivedPart = handlePart.mock.calls[0][0];
+
+ expect(receivedPart.type).toBe(partType);
+ expect(receivedPart.size).toBe(partData.length);
+ expect(concatenateChunks(receivedPart.data.chunks)).toEqual(partData);
+ });
+});
\ No newline at end of file
diff --git a/tests/ump.writer.test.ts b/tests/ump.writer.test.ts
new file mode 100644
index 0000000..99c987f
--- /dev/null
+++ b/tests/ump.writer.test.ts
@@ -0,0 +1,126 @@
+import { describe, it, expect } from 'vitest';
+
+import { concatenateChunks } from '../src/utils/shared.js';
+import { CompositeBuffer, UmpWriter } from '../src/exports/ump.js';
+
+describe('UmpWriter', () => {
+ it('should write a small part correctly', () => {
+ const buffer = new CompositeBuffer();
+ const umpWriter = new UmpWriter(buffer);
+
+ const partData = new Uint8Array([ 10, 20, 30 ]);
+ umpWriter.write(1, partData);
+
+ const expected = new Uint8Array([
+ 1, // part type.
+ 3, // part size.
+ 10, 20, 30 // the data.
+ ]);
+
+ expect(concatenateChunks(buffer.chunks)).toEqual(expected);
+ });
+
+ it('should handle a part type that requires 2-byte VarInt', () => {
+ const buffer = new CompositeBuffer();
+ const umpWriter = new UmpWriter(buffer);
+
+ const partData = new Uint8Array([ 1, 2 ]);
+ umpWriter.write(150, partData);
+
+ const expected = new Uint8Array([
+ 0x96, 0x02,
+ 2,
+ 1, 2
+ ]);
+
+ expect(concatenateChunks(buffer.chunks)).toEqual(expected);
+ });
+
+ it('should handle a part size that requires 3-byte VarInt', () => {
+ const buffer = new CompositeBuffer();
+ const umpWriter = new UmpWriter(buffer);
+
+ const partData = new Uint8Array(20000);
+ umpWriter.write(5, partData);
+
+ const expectedHeader = new Uint8Array([
+ 5,
+ 0xC0, 0x71, 0x02
+ ]);
+
+ expect(concatenateChunks(buffer.split(4).extractedBuffer.chunks)).toEqual(expectedHeader);
+ expect(buffer.getLength()).toBe(4 + partData.length);
+ });
+
+ it('should handle a part size that requires 4-byte VarInt', () => {
+ const buffer = new CompositeBuffer();
+ const umpWriter = new UmpWriter(buffer);
+
+ const partData = new Uint8Array(10000000);
+ umpWriter.write(10, partData);
+
+ const expectedHeader = new Uint8Array([
+ 10,
+ 0xE0, 0x68, 0x89, 0x09
+ ]);
+
+ expect(concatenateChunks(buffer.split(5).extractedBuffer.chunks)).toEqual(expectedHeader);
+ expect(buffer.getLength()).toBe(5 + partData.length);
+ });
+
+ it('should handle a part size that requires 5-byte VarInt', () => {
+ const buffer = new CompositeBuffer();
+ const umpWriter = new UmpWriter(buffer);
+
+ const partData = new Uint8Array(300000000);
+ umpWriter.write(15, partData);
+
+ const expectedHeader = new Uint8Array([
+ 15,
+ 0xF0, 0x00, 0xA3, 0xE1, 0x11
+ ]);
+
+ expect(concatenateChunks(buffer.split(6).extractedBuffer.chunks)).toEqual(expectedHeader);
+ expect(buffer.getLength()).toBe(6 + partData.length);
+ });
+
+ it('should write multiple parts sequentially', () => {
+ const buffer = new CompositeBuffer();
+ const umpWriter = new UmpWriter(buffer);
+
+ const partType1 = 1;
+ const partData1 = new Uint8Array([ 1, 2 ]);
+ umpWriter.write(partType1, partData1);
+
+ const partType2 = 2;
+ const partData2 = new Uint8Array([ 3, 4, 5 ]);
+ umpWriter.write(partType2, partData2);
+
+ const partType3 = 3;
+ const partData3 = new Uint8Array([ 6, 7, 8, 9 ]);
+ umpWriter.write(partType3, partData3);
+
+ const expected = new Uint8Array([
+ // Part 1
+ 1,
+ 2,
+ 1, 2,
+ // Part 2
+ 2,
+ 3,
+ 3, 4, 5,
+ // Part 3
+ 3,
+ 4,
+ 6, 7, 8, 9
+ ]);
+
+ expect(concatenateChunks(buffer.chunks)).toEqual(expected);
+ });
+
+ it('should throw an error for negative VarInt value', () => {
+ const buffer = new CompositeBuffer();
+ const umpWriter = new UmpWriter(buffer);
+ expect(() => umpWriter.write(-1, new Uint8Array())).toThrow('VarInt value cannot be negative.');
+ });
+});
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
index ee56793..81c1375 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -42,7 +42,7 @@
"declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
- // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
+ "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
"outDir": "./dist", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
diff --git a/typedoc.json b/typedoc.json
new file mode 100644
index 0000000..c47ffdc
--- /dev/null
+++ b/typedoc.json
@@ -0,0 +1,17 @@
+{
+ "readme": "none",
+ "entryPoints": [
+ "src/exports/ump.ts",
+ "src/exports/sabr-streaming-adapter.ts",
+ "src/exports/sabr-stream.ts",
+ "src/exports/protos.ts",
+ "src/exports/utils.ts",
+ "src/types/shared.ts"
+ ],
+ "out": "docs/api",
+ "plugin": [
+ "typedoc-plugin-markdown"
+ ],
+ "hidePageHeader": true,
+ "tsconfig": "tsconfig.json"
+}
\ No newline at end of file