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 [![JSR](https://jsr.io/badges/@luanrt/googlevideo)](https://jsr.io/@luanrt/googlevideo) [![NPM](https://img.shields.io/npm/v/googlevideo)](https://www.npmjs.com/package/googlevideo) [![License](https://img.shields.io/github/license/LuanRT/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