From d683f123c04764ea1da90bb9443bb967712275c8 Mon Sep 17 00:00:00 2001 From: Luan Date: Mon, 23 Dec 2024 14:36:48 -0300 Subject: [PATCH] chore: fix SABR example and update protos + SABR now requires PoTokens. --- examples/downloader/ffmpeg-example.ts | 8 +- examples/downloader/main.ts | 13 +- examples/downloader/package-lock.json | 473 +++++++++++++++++- examples/downloader/package.json | 5 +- examples/downloader/utils.ts | 48 ++ examples/onesie-request/main.ts | 6 +- examples/onesie-request/package-lock.json | 19 +- examples/onesie-request/package.json | 2 +- .../video_streaming/client_abr_state.ts | 359 ++++++------- .../video_playback_abr_request.ts | 28 ++ protos/video_streaming/client_abr_state.proto | 38 +- .../video_playback_abr_request.proto | 1 + src/core/ServerAbrStream.ts | 19 +- src/utils/Protos.ts | 3 +- 14 files changed, 792 insertions(+), 230 deletions(-) create mode 100644 examples/downloader/utils.ts diff --git a/examples/downloader/ffmpeg-example.ts b/examples/downloader/ffmpeg-example.ts index 79240a5..d5a2e91 100644 --- a/examples/downloader/ffmpeg-example.ts +++ b/examples/downloader/ffmpeg-example.ts @@ -4,6 +4,7 @@ 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, @@ -13,6 +14,7 @@ const progressBars = new cliProgress.MultiBar({ const formatProgressBars = new Map(); 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(` @@ -21,6 +23,7 @@ console.info(` 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; @@ -59,6 +62,7 @@ if (!serverAbrStreamingUrl) const serverAbrStream = new GoogleVideo.ServerAbrStream({ fetch: innertube.session.http.fetch_function, + poToken: webPoTokenResult.poToken, serverAbrStreamingUrl, videoPlaybackUstreamerConfig: videoPlaybackUstreamerConfig, durationMs @@ -121,8 +125,8 @@ await serverAbrStream.init({ audioFormats: [ selectedAudioFormat ], videoFormats: [ selectedVideoFormat ], clientAbrState: { - startTimeMs: 0, - mediaType: 0 // 0 = BOTH, 1 = AUDIO (video-only is no longer supported by YouTube) + playerTimeMs: 0, + enabledTrackTypesBitfield: 0 // 0 = BOTH, 1 = AUDIO (video-only is no longer supported by YouTube) } }); diff --git a/examples/downloader/main.ts b/examples/downloader/main.ts index 75030f9..7105e55 100644 --- a/examples/downloader/main.ts +++ b/examples/downloader/main.ts @@ -3,6 +3,7 @@ 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'; const progressBars = new cliProgress.MultiBar({ stopOnComplete: true, @@ -12,6 +13,7 @@ const progressBars = new cliProgress.MultiBar({ const formatProgressBars = new Map(); 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(` @@ -20,6 +22,7 @@ console.info(` 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; @@ -66,12 +69,12 @@ const determineFileExtension = (mimeType: string) => { const getOutputStream = (isVideo: boolean, mimeType: string, formatId?: number) => { const type = isVideo ? 'video' : 'audio'; const extension = determineFileExtension(mimeType); - const stream = createWriteStream(`${sanitizedTitle}.${formatId}.${type}.${extension}`); - return stream; + return createWriteStream(`${sanitizedTitle}.${formatId}.${type}.${extension}`); }; const serverAbrStream = new GoogleVideo.ServerAbrStream({ fetch: innertube.session.http.fetch_function, + poToken: webPoTokenResult.poToken, serverAbrStreamingUrl, videoPlaybackUstreamerConfig: videoPlaybackUstreamerConfig, durationMs @@ -126,12 +129,12 @@ serverAbrStream.on('error', (error) => { console.error(error); }); -await serverAbrStream.init({ +await serverAbrStream.init({ audioFormats: [ selectedAudioFormat ], videoFormats: [ selectedVideoFormat ], clientAbrState: { - startTimeMs: 0, - mediaType: 0 // 0 = BOTH, 1 = AUDIO (video-only is no longer supported by YouTube) + playerTimeMs: 0, + enabledTrackTypesBitfield: 0 // 0 = BOTH, 1 = AUDIO (video-only is no longer supported by YouTube) } }); diff --git a/examples/downloader/package-lock.json b/examples/downloader/package-lock.json index 03dd022..59fc890 100644 --- a/examples/downloader/package-lock.json +++ b/examples/downloader/package-lock.json @@ -9,14 +9,17 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "bgutils-js": "^3.1.0", "cli-progress": "^3.12.0", "fluent-ffmpeg": "^2.1.3", + "jsdom": "^25.0.1", "shaka-player": "^4.11.2", - "youtubei.js": "^12.1.0" + "youtubei.js": "^12.2.0" }, "devDependencies": { "@types/cli-progress": "^3.11.6", "@types/fluent-ffmpeg": "^2.1.26", + "@types/jsdom": "^21.1.7", "typescript": "^5.6.2" } }, @@ -51,6 +54,18 @@ "@types/node": "*" } }, + "node_modules/@types/jsdom": { + "version": "21.1.7", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.7.tgz", + "integrity": "sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, "node_modules/@types/node": { "version": "22.5.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.5.tgz", @@ -60,10 +75,18 @@ "undici-types": "~6.19.2" } }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true, + "license": "MIT" + }, "node_modules/acorn": { "version": "8.14.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -71,6 +94,15 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -84,6 +116,21 @@ "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", "integrity": "sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ==" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/bgutils-js": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bgutils-js/-/bgutils-js-3.1.0.tgz", + "integrity": "sha512-2S80c/B4OQFubJLD5ddRRp74utrvjA70x9U0RsIVK7gJaDnaPrbw+bnXWxnEnc0euLznmO9jxOtTTC7FxGmv6w==", + "funding": [ + "https://github.com/sponsors/LuanRT" + ], + "license": "MIT" + }, "node_modules/cli-progress": { "version": "3.12.0", "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", @@ -95,6 +142,75 @@ "node": ">=4" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cssstyle": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.1.0.tgz", + "integrity": "sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA==", + "license": "MIT", + "dependencies": { + "rrweb-cssom": "^0.7.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/eme-encryption-scheme-polyfill": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/eme-encryption-scheme-polyfill/-/eme-encryption-scheme-polyfill-2.1.5.tgz", @@ -105,6 +221,18 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/fluent-ffmpeg": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.3.tgz", @@ -117,6 +245,70 @@ "node": ">=18" } }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -125,22 +317,147 @@ "node": ">=8" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "node_modules/jintr": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jintr/-/jintr-3.1.0.tgz", - "integrity": "sha512-azhCHApkRfBH8INpiUCwKBYaNCdB5G+x3NApsI2MxQXSlgFAx7rap3YwE3JAkN08GO8f3ilZsGB0Yvc+412ntQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jintr/-/jintr-3.2.0.tgz", + "integrity": "sha512-psD1yf05kMKDNsUdW1l5YhO59pHScQ6OIHHb8W5SKSM2dCOFPsqolmIuSHgVA8+3Dc47NJR181CXZ4alCAPTkA==", "funding": [ "https://github.com/sponsors/LuanRT" ], + "license": "MIT", "dependencies": { "acorn": "^8.8.0" } }, + "node_modules/jsdom": { + "version": "25.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz", + "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==", + "license": "MIT", + "dependencies": { + "cssstyle": "^4.1.0", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.12", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.7.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^2.11.2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nwsapi": { + "version": "2.2.16", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.16.tgz", + "integrity": "sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==", + "license": "MIT" + }, + "node_modules/parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "license": "MIT", + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/shaka-player": { "version": "4.11.2", "resolved": "https://registry.npmjs.org/shaka-player/-/shaka-player-4.11.2.tgz", @@ -176,6 +493,54 @@ "node": ">=8" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "license": "MIT" + }, + "node_modules/tldts": { + "version": "6.1.69", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.69.tgz", + "integrity": "sha512-Oh/CqRQ1NXNY7cy9NkTPUauOWiTro0jEYZTioGbOmcQh6EC45oribyIMJp0OJO3677r13tO6SKdWoGZUx2BDFw==", + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.69" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.69", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.69.tgz", + "integrity": "sha512-nygxy9n2PBUFQUtAXAc122gGo+04/j5qr5TGQFZTHafTKYvmARVXt2cA5rgero2/dnXUfkdPtiJoKmrd3T+wdA==", + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz", + "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==", + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/tslib": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", @@ -211,6 +576,61 @@ "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "dev": true }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.0.tgz", + "integrity": "sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==", + "license": "MIT", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -222,16 +642,53 @@ "which": "bin/which" } }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT" + }, "node_modules/youtubei.js": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/youtubei.js/-/youtubei.js-12.1.0.tgz", - "integrity": "sha512-42SUw7zPpx4b7+XBm9QW+//T2/tixBRoEjJAbfOLmGCiyGZuvfW+oq/IngnXo2Keu+yD7YVAfYFc/NaFjFDxGg==", + "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==", "funding": [ "https://github.com/sponsors/LuanRT" ], + "license": "MIT", "dependencies": { "@bufbuild/protobuf": "^2.0.0", - "jintr": "^3.1.0", + "jintr": "^3.2.0", "tslib": "^2.5.0", "undici": "^5.19.1" } diff --git a/examples/downloader/package.json b/examples/downloader/package.json index 5650be0..2f7bc4a 100644 --- a/examples/downloader/package.json +++ b/examples/downloader/package.json @@ -12,14 +12,17 @@ "author": "", "license": "ISC", "dependencies": { + "bgutils-js": "^3.1.0", "cli-progress": "^3.12.0", "fluent-ffmpeg": "^2.1.3", + "jsdom": "^25.0.1", "shaka-player": "^4.11.2", - "youtubei.js": "^12.1.0" + "youtubei.js": "^12.2.0" }, "devDependencies": { "@types/cli-progress": "^3.11.6", "@types/fluent-ffmpeg": "^2.1.26", + "@types/jsdom": "^21.1.7", "typescript": "^5.6.2" } } diff --git a/examples/downloader/utils.ts b/examples/downloader/utils.ts new file mode 100644 index 0000000..5bd9c2c --- /dev/null +++ b/examples/downloader/utils.ts @@ -0,0 +1,48 @@ +import { BG, type BgConfig } from 'bgutils-js'; +import { JSDOM } from 'jsdom'; + +export async function generateWebPoToken(visitorData: string) { + const requestKey = 'O43z0dpjhgX20SCx4KAo'; + + if (!visitorData) + throw new Error('Could not get visitor data'); + + const dom = new JSDOM(); + + Object.assign(globalThis, { + window: dom.window, + document: dom.window.document + }); + + const bgConfig: BgConfig = { + fetch: (input: string | URL | globalThis.Request, init?: RequestInit) => fetch(input, init), + globalObj: globalThis, + identifier: visitorData, + requestKey + }; + + const bgChallenge = await BG.Challenge.create(bgConfig); + + if (!bgChallenge) + throw new Error('Could not get challenge'); + + const interpreterJavascript = bgChallenge.interpreterJavascript.privateDoNotAccessOrElseSafeScriptWrappedValue; + + if (interpreterJavascript) { + new Function(interpreterJavascript)(); + } else throw new Error('Could not load VM'); + + const poTokenResult = await BG.PoToken.generate({ + program: bgChallenge.program, + globalName: bgChallenge.globalName, + bgConfig + }); + + const placeholderPoToken = BG.PoToken.generatePlaceholder(visitorData); + + return { + visitorData, + placeholderPoToken, + poToken: poTokenResult.poToken, + }; +} \ No newline at end of file diff --git a/examples/onesie-request/main.ts b/examples/onesie-request/main.ts index 48a6c79..1a94acb 100644 --- a/examples/onesie-request/main.ts +++ b/examples/onesie-request/main.ts @@ -127,9 +127,9 @@ async function prepareOnesieRequest(args: OnesieRequestArgs): Promise = { if (message.lastManualDirection !== undefined && message.lastManualDirection !== 0) { writer.uint32(112).int32(message.lastManualDirection); } - if (message.quality !== undefined && message.quality !== 0) { - writer.uint32(128).int32(message.quality); + if (message.lastManualSelectedResolution !== undefined && message.lastManualSelectedResolution !== 0) { + writer.uint32(128).int32(message.lastManualSelectedResolution); } if (message.detailedNetworkType !== undefined && message.detailedNetworkType !== 0) { writer.uint32(136).int32(message.detailedNetworkType); } - if (message.maxWidth !== undefined && message.maxWidth !== 0) { - writer.uint32(144).int32(message.maxWidth); + if (message.clientViewportWidth !== undefined && message.clientViewportWidth !== 0) { + writer.uint32(144).int32(message.clientViewportWidth); } - if (message.maxHeight !== undefined && message.maxHeight !== 0) { - writer.uint32(152).int32(message.maxHeight); + if (message.clientViewportHeight !== undefined && message.clientViewportHeight !== 0) { + writer.uint32(152).int32(message.clientViewportHeight); } - if (message.selectedQualityHeight !== undefined && message.selectedQualityHeight !== 0) { - writer.uint32(168).int32(message.selectedQualityHeight); + if (message.clientBitrateCap !== undefined && message.clientBitrateCap !== 0) { + writer.uint32(160).int64(message.clientBitrateCap); } - if (message.r7 !== undefined && message.r7 !== 0) { - writer.uint32(184).int32(message.r7); + if (message.stickyResolution !== undefined && message.stickyResolution !== 0) { + writer.uint32(168).int32(message.stickyResolution); } - if (message.startTimeMs !== undefined && message.startTimeMs !== 0) { - writer.uint32(224).int64(message.startTimeMs); + if (message.clientViewportIsFlexible !== undefined && message.clientViewportIsFlexible !== false) { + writer.uint32(176).bool(message.clientViewportIsFlexible); + } + if (message.bandwidthEstimate !== undefined && message.bandwidthEstimate !== 0) { + writer.uint32(184).int32(message.bandwidthEstimate); + } + if (message.playerTimeMs !== undefined && message.playerTimeMs !== 0) { + writer.uint32(224).int64(message.playerTimeMs); } if (message.timeSinceLastSeek !== undefined && message.timeSinceLastSeek !== 0) { writer.uint32(232).int64(message.timeSinceLastSeek); } + if (message.dataSaverMode !== undefined && message.dataSaverMode !== false) { + writer.uint32(240).bool(message.dataSaverMode); + } if (message.visibility !== undefined && message.visibility !== 0) { writer.uint32(272).int32(message.visibility); } - if (message.timeSinceLastReq !== undefined && message.timeSinceLastReq !== 0) { - writer.uint32(288).int64(message.timeSinceLastReq); + if (message.playbackRate !== undefined && message.playbackRate !== 0) { + writer.uint32(285).float(message.playbackRate); + } + 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); } - if (message.timeSinceLastAction !== undefined && message.timeSinceLastAction !== 0) { - writer.uint32(312).int64(message.timeSinceLastAction); + if (message.enabledTrackTypesBitfield !== undefined && message.enabledTrackTypesBitfield !== 0) { + writer.uint32(320).int32(message.enabledTrackTypesBitfield); } - if (message.mediaType !== undefined && message.mediaType !== 0) { - writer.uint32(320).int32(message.mediaType); + if (message.maxPacingRate !== undefined && message.maxPacingRate !== 0) { + writer.uint32(344).int32(message.maxPacingRate); } if (message.playerState !== undefined && message.playerState !== 0) { writer.uint32(352).int64(message.playerState); } - if (message.rangeCompression !== undefined && message.rangeCompression !== false) { - writer.uint32(368).bool(message.rangeCompression); + 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); @@ -240,9 +214,6 @@ export const ClientAbrState: MessageFns = { ) { writer.uint32(544).int64(message.sabrForceMaxNetworkInterruptionDurationMs); } - if (message.playbackRate !== undefined && message.playbackRate !== 0) { - writer.uint32(2285).float(message.playbackRate); - } return writer; }, @@ -272,7 +243,7 @@ export const ClientAbrState: MessageFns = { break; } - message.quality = reader.int32(); + message.lastManualSelectedResolution = reader.int32(); continue; case 17: if (tag !== 136) { @@ -286,35 +257,49 @@ export const ClientAbrState: MessageFns = { break; } - message.maxWidth = reader.int32(); + message.clientViewportWidth = reader.int32(); continue; case 19: if (tag !== 152) { break; } - message.maxHeight = reader.int32(); + message.clientViewportHeight = reader.int32(); + continue; + case 20: + if (tag !== 160) { + break; + } + + message.clientBitrateCap = longToNumber(reader.int64()); continue; case 21: if (tag !== 168) { break; } - message.selectedQualityHeight = reader.int32(); + message.stickyResolution = reader.int32(); + continue; + case 22: + if (tag !== 176) { + break; + } + + message.clientViewportIsFlexible = reader.bool(); continue; case 23: if (tag !== 184) { break; } - message.r7 = reader.int32(); + message.bandwidthEstimate = reader.int32(); continue; case 28: if (tag !== 224) { break; } - message.startTimeMs = longToNumber(reader.int64()); + message.playerTimeMs = longToNumber(reader.int64()); continue; case 29: if (tag !== 232) { @@ -323,6 +308,13 @@ export const ClientAbrState: MessageFns = { message.timeSinceLastSeek = longToNumber(reader.int64()); continue; + case 30: + if (tag !== 240) { + break; + } + + message.dataSaverMode = reader.bool(); + continue; case 34: if (tag !== 272) { break; @@ -330,33 +322,47 @@ export const ClientAbrState: MessageFns = { message.visibility = reader.int32(); continue; + case 35: + if (tag !== 285) { + break; + } + + message.playbackRate = reader.float(); + continue; case 36: if (tag !== 288) { break; } - message.timeSinceLastReq = longToNumber(reader.int64()); + message.elapsedWallTimeMs = longToNumber(reader.int64()); continue; case 38: if (tag !== 306) { break; } - message.mediaCapabilities = MediaCapabilities.decode(reader, reader.uint32()); + message.mediaCapabilities = reader.bytes(); continue; case 39: if (tag !== 312) { break; } - message.timeSinceLastAction = longToNumber(reader.int64()); + message.timeSinceLastActionMs = longToNumber(reader.int64()); continue; case 40: if (tag !== 320) { break; } - message.mediaType = reader.int32() as any; + message.enabledTrackTypesBitfield = reader.int32(); + continue; + case 43: + if (tag !== 344) { + break; + } + + message.maxPacingRate = reader.int32(); continue; case 44: if (tag !== 352) { @@ -370,7 +376,7 @@ export const ClientAbrState: MessageFns = { break; } - message.rangeCompression = reader.bool(); + message.drcEnabled = reader.bool(); continue; case 48: if (tag !== 384) { @@ -484,13 +490,6 @@ export const ClientAbrState: MessageFns = { message.sabrForceMaxNetworkInterruptionDurationMs = longToNumber(reader.int64()); continue; - case 285: - if (tag !== 2285) { - break; - } - - message.playbackRate = reader.float(); - continue; } if ((tag & 7) === 4 || tag === 0) { break; @@ -506,23 +505,34 @@ export const ClientAbrState: MessageFns = { ? globalThis.Number(object.timeSinceLastManualFormatSelectionMs) : 0, lastManualDirection: isSet(object.lastManualDirection) ? globalThis.Number(object.lastManualDirection) : 0, - quality: isSet(object.quality) ? globalThis.Number(object.quality) : 0, + lastManualSelectedResolution: isSet(object.lastManualSelectedResolution) + ? globalThis.Number(object.lastManualSelectedResolution) + : 0, detailedNetworkType: isSet(object.detailedNetworkType) ? globalThis.Number(object.detailedNetworkType) : 0, - maxWidth: isSet(object.maxWidth) ? globalThis.Number(object.maxWidth) : 0, - maxHeight: isSet(object.maxHeight) ? globalThis.Number(object.maxHeight) : 0, - selectedQualityHeight: isSet(object.selectedQualityHeight) ? globalThis.Number(object.selectedQualityHeight) : 0, - r7: isSet(object.r7) ? globalThis.Number(object.r7) : 0, - startTimeMs: isSet(object.startTimeMs) ? globalThis.Number(object.startTimeMs) : 0, + clientViewportWidth: isSet(object.clientViewportWidth) ? globalThis.Number(object.clientViewportWidth) : 0, + clientViewportHeight: isSet(object.clientViewportHeight) ? globalThis.Number(object.clientViewportHeight) : 0, + clientBitrateCap: isSet(object.clientBitrateCap) ? globalThis.Number(object.clientBitrateCap) : 0, + stickyResolution: isSet(object.stickyResolution) ? globalThis.Number(object.stickyResolution) : 0, + clientViewportIsFlexible: isSet(object.clientViewportIsFlexible) + ? globalThis.Boolean(object.clientViewportIsFlexible) + : false, + bandwidthEstimate: isSet(object.bandwidthEstimate) ? globalThis.Number(object.bandwidthEstimate) : 0, + playerTimeMs: isSet(object.playerTimeMs) ? globalThis.Number(object.playerTimeMs) : 0, timeSinceLastSeek: isSet(object.timeSinceLastSeek) ? globalThis.Number(object.timeSinceLastSeek) : 0, + dataSaverMode: isSet(object.dataSaverMode) ? globalThis.Boolean(object.dataSaverMode) : false, visibility: isSet(object.visibility) ? globalThis.Number(object.visibility) : 0, - timeSinceLastReq: isSet(object.timeSinceLastReq) ? globalThis.Number(object.timeSinceLastReq) : 0, + playbackRate: isSet(object.playbackRate) ? globalThis.Number(object.playbackRate) : 0, + elapsedWallTimeMs: isSet(object.elapsedWallTimeMs) ? globalThis.Number(object.elapsedWallTimeMs) : 0, mediaCapabilities: isSet(object.mediaCapabilities) - ? MediaCapabilities.fromJSON(object.mediaCapabilities) - : undefined, - timeSinceLastAction: isSet(object.timeSinceLastAction) ? globalThis.Number(object.timeSinceLastAction) : 0, - mediaType: isSet(object.mediaType) ? clientAbrState_MediaTypeFromJSON(object.mediaType) : 0, + ? bytesFromBase64(object.mediaCapabilities) + : new Uint8Array(0), + timeSinceLastActionMs: isSet(object.timeSinceLastActionMs) ? globalThis.Number(object.timeSinceLastActionMs) : 0, + enabledTrackTypesBitfield: isSet(object.enabledTrackTypesBitfield) + ? globalThis.Number(object.enabledTrackTypesBitfield) + : 0, + maxPacingRate: isSet(object.maxPacingRate) ? globalThis.Number(object.maxPacingRate) : 0, playerState: isSet(object.playerState) ? globalThis.Number(object.playerState) : 0, - rangeCompression: isSet(object.rangeCompression) ? globalThis.Boolean(object.rangeCompression) : false, + drcEnabled: isSet(object.drcEnabled) ? globalThis.Boolean(object.drcEnabled) : false, Jda: isSet(object.Jda) ? globalThis.Number(object.Jda) : 0, qw: isSet(object.qw) ? globalThis.Number(object.qw) : 0, Ky: isSet(object.Ky) ? globalThis.Number(object.Ky) : 0, @@ -549,7 +559,6 @@ export const ClientAbrState: MessageFns = { sabrForceMaxNetworkInterruptionDurationMs: isSet(object.sabrForceMaxNetworkInterruptionDurationMs) ? globalThis.Number(object.sabrForceMaxNetworkInterruptionDurationMs) : 0, - playbackRate: isSet(object.playbackRate) ? globalThis.Number(object.playbackRate) : 0, }; }, @@ -563,50 +572,65 @@ export const ClientAbrState: MessageFns = { if (message.lastManualDirection !== undefined && message.lastManualDirection !== 0) { obj.lastManualDirection = Math.round(message.lastManualDirection); } - if (message.quality !== undefined && message.quality !== 0) { - obj.quality = Math.round(message.quality); + if (message.lastManualSelectedResolution !== undefined && message.lastManualSelectedResolution !== 0) { + obj.lastManualSelectedResolution = Math.round(message.lastManualSelectedResolution); } if (message.detailedNetworkType !== undefined && message.detailedNetworkType !== 0) { obj.detailedNetworkType = Math.round(message.detailedNetworkType); } - if (message.maxWidth !== undefined && message.maxWidth !== 0) { - obj.maxWidth = Math.round(message.maxWidth); + if (message.clientViewportWidth !== undefined && message.clientViewportWidth !== 0) { + obj.clientViewportWidth = Math.round(message.clientViewportWidth); + } + if (message.clientViewportHeight !== undefined && message.clientViewportHeight !== 0) { + obj.clientViewportHeight = Math.round(message.clientViewportHeight); } - if (message.maxHeight !== undefined && message.maxHeight !== 0) { - obj.maxHeight = Math.round(message.maxHeight); + if (message.clientBitrateCap !== undefined && message.clientBitrateCap !== 0) { + obj.clientBitrateCap = Math.round(message.clientBitrateCap); } - if (message.selectedQualityHeight !== undefined && message.selectedQualityHeight !== 0) { - obj.selectedQualityHeight = Math.round(message.selectedQualityHeight); + if (message.stickyResolution !== undefined && message.stickyResolution !== 0) { + obj.stickyResolution = Math.round(message.stickyResolution); } - if (message.r7 !== undefined && message.r7 !== 0) { - obj.r7 = Math.round(message.r7); + if (message.clientViewportIsFlexible !== undefined && message.clientViewportIsFlexible !== false) { + obj.clientViewportIsFlexible = message.clientViewportIsFlexible; } - if (message.startTimeMs !== undefined && message.startTimeMs !== 0) { - obj.startTimeMs = Math.round(message.startTimeMs); + if (message.bandwidthEstimate !== undefined && message.bandwidthEstimate !== 0) { + obj.bandwidthEstimate = Math.round(message.bandwidthEstimate); + } + if (message.playerTimeMs !== undefined && message.playerTimeMs !== 0) { + obj.playerTimeMs = Math.round(message.playerTimeMs); } if (message.timeSinceLastSeek !== undefined && message.timeSinceLastSeek !== 0) { obj.timeSinceLastSeek = Math.round(message.timeSinceLastSeek); } + if (message.dataSaverMode !== undefined && message.dataSaverMode !== false) { + obj.dataSaverMode = message.dataSaverMode; + } if (message.visibility !== undefined && message.visibility !== 0) { obj.visibility = Math.round(message.visibility); } - if (message.timeSinceLastReq !== undefined && message.timeSinceLastReq !== 0) { - obj.timeSinceLastReq = Math.round(message.timeSinceLastReq); + if (message.playbackRate !== undefined && message.playbackRate !== 0) { + obj.playbackRate = message.playbackRate; } - if (message.mediaCapabilities !== undefined) { - obj.mediaCapabilities = MediaCapabilities.toJSON(message.mediaCapabilities); + if (message.elapsedWallTimeMs !== undefined && message.elapsedWallTimeMs !== 0) { + obj.elapsedWallTimeMs = Math.round(message.elapsedWallTimeMs); } - if (message.timeSinceLastAction !== undefined && message.timeSinceLastAction !== 0) { - obj.timeSinceLastAction = Math.round(message.timeSinceLastAction); + if (message.mediaCapabilities !== undefined && message.mediaCapabilities.length !== 0) { + obj.mediaCapabilities = base64FromBytes(message.mediaCapabilities); } - if (message.mediaType !== undefined && message.mediaType !== 0) { - obj.mediaType = clientAbrState_MediaTypeToJSON(message.mediaType); + if (message.timeSinceLastActionMs !== undefined && message.timeSinceLastActionMs !== 0) { + obj.timeSinceLastActionMs = Math.round(message.timeSinceLastActionMs); + } + if (message.enabledTrackTypesBitfield !== undefined && message.enabledTrackTypesBitfield !== 0) { + obj.enabledTrackTypesBitfield = Math.round(message.enabledTrackTypesBitfield); + } + if (message.maxPacingRate !== undefined && message.maxPacingRate !== 0) { + obj.maxPacingRate = Math.round(message.maxPacingRate); } if (message.playerState !== undefined && message.playerState !== 0) { obj.playerState = Math.round(message.playerState); } - if (message.rangeCompression !== undefined && message.rangeCompression !== false) { - obj.rangeCompression = message.rangeCompression; + if (message.drcEnabled !== undefined && message.drcEnabled !== false) { + obj.drcEnabled = message.drcEnabled; } if (message.Jda !== undefined && message.Jda !== 0) { obj.Jda = Math.round(message.Jda); @@ -659,9 +683,6 @@ export const ClientAbrState: MessageFns = { ) { obj.sabrForceMaxNetworkInterruptionDurationMs = Math.round(message.sabrForceMaxNetworkInterruptionDurationMs); } - if (message.playbackRate !== undefined && message.playbackRate !== 0) { - obj.playbackRate = message.playbackRate; - } return obj; }, @@ -672,23 +693,26 @@ export const ClientAbrState: MessageFns = { const message = createBaseClientAbrState(); message.timeSinceLastManualFormatSelectionMs = object.timeSinceLastManualFormatSelectionMs ?? 0; message.lastManualDirection = object.lastManualDirection ?? 0; - message.quality = object.quality ?? 0; + message.lastManualSelectedResolution = object.lastManualSelectedResolution ?? 0; message.detailedNetworkType = object.detailedNetworkType ?? 0; - message.maxWidth = object.maxWidth ?? 0; - message.maxHeight = object.maxHeight ?? 0; - message.selectedQualityHeight = object.selectedQualityHeight ?? 0; - message.r7 = object.r7 ?? 0; - message.startTimeMs = object.startTimeMs ?? 0; + message.clientViewportWidth = object.clientViewportWidth ?? 0; + message.clientViewportHeight = object.clientViewportHeight ?? 0; + message.clientBitrateCap = object.clientBitrateCap ?? 0; + message.stickyResolution = object.stickyResolution ?? 0; + message.clientViewportIsFlexible = object.clientViewportIsFlexible ?? false; + message.bandwidthEstimate = object.bandwidthEstimate ?? 0; + message.playerTimeMs = object.playerTimeMs ?? 0; message.timeSinceLastSeek = object.timeSinceLastSeek ?? 0; + message.dataSaverMode = object.dataSaverMode ?? false; message.visibility = object.visibility ?? 0; - message.timeSinceLastReq = object.timeSinceLastReq ?? 0; - message.mediaCapabilities = (object.mediaCapabilities !== undefined && object.mediaCapabilities !== null) - ? MediaCapabilities.fromPartial(object.mediaCapabilities) - : undefined; - message.timeSinceLastAction = object.timeSinceLastAction ?? 0; - message.mediaType = object.mediaType ?? 0; + message.playbackRate = object.playbackRate ?? 0; + message.elapsedWallTimeMs = object.elapsedWallTimeMs ?? 0; + message.mediaCapabilities = object.mediaCapabilities ?? new Uint8Array(0); + message.timeSinceLastActionMs = object.timeSinceLastActionMs ?? 0; + message.enabledTrackTypesBitfield = object.enabledTrackTypesBitfield ?? 0; + message.maxPacingRate = object.maxPacingRate ?? 0; message.playerState = object.playerState ?? 0; - message.rangeCompression = object.rangeCompression ?? false; + message.drcEnabled = object.drcEnabled ?? false; message.Jda = object.Jda ?? 0; message.qw = object.qw ?? 0; message.Ky = object.Ky ?? 0; @@ -705,7 +729,6 @@ export const ClientAbrState: MessageFns = { message.sabrForceProxima = object.sabrForceProxima ?? 0; message.Tqb = object.Tqb ?? 0; message.sabrForceMaxNetworkInterruptionDurationMs = object.sabrForceMaxNetworkInterruptionDurationMs ?? 0; - message.playbackRate = object.playbackRate ?? 0; return message; }, }; diff --git a/protos/generated/video_streaming/video_playback_abr_request.ts b/protos/generated/video_streaming/video_playback_abr_request.ts index 223cffb..ab96d3d 100644 --- a/protos/generated/video_streaming/video_playback_abr_request.ts +++ b/protos/generated/video_streaming/video_playback_abr_request.ts @@ -17,6 +17,7 @@ export interface VideoPlaybackAbrRequest { clientAbrState?: ClientAbrState | undefined; selectedFormatIds: FormatId[]; bufferedRanges: BufferedRange[]; + playerTimeMs?: number | undefined; videoPlaybackUstreamerConfig?: Uint8Array | undefined; lo?: Lo | undefined; selectedAudioFormatIds: FormatId[]; @@ -62,6 +63,7 @@ function createBaseVideoPlaybackAbrRequest(): VideoPlaybackAbrRequest { clientAbrState: undefined, selectedFormatIds: [], bufferedRanges: [], + playerTimeMs: 0, videoPlaybackUstreamerConfig: new Uint8Array(0), lo: undefined, selectedAudioFormatIds: [], @@ -85,6 +87,9 @@ export const VideoPlaybackAbrRequest: MessageFns = { for (const v of message.bufferedRanges) { BufferedRange.encode(v!, writer.uint32(26).fork()).join(); } + if (message.playerTimeMs !== undefined && message.playerTimeMs !== 0) { + writer.uint32(32).int64(message.playerTimeMs); + } if (message.videoPlaybackUstreamerConfig !== undefined && message.videoPlaybackUstreamerConfig.length !== 0) { writer.uint32(42).bytes(message.videoPlaybackUstreamerConfig); } @@ -143,6 +148,13 @@ export const VideoPlaybackAbrRequest: MessageFns = { message.bufferedRanges.push(BufferedRange.decode(reader, reader.uint32())); continue; + case 4: + if (tag !== 32) { + break; + } + + message.playerTimeMs = longToNumber(reader.int64()); + continue; case 5: if (tag !== 42) { break; @@ -224,6 +236,7 @@ export const VideoPlaybackAbrRequest: MessageFns = { bufferedRanges: globalThis.Array.isArray(object?.bufferedRanges) ? object.bufferedRanges.map((e: any) => BufferedRange.fromJSON(e)) : [], + playerTimeMs: isSet(object.playerTimeMs) ? globalThis.Number(object.playerTimeMs) : 0, videoPlaybackUstreamerConfig: isSet(object.videoPlaybackUstreamerConfig) ? bytesFromBase64(object.videoPlaybackUstreamerConfig) : new Uint8Array(0), @@ -253,6 +266,9 @@ export const VideoPlaybackAbrRequest: MessageFns = { if (message.bufferedRanges?.length) { obj.bufferedRanges = message.bufferedRanges.map((e) => BufferedRange.toJSON(e)); } + if (message.playerTimeMs !== undefined && message.playerTimeMs !== 0) { + obj.playerTimeMs = Math.round(message.playerTimeMs); + } if (message.videoPlaybackUstreamerConfig !== undefined && message.videoPlaybackUstreamerConfig.length !== 0) { obj.videoPlaybackUstreamerConfig = base64FromBytes(message.videoPlaybackUstreamerConfig); } @@ -293,6 +309,7 @@ export const VideoPlaybackAbrRequest: MessageFns = { : undefined; message.selectedFormatIds = object.selectedFormatIds?.map((e) => FormatId.fromPartial(e)) || []; message.bufferedRanges = object.bufferedRanges?.map((e) => BufferedRange.fromPartial(e)) || []; + message.playerTimeMs = object.playerTimeMs ?? 0; message.videoPlaybackUstreamerConfig = object.videoPlaybackUstreamerConfig ?? new Uint8Array(0); message.lo = (object.lo !== undefined && object.lo !== null) ? Lo.fromPartial(object.lo) : undefined; message.selectedAudioFormatIds = object.selectedAudioFormatIds?.map((e) => FormatId.fromPartial(e)) || []; @@ -774,6 +791,17 @@ type KeysOfUnion = T extends T ? keyof T : never; export type Exact = P extends Builtin ? P : P & { [K in keyof P]: Exact } & { [K in Exclude>]: never }; +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; +} + function isSet(value: any): boolean { return value !== null && value !== undefined; } diff --git a/protos/video_streaming/client_abr_state.proto b/protos/video_streaming/client_abr_state.proto index 4f0868b..d64306a 100644 --- a/protos/video_streaming/client_abr_state.proto +++ b/protos/video_streaming/client_abr_state.proto @@ -1,27 +1,29 @@ syntax = "proto2"; package video_streaming; -import "video_streaming/media_capabilities.proto"; - message ClientAbrState { optional int32 time_since_last_manual_format_selection_ms = 13; optional int32 last_manual_direction = 14; - optional int32 quality = 16; + optional int32 last_manual_selected_resolution = 16; optional int32 detailed_network_type = 17; - optional int32 max_width = 18; - optional int32 max_height = 19; - optional int32 selected_quality_height = 21; - optional int32 r7 = 23; - optional int64 start_time_ms = 28; + optional int32 client_viewport_width = 18; + optional int32 client_viewport_height = 19; + optional int64 client_bitrate_cap = 20; + optional int32 sticky_resolution = 21; + optional bool client_viewport_is_flexible = 22; + optional int32 bandwidth_estimate = 23; + optional int64 player_time_ms = 28; optional int64 time_since_last_seek = 29; + optional bool data_saver_mode = 30; optional int32 visibility = 34; - optional int64 time_since_last_req = 36; - optional MediaCapabilities media_capabilities = 38; - optional int64 time_since_last_action = 39; - // optional int32 Gw = 40; - optional MediaType media_type = 40; + optional float playback_rate = 35; + optional int64 elapsed_wall_time_ms = 36; + optional bytes 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 range_compression = 46; + optional bool drc_enabled = 46; optional int32 Jda = 48; optional int32 qw = 50; optional int32 Ky = 51; @@ -38,12 +40,4 @@ message ClientAbrState { optional int32 sabr_force_proxima = 66; optional int32 Tqb = 67; optional int64 sabr_force_max_network_interruption_duration_ms = 68; - optional float playback_rate = 285; - - enum MediaType { - MEDIA_TYPE_DEFAULT = 0; - MEDIA_TYPE_AUDIO = 1; - MEDIA_TYPE_VIDEO = 2; - USE_SERVER_FORMAT_FILTER = 3; - } } \ 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 e24ac39..cf27004 100644 --- a/protos/video_streaming/video_playback_abr_request.proto +++ b/protos/video_streaming/video_playback_abr_request.proto @@ -10,6 +10,7 @@ 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 bytes video_playback_ustreamer_config = 5; optional Lo lo = 6; repeated .misc.FormatId selected_audio_format_ids = 16; diff --git a/src/core/ServerAbrStream.ts b/src/core/ServerAbrStream.ts index a7de081..a91835f 100644 --- a/src/core/ServerAbrStream.ts +++ b/src/core/ServerAbrStream.ts @@ -2,7 +2,6 @@ import { UMP } from './UMP.js'; import { ChunkedDataBuffer } from './ChunkedDataBuffer.js'; import { EventEmitterLike, PART, QUALITY, base64ToU8, getFormatKey } from '../utils/index.js'; -import { ClientAbrState_MediaType } from '../../protos/generated/video_streaming/client_abr_state.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'; @@ -65,11 +64,11 @@ export class ServerAbrStream extends EventEmitterLike { const clientAbrState: ClientAbrState = { lastManualDirection: 0, timeSinceLastManualFormatSelectionMs: 0, - quality: videoFormats.length === 1 ? firstVideoFormat?.height : DEFAULT_QUALITY, - selectedQualityHeight: videoFormats.length === 1 ? firstVideoFormat?.height : DEFAULT_QUALITY, - startTimeMs: 0, + lastManualSelectedResolution: videoFormats.length === 1 ? firstVideoFormat?.height : DEFAULT_QUALITY, + stickyResolution: videoFormats.length === 1 ? firstVideoFormat?.height : DEFAULT_QUALITY, + playerTimeMs: 0, visibility: 0, - mediaType: ClientAbrState_MediaType.MEDIA_TYPE_DEFAULT, + enabledTrackTypesBitfield: 0, ...initialState }; @@ -85,11 +84,11 @@ export class ServerAbrStream extends EventEmitterLike { xtags: fmt.xtags })); - if (typeof clientAbrState.startTimeMs !== 'number') + if (typeof clientAbrState.playerTimeMs !== 'number') throw new Error('Invalid media start time'); try { - while (clientAbrState.startTimeMs < this.totalDurationMs) { + while (clientAbrState.playerTimeMs < this.totalDurationMs) { const data = await this.fetchMedia({ clientAbrState, audioFormatIds, videoFormatIds }); this.emit('data', data); @@ -97,7 +96,7 @@ export class ServerAbrStream extends EventEmitterLike { if (data.sabrError) break; const mainFormat = - clientAbrState.mediaType === ClientAbrState_MediaType.MEDIA_TYPE_DEFAULT + clientAbrState.enabledTrackTypesBitfield === 0 ? data.initializedFormats.find((fmt) => fmt.mimeType?.includes('video')) : data.initializedFormats[0]; @@ -114,11 +113,11 @@ export class ServerAbrStream extends EventEmitterLike { break; } - clientAbrState.startTimeMs += mainFormat.sequenceList.reduce((acc, seq) => acc + (seq.durationMs || 0), 0); + clientAbrState.playerTimeMs += mainFormat.sequenceList.reduce((acc, seq) => acc + (seq.durationMs || 0), 0); } } catch (error) { this.emit('error', error); - clientAbrState.startTimeMs = Infinity; + clientAbrState.playerTimeMs = Infinity; } } diff --git a/src/utils/Protos.ts b/src/utils/Protos.ts index dd50645..bbe86b2 100644 --- a/src/utils/Protos.ts +++ b/src/utils/Protos.ts @@ -19,5 +19,4 @@ export { ClientAbrState } from '../../protos/generated/video_streaming/client_ab export { StreamerContext } from '../../protos/generated/video_streaming/streamer_context.js'; export { ProxyStatus } from '../../protos/generated/video_streaming/proxy_status.js'; export { MediaCapabilities } from '../../protos/generated/video_streaming/media_capabilities.js'; -export { CryptoParams } from '../../protos/generated/video_streaming/crypto_params.js'; -export { ClientAbrState_MediaType as MediaType } from '../../protos/generated/video_streaming/client_abr_state.js'; \ No newline at end of file +export { CryptoParams } from '../../protos/generated/video_streaming/crypto_params.js'; \ No newline at end of file