@jburnhams/tube-ts 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,854 @@
1
+ 'use strict';
2
+
3
+ var shaka2 = require('shaka-player/dist/shaka-player.ui');
4
+ var web = require('youtubei.js/web');
5
+ var sabrStreamingAdapter = require('googlevideo/sabr-streaming-adapter');
6
+ var utils = require('googlevideo/utils');
7
+ var bgutilsJs = require('bgutils-js');
8
+ require('shaka-player/dist/controls.css');
9
+
10
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
+
12
+ var shaka2__default = /*#__PURE__*/_interopDefault(shaka2);
13
+
14
+ // src/TubePlayer.ts
15
+ function asMap(object) {
16
+ const map = /* @__PURE__ */ new Map();
17
+ for (const key of Object.keys(object)) {
18
+ map.set(key, object[key]);
19
+ }
20
+ return map;
21
+ }
22
+ function createRecoverableError(message, info) {
23
+ return new shaka2__default.default.util.Error(
24
+ shaka2__default.default.util.Error.Severity.RECOVERABLE,
25
+ shaka2__default.default.util.Error.Category.NETWORK,
26
+ shaka2__default.default.util.Error.Code.HTTP_ERROR,
27
+ message,
28
+ { info }
29
+ );
30
+ }
31
+ function headersToGenericObject(headers) {
32
+ const headersObj = {};
33
+ headers.forEach((value, key) => {
34
+ headersObj[key.trim()] = value;
35
+ });
36
+ return headersObj;
37
+ }
38
+ function makeResponse(headers, data, status, uri, responseURL, request, requestType) {
39
+ if (status >= 200 && status <= 299 && status !== 202) {
40
+ return {
41
+ uri: responseURL || uri,
42
+ originalUri: uri,
43
+ data,
44
+ status,
45
+ headers,
46
+ originalRequest: request,
47
+ fromCache: !!headers["x-shaka-from-cache"]
48
+ };
49
+ }
50
+ let responseText = null;
51
+ try {
52
+ responseText = shaka2__default.default.util.StringUtils.fromBytesAutoDetect(data);
53
+ } catch {
54
+ }
55
+ const severity = status === 401 || status === 403 ? shaka2__default.default.util.Error.Severity.CRITICAL : shaka2__default.default.util.Error.Severity.RECOVERABLE;
56
+ throw new shaka2__default.default.util.Error(
57
+ severity,
58
+ shaka2__default.default.util.Error.Category.NETWORK,
59
+ shaka2__default.default.util.Error.Code.BAD_HTTP_STATUS,
60
+ uri,
61
+ status,
62
+ responseText,
63
+ headers,
64
+ requestType,
65
+ responseURL || uri
66
+ );
67
+ }
68
+ async function fetchFunction(input, init) {
69
+ const url = input instanceof URL ? input : new URL(typeof input === "string" ? input : input.url);
70
+ const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : void 0));
71
+ if (url.pathname.includes("v1/player")) {
72
+ url.searchParams.set("$fields", "playerConfig,storyboards,captions,playabilityStatus,streamingData,responseContext.mainAppWebResponseContext.datasyncId,videoDetails.isLive,videoDetails.isLiveContent,videoDetails.title,videoDetails.author,videoDetails.thumbnail");
73
+ }
74
+ let skipProxy = false;
75
+ try {
76
+ if (typeof process !== "undefined" && process.env && process.env.SKIP_PROXY === "true") {
77
+ skipProxy = true;
78
+ }
79
+ } catch (e) {
80
+ }
81
+ if (skipProxy) {
82
+ const requestInit2 = {
83
+ ...init,
84
+ headers
85
+ };
86
+ if (input instanceof Request && !requestInit2.method) {
87
+ requestInit2.method = input.method;
88
+ }
89
+ return fetch(url.toString(), requestInit2);
90
+ }
91
+ const proxyUrl = new URL(url.pathname + url.search, "https://vps.jonathanburnhams.com/");
92
+ if (url.pathname.includes("/s/player/") || url.pathname.includes("/yts/jsbin/")) {
93
+ proxyUrl.searchParams.set("__host", "www.youtube.com");
94
+ const scriptPathIndex = url.pathname.indexOf("/s/player/");
95
+ if (scriptPathIndex > -1) {
96
+ proxyUrl.pathname = url.pathname.substring(scriptPathIndex);
97
+ } else {
98
+ const jsbinIndex = url.pathname.indexOf("/yts/jsbin/");
99
+ if (jsbinIndex > -1) {
100
+ proxyUrl.pathname = url.pathname.substring(jsbinIndex);
101
+ }
102
+ }
103
+ } else {
104
+ proxyUrl.searchParams.set("__host", url.host);
105
+ }
106
+ try {
107
+ let sessionId = null;
108
+ if (typeof process !== "undefined" && process.env && process.env.PROXY_SESSION_ID) {
109
+ sessionId = process.env.PROXY_SESSION_ID;
110
+ } else if (typeof window !== "undefined" && window.localStorage) {
111
+ sessionId = window.localStorage.getItem("tube-ts-session-id");
112
+ }
113
+ if (sessionId) {
114
+ proxyUrl.searchParams.set("session", sessionId);
115
+ }
116
+ } catch {
117
+ }
118
+ const headersObj = {};
119
+ headers.forEach((value, key) => {
120
+ headersObj[key] = value;
121
+ });
122
+ proxyUrl.searchParams.set("__headers", JSON.stringify(headersObj));
123
+ const requestInit = {
124
+ ...init,
125
+ headers
126
+ };
127
+ if (input instanceof Request) {
128
+ if (!requestInit.method) {
129
+ requestInit.method = input.method;
130
+ }
131
+ }
132
+ const response = await fetch(proxyUrl, requestInit);
133
+ const contentType = response.headers.get("content-type");
134
+ if (contentType && contentType.includes("text/html")) {
135
+ const text = await response.text();
136
+ throw new Error(`Proxy returned HTML (likely error page): ${response.status} ${response.statusText} - ${text.substring(0, 100)}`);
137
+ }
138
+ return response;
139
+ }
140
+
141
+ // src/ShakaPlayerAdapter.ts
142
+ var ShakaPlayerAdapter = class {
143
+ player = null;
144
+ requestMetadataManager;
145
+ cacheManager;
146
+ abortController;
147
+ requestFilter;
148
+ responseFilter;
149
+ initialize(player, requestMetadataManager, cacheManager) {
150
+ this.player = player;
151
+ this.requestMetadataManager = requestMetadataManager;
152
+ this.cacheManager = cacheManager;
153
+ const networkingEngine = shaka2__default.default.net.NetworkingEngine;
154
+ const schemes = ["http", "https"];
155
+ if (!shaka2__default.default.net.HttpFetchPlugin.isSupported())
156
+ throw new Error("The Fetch API is not supported in this browser.");
157
+ schemes.forEach((scheme) => {
158
+ networkingEngine.registerScheme(
159
+ scheme,
160
+ this.parseRequest.bind(this),
161
+ networkingEngine.PluginPriority.PREFERRED
162
+ );
163
+ });
164
+ }
165
+ parseRequest(uri, request, requestType, progressUpdated, headersReceived, config) {
166
+ const headers = new Headers();
167
+ asMap(request.headers).forEach((value, key) => {
168
+ headers.append(key, value);
169
+ });
170
+ const controller = new AbortController();
171
+ this.abortController = controller;
172
+ const init = {
173
+ body: request.body || void 0,
174
+ headers,
175
+ method: request.method,
176
+ signal: this.abortController.signal,
177
+ credentials: request.allowCrossSiteCredentials ? "include" : void 0
178
+ };
179
+ const abortStatus = { canceled: false, timedOut: false };
180
+ const minBytes = config.minBytesForProgressEvents || 0;
181
+ const pendingRequest = this.request(uri, request, requestType, init, controller, abortStatus, progressUpdated, headersReceived, minBytes);
182
+ const operation = new shaka2__default.default.util.AbortableOperation(
183
+ pendingRequest,
184
+ () => {
185
+ abortStatus.canceled = true;
186
+ controller.abort();
187
+ return Promise.resolve();
188
+ }
189
+ );
190
+ const timeoutMs = request.retryParameters.timeout;
191
+ if (timeoutMs) {
192
+ const timer = new shaka2__default.default.util.Timer(() => {
193
+ abortStatus.timedOut = true;
194
+ controller.abort();
195
+ console.warn("[ShakaPlayerAdapter]", "Request aborted due to timeout:", uri, requestType);
196
+ });
197
+ timer.tickAfter(timeoutMs / 1e3);
198
+ operation.finally(() => timer.stop());
199
+ }
200
+ return operation;
201
+ }
202
+ async handleCachedRequest(requestMetadata, uri, request, progressUpdated, headersReceived, requestType) {
203
+ if (!requestMetadata.byteRange || !this.cacheManager) {
204
+ return null;
205
+ }
206
+ const segmentKey = utils.FormatKeyUtils.createSegmentCacheKeyFromMetadata(requestMetadata);
207
+ let arrayBuffer = (requestMetadata.isInit ? this.cacheManager.getInitSegment(segmentKey) : this.cacheManager.getSegment(segmentKey))?.buffer;
208
+ if (!arrayBuffer) {
209
+ return null;
210
+ }
211
+ if (requestMetadata.isInit) {
212
+ arrayBuffer = arrayBuffer.slice(
213
+ requestMetadata.byteRange.start,
214
+ requestMetadata.byteRange.end + 1
215
+ );
216
+ }
217
+ const headers = {
218
+ "content-type": requestMetadata.format?.mimeType?.split(";")[0] || "",
219
+ "content-length": arrayBuffer.byteLength.toString(),
220
+ "x-shaka-from-cache": "true"
221
+ };
222
+ headersReceived(headers);
223
+ progressUpdated(0, arrayBuffer.byteLength, 0);
224
+ return makeResponse(headers, arrayBuffer, 200, uri, uri, request, requestType);
225
+ }
226
+ async handleUmpResponse(response, requestMetadata, uri, request, requestType, progressUpdated, abortController, minBytes) {
227
+ let lastTime = Date.now();
228
+ const sabrUmpReader = new sabrStreamingAdapter.SabrUmpProcessor(requestMetadata, this.cacheManager);
229
+ const checkResultIntegrity = (result) => {
230
+ if (!result.data && ((!!requestMetadata.error || requestMetadata.streamInfo?.streamProtectionStatus?.status === 3) && !requestMetadata.streamInfo?.sabrContextUpdate)) {
231
+ throw createRecoverableError("Server streaming error", requestMetadata);
232
+ }
233
+ };
234
+ const shouldReturnEmptyResponse = () => {
235
+ return requestMetadata.isSABR && (requestMetadata.streamInfo?.redirect || requestMetadata.streamInfo?.sabrContextUpdate);
236
+ };
237
+ if (!response.body) {
238
+ const arrayBuffer = await response.arrayBuffer();
239
+ const currentTime = Date.now();
240
+ progressUpdated(currentTime - lastTime, arrayBuffer.byteLength, 0);
241
+ const result = await sabrUmpReader.processChunk(new Uint8Array(arrayBuffer));
242
+ if (result) {
243
+ checkResultIntegrity(result);
244
+ return this.createShakaResponse({ uri, request, requestType, response, arrayBuffer: result.data });
245
+ }
246
+ if (shouldReturnEmptyResponse()) {
247
+ return this.createShakaResponse({ uri, request, requestType, response, arrayBuffer: void 0 });
248
+ }
249
+ throw createRecoverableError("Empty response with no redirect information", requestMetadata);
250
+ } else {
251
+ const reader = response.body.getReader();
252
+ let loaded = 0;
253
+ let lastLoaded = 0;
254
+ let contentLength;
255
+ while (!abortController.signal.aborted) {
256
+ let readObj;
257
+ try {
258
+ readObj = await reader.read();
259
+ } catch {
260
+ break;
261
+ }
262
+ const { value, done } = readObj;
263
+ if (done) {
264
+ if (shouldReturnEmptyResponse()) {
265
+ return this.createShakaResponse({ uri, request, requestType, response, arrayBuffer: void 0 });
266
+ }
267
+ throw createRecoverableError("Empty response with no redirect information", requestMetadata);
268
+ }
269
+ const result = await sabrUmpReader.processChunk(value);
270
+ const segmentInfo = sabrUmpReader.getSegmentInfo();
271
+ if (segmentInfo) {
272
+ if (!contentLength) {
273
+ contentLength = segmentInfo.mediaHeader.contentLength?.toString();
274
+ }
275
+ loaded += segmentInfo.lastChunkSize || 0;
276
+ segmentInfo.lastChunkSize = 0;
277
+ }
278
+ const currentTime = Date.now();
279
+ const chunkSize = loaded - lastLoaded;
280
+ if (currentTime - lastTime > 100 && chunkSize >= minBytes || result) {
281
+ if (result) checkResultIntegrity(result);
282
+ if (contentLength) {
283
+ const numBytesRemaining = result ? 0 : parseInt(contentLength) - loaded;
284
+ try {
285
+ progressUpdated(currentTime - lastTime, chunkSize, numBytesRemaining);
286
+ } catch {
287
+ } finally {
288
+ lastLoaded = loaded;
289
+ lastTime = currentTime;
290
+ }
291
+ }
292
+ }
293
+ if (result) {
294
+ abortController.abort();
295
+ return this.createShakaResponse({ uri, request, requestType, response, arrayBuffer: result.data });
296
+ }
297
+ }
298
+ throw createRecoverableError("UMP stream processing was aborted but did not produce a result.", requestMetadata);
299
+ }
300
+ }
301
+ async request(uri, request, requestType, init, abortController, abortStatus, progressUpdated, headersReceived, minBytes) {
302
+ try {
303
+ const requestMetadata = this.requestMetadataManager?.getRequestMetadata(uri);
304
+ if (requestMetadata) {
305
+ const cachedResponse = await this.handleCachedRequest(requestMetadata, uri, request, progressUpdated, headersReceived, requestType);
306
+ if (cachedResponse) {
307
+ return cachedResponse;
308
+ }
309
+ }
310
+ const fetchFn = fetchFunction;
311
+ const response = await fetchFn(uri, init);
312
+ headersReceived(headersToGenericObject(response.headers));
313
+ if (requestMetadata && init.method !== "HEAD" && response.headers.get("content-type") === "application/vnd.yt-ump") {
314
+ return this.handleUmpResponse(response, requestMetadata, uri, request, requestType, progressUpdated, abortController, minBytes);
315
+ }
316
+ const lastTime = Date.now();
317
+ const arrayBuffer = await response.arrayBuffer();
318
+ const currentTime = Date.now();
319
+ progressUpdated(currentTime - lastTime, arrayBuffer.byteLength, 0);
320
+ return this.createShakaResponse({
321
+ uri,
322
+ request,
323
+ requestType,
324
+ response,
325
+ arrayBuffer
326
+ });
327
+ } catch (error) {
328
+ if (abortStatus.canceled) {
329
+ throw new shaka2__default.default.util.Error(
330
+ shaka2__default.default.util.Error.Severity.RECOVERABLE,
331
+ shaka2__default.default.util.Error.Category.NETWORK,
332
+ shaka2__default.default.util.Error.Code.OPERATION_ABORTED,
333
+ uri,
334
+ requestType
335
+ );
336
+ } else if (abortStatus.timedOut) {
337
+ throw new shaka2__default.default.util.Error(
338
+ shaka2__default.default.util.Error.Severity.RECOVERABLE,
339
+ shaka2__default.default.util.Error.Category.NETWORK,
340
+ shaka2__default.default.util.Error.Code.TIMEOUT,
341
+ uri,
342
+ requestType
343
+ );
344
+ }
345
+ throw new shaka2__default.default.util.Error(
346
+ shaka2__default.default.util.Error.Severity.RECOVERABLE,
347
+ shaka2__default.default.util.Error.Category.NETWORK,
348
+ shaka2__default.default.util.Error.Code.HTTP_ERROR,
349
+ uri,
350
+ error,
351
+ requestType
352
+ );
353
+ }
354
+ }
355
+ checkPlayerStatus() {
356
+ if (!this.player) {
357
+ throw new Error("Player not initialized");
358
+ }
359
+ }
360
+ getPlayerTime() {
361
+ this.checkPlayerStatus();
362
+ return this.player.getMediaElement()?.currentTime || 0;
363
+ }
364
+ getPlaybackRate() {
365
+ this.checkPlayerStatus();
366
+ return this.player.getPlaybackRate();
367
+ }
368
+ getBandwidthEstimate() {
369
+ this.checkPlayerStatus();
370
+ return this.player.getStats().estimatedBandwidth;
371
+ }
372
+ getActiveTrackFormats(activeFormat, sabrFormats) {
373
+ this.checkPlayerStatus();
374
+ const activeVariant = this.player.getVariantTracks().find(
375
+ (track) => utils.FormatKeyUtils.getUniqueFormatId(activeFormat) === (activeFormat.width ? track.originalVideoId : track.originalAudioId)
376
+ );
377
+ if (!activeVariant) {
378
+ return { videoFormat: void 0, audioFormat: void 0 };
379
+ }
380
+ const formatMap = new Map(sabrFormats.map((format) => [utils.FormatKeyUtils.getUniqueFormatId(format), format]));
381
+ return {
382
+ videoFormat: activeVariant.originalVideoId ? formatMap.get(activeVariant.originalVideoId) : void 0,
383
+ audioFormat: activeVariant.originalAudioId ? formatMap.get(activeVariant.originalAudioId) : void 0
384
+ };
385
+ }
386
+ registerRequestInterceptor(interceptor) {
387
+ this.checkPlayerStatus();
388
+ const networkingEngine = this.player.getNetworkingEngine();
389
+ if (!networkingEngine)
390
+ return;
391
+ this.requestFilter = async (type, request, context) => {
392
+ if (type !== shaka2__default.default.net.NetworkingEngine.RequestType.SEGMENT || !utils.isGoogleVideoURL(request.uris[0])) return;
393
+ const modifiedRequest = await interceptor({
394
+ headers: request.headers,
395
+ url: request.uris[0],
396
+ method: request.method,
397
+ segment: {
398
+ getStartTime: () => context?.segment?.getStartTime() ?? null,
399
+ isInit: () => !context?.segment
400
+ },
401
+ body: request.body
402
+ });
403
+ if (modifiedRequest) {
404
+ request.uris = modifiedRequest.url ? [modifiedRequest.url] : request.uris;
405
+ request.method = modifiedRequest.method || request.method;
406
+ request.headers = modifiedRequest.headers || request.headers;
407
+ request.body = modifiedRequest.body || request.body;
408
+ }
409
+ };
410
+ networkingEngine.registerRequestFilter(this.requestFilter);
411
+ }
412
+ registerResponseInterceptor(interceptor) {
413
+ this.checkPlayerStatus();
414
+ const networkingEngine = this.player.getNetworkingEngine();
415
+ if (!networkingEngine) return;
416
+ this.responseFilter = async (type, response, context) => {
417
+ if (type !== shaka2__default.default.net.NetworkingEngine.RequestType.SEGMENT || !utils.isGoogleVideoURL(response.uri)) return;
418
+ const modifiedResponse = await interceptor({
419
+ url: response.originalRequest.uris[0],
420
+ method: response.originalRequest.method,
421
+ headers: response.headers,
422
+ data: response.data,
423
+ makeRequest: async (url, headers) => {
424
+ const retryParameters = this.player.getConfiguration().streaming.retryParameters;
425
+ const redirectRequest = shaka2__default.default.net.NetworkingEngine.makeRequest([url], retryParameters);
426
+ Object.assign(redirectRequest.headers, headers);
427
+ const requestOperation = networkingEngine.request(type, redirectRequest, context);
428
+ const redirectResponse = await requestOperation.promise;
429
+ return {
430
+ url: redirectResponse.uri,
431
+ method: redirectResponse.originalRequest.method,
432
+ headers: redirectResponse.headers,
433
+ data: redirectResponse.data
434
+ };
435
+ }
436
+ });
437
+ if (modifiedResponse) {
438
+ response.data = modifiedResponse.data ?? response.data;
439
+ Object.assign(response.headers, modifiedResponse.headers);
440
+ }
441
+ };
442
+ networkingEngine.registerResponseFilter(this.responseFilter);
443
+ }
444
+ createShakaResponse(args) {
445
+ return makeResponse(
446
+ headersToGenericObject(args.response.headers),
447
+ args.arrayBuffer || new ArrayBuffer(0),
448
+ args.response.status,
449
+ args.uri,
450
+ args.response.url,
451
+ args.request,
452
+ args.requestType
453
+ );
454
+ }
455
+ dispose() {
456
+ if (this.abortController) {
457
+ this.abortController.abort();
458
+ this.abortController = void 0;
459
+ }
460
+ if (this.player) {
461
+ const networkingEngine = this.player.getNetworkingEngine();
462
+ if (networkingEngine && this.requestFilter && this.responseFilter) {
463
+ networkingEngine.unregisterRequestFilter(this.requestFilter);
464
+ networkingEngine.unregisterResponseFilter(this.responseFilter);
465
+ }
466
+ shaka2__default.default.net.NetworkingEngine.unregisterScheme("http");
467
+ shaka2__default.default.net.NetworkingEngine.unregisterScheme("https");
468
+ this.player = null;
469
+ }
470
+ }
471
+ };
472
+ var BotguardService = class {
473
+ waaRequestKey = "O43z0dpjhgX20SCx4KAo";
474
+ botguardClient;
475
+ initializationPromise = null;
476
+ integrityTokenBasedMinter;
477
+ bgChallenge;
478
+ async init() {
479
+ if (this.initializationPromise) {
480
+ return await this.initializationPromise;
481
+ }
482
+ return this.setup();
483
+ }
484
+ async setup() {
485
+ if (this.initializationPromise)
486
+ return await this.initializationPromise;
487
+ this.initializationPromise = this._initBotguard();
488
+ try {
489
+ this.botguardClient = await this.initializationPromise;
490
+ return this.botguardClient;
491
+ } finally {
492
+ this.initializationPromise = null;
493
+ }
494
+ }
495
+ async _initBotguard() {
496
+ const challengeResponse = await fetchFunction(bgutilsJs.buildURL("Create", true), {
497
+ method: "POST",
498
+ headers: {
499
+ "content-type": "application/json+protobuf",
500
+ "x-goog-api-key": bgutilsJs.GOOG_API_KEY,
501
+ "x-user-agent": "grpc-web-javascript/0.1"
502
+ },
503
+ body: JSON.stringify([this.waaRequestKey])
504
+ });
505
+ const challengeResponseData = await challengeResponse.json();
506
+ this.bgChallenge = bgutilsJs.BG.Challenge.parseChallengeData(challengeResponseData);
507
+ if (!this.bgChallenge)
508
+ return;
509
+ const interpreterJavascript = this.bgChallenge.interpreterJavascript.privateDoNotAccessOrElseSafeScriptWrappedValue;
510
+ if (!interpreterJavascript) {
511
+ console.error("[BotguardService]", "Could not get interpreter javascript. Interpreter Hash:", this.bgChallenge.interpreterHash);
512
+ return;
513
+ }
514
+ if (!document.getElementById(this.bgChallenge.interpreterHash)) {
515
+ const script = document.createElement("script");
516
+ script.type = "text/javascript";
517
+ script.id = this.bgChallenge.interpreterHash;
518
+ script.textContent = interpreterJavascript;
519
+ document.head.appendChild(script);
520
+ }
521
+ this.botguardClient = await bgutilsJs.BG.BotGuardClient.create({
522
+ globalObj: globalThis,
523
+ globalName: this.bgChallenge.globalName,
524
+ program: this.bgChallenge.program
525
+ });
526
+ if (this.bgChallenge) {
527
+ const webPoSignalOutput = [];
528
+ const botguardResponse = await this.botguardClient.snapshot({ webPoSignalOutput });
529
+ const integrityTokenResponse = await fetchFunction(bgutilsJs.buildURL("GenerateIT", true), {
530
+ method: "POST",
531
+ headers: {
532
+ "content-type": "application/json+protobuf",
533
+ "x-goog-api-key": bgutilsJs.GOOG_API_KEY,
534
+ "x-user-agent": "grpc-web-javacript/0.1"
535
+ },
536
+ body: JSON.stringify([this.waaRequestKey, botguardResponse])
537
+ });
538
+ const integrityTokenResponseData = await integrityTokenResponse.json();
539
+ const integrityToken = integrityTokenResponseData[0];
540
+ if (!integrityToken) {
541
+ console.error("[BotguardService]", "Could not get integrity token. Interpreter Hash:", this.bgChallenge.interpreterHash);
542
+ return;
543
+ }
544
+ this.integrityTokenBasedMinter = await bgutilsJs.BG.WebPoMinter.create({ integrityToken }, webPoSignalOutput);
545
+ }
546
+ return this.botguardClient;
547
+ }
548
+ mintColdStartToken(contentBinding) {
549
+ return bgutilsJs.BG.PoToken.generateColdStartToken(contentBinding);
550
+ }
551
+ isInitialized() {
552
+ return !!this.botguardClient && !!this.integrityTokenBasedMinter;
553
+ }
554
+ dispose() {
555
+ if (this.botguardClient && this.bgChallenge) {
556
+ this.botguardClient.shutdown();
557
+ this.botguardClient = void 0;
558
+ this.integrityTokenBasedMinter = void 0;
559
+ const script = document.getElementById(this.bgChallenge.interpreterHash);
560
+ if (script) {
561
+ script.remove();
562
+ }
563
+ }
564
+ }
565
+ async reinit() {
566
+ if (this.initializationPromise)
567
+ return this.initializationPromise;
568
+ this.dispose();
569
+ return this.setup();
570
+ }
571
+ };
572
+ var botguardService = new BotguardService();
573
+ web.Platform.shim.eval = async (data, env) => {
574
+ const properties = [];
575
+ if (data.output && data.output.length < 1e3) {
576
+ console.log("[TubePlayer] Short code received:", data.output);
577
+ } else {
578
+ console.log(`[TubePlayer] Code received, length: ${data.output?.length}`);
579
+ }
580
+ if (env.n) {
581
+ if (data.exported?.includes("nFunction")) {
582
+ properties.push(`n: exportedVars.nFunction(${JSON.stringify(String(env.n))})`);
583
+ } else {
584
+ console.warn("[TubePlayer] nFunction not exported. Available exports:", data.exported);
585
+ throw new Error(`[TubePlayer] nFunction not exported. Available: ${data.exported?.join(", ")}`);
586
+ }
587
+ }
588
+ if (env.sig) {
589
+ if (data.exported?.includes("sigFunction")) {
590
+ properties.push(`sig: exportedVars.sigFunction(${JSON.stringify(String(env.sig))})`);
591
+ } else {
592
+ console.warn("[TubePlayer] sigFunction not exported, skipping sig transformation");
593
+ }
594
+ }
595
+ const code = `${data.output}
596
+ return { ${properties.join(", ")} }`;
597
+ try {
598
+ return new Function(code)();
599
+ } catch (e) {
600
+ console.error("[TubePlayer] Shim evaluation failed:", e);
601
+ console.error("[TubePlayer] Code preview:", code.substring(0, 200));
602
+ throw e;
603
+ }
604
+ };
605
+ var TubePlayer = class {
606
+ player;
607
+ ui;
608
+ sabrAdapter;
609
+ innertube;
610
+ playbackWebPoTokenContentBinding;
611
+ playbackWebPoTokenCreationLock = false;
612
+ playbackWebPoToken;
613
+ coldStartToken;
614
+ container;
615
+ videoElement;
616
+ constructor(containerId) {
617
+ const container = document.getElementById(containerId);
618
+ if (!container) throw new Error(`Container element with ID ${containerId} not found.`);
619
+ this.container = container;
620
+ this.videoElement = document.createElement("video");
621
+ this.videoElement.style.width = "100%";
622
+ this.videoElement.style.height = "100%";
623
+ this.videoElement.controls = false;
624
+ this.container.appendChild(this.videoElement);
625
+ shaka2__default.default.polyfill.installAll();
626
+ if (!shaka2__default.default.Player.isBrowserSupported()) {
627
+ console.warn("Shaka Player is not supported on this browser.");
628
+ }
629
+ this.player = new shaka2__default.default.Player();
630
+ this.ui = new shaka2__default.default.ui.Overlay(this.player, this.container, this.videoElement);
631
+ }
632
+ // Store initialization options for retry logic
633
+ initOptions;
634
+ async initialize(options) {
635
+ this.initOptions = options;
636
+ let retryCount = 0;
637
+ const maxRetries = 3;
638
+ const useProxy = options?.useProxy ?? true;
639
+ const enableCache = options?.cache ?? true;
640
+ while (retryCount < maxRetries) {
641
+ try {
642
+ const fetchWrapper = async (input, init) => {
643
+ let urlStr = typeof input === "string" ? input : input instanceof Request ? input.url : input.toString();
644
+ const urlObj = new URL(urlStr);
645
+ if (urlStr.includes("player") || urlStr.includes("base.js")) {
646
+ urlObj.searchParams.set("t", String(Date.now()));
647
+ console.log("[TubePlayer] Fetching player script from:", urlObj.toString());
648
+ let modifiedInit = init;
649
+ if (input instanceof Request) {
650
+ modifiedInit = {
651
+ method: input.method,
652
+ ...init
653
+ };
654
+ }
655
+ if (!useProxy) return fetch(urlObj.toString(), modifiedInit);
656
+ return fetchFunction(urlObj.toString(), modifiedInit);
657
+ }
658
+ if (!useProxy) {
659
+ return fetch(input, init);
660
+ }
661
+ return fetchFunction(input, init);
662
+ };
663
+ this.innertube = await web.Innertube.create({
664
+ // Create cache: persistent if enabled AND first try.
665
+ // If we are retrying internally here (retryCount > 0), we disable it.
666
+ // If enableCache is passed as false (from loadVideo retry), we disable it.
667
+ cache: new web.UniversalCache(enableCache && retryCount === 0),
668
+ fetch: fetchWrapper
669
+ });
670
+ break;
671
+ } catch (error) {
672
+ console.error("Innertube init failed", error);
673
+ retryCount++;
674
+ if (retryCount >= maxRetries) throw error;
675
+ console.log(`Retrying Innertube init (attempt ${retryCount + 1})...`);
676
+ }
677
+ }
678
+ await botguardService.init();
679
+ this.player.configure({
680
+ abr: { enabled: true },
681
+ streaming: {
682
+ bufferingGoal: 120,
683
+ rebufferingGoal: 2
684
+ }
685
+ });
686
+ await this.player.attach(this.videoElement);
687
+ this.ui.configure({
688
+ addBigPlayButton: false,
689
+ overflowMenuButtons: [
690
+ "captions",
691
+ "quality",
692
+ "language",
693
+ "chapter",
694
+ "picture_in_picture",
695
+ "playback_rate",
696
+ "loop",
697
+ "recenter_vr",
698
+ "toggle_stereoscopic",
699
+ "save_video_frame"
700
+ ],
701
+ customContextMenu: true
702
+ });
703
+ }
704
+ async loadVideo(videoId) {
705
+ if (!this.innertube) {
706
+ throw new Error("TubePlayer not initialized. Call initialize() first.");
707
+ }
708
+ if (!videoId) {
709
+ throw new Error("Please enter a video ID.");
710
+ }
711
+ this.playbackWebPoToken = void 0;
712
+ this.playbackWebPoTokenContentBinding = videoId;
713
+ try {
714
+ await this.player.unload();
715
+ if (this.sabrAdapter) {
716
+ this.sabrAdapter.dispose();
717
+ }
718
+ const playerResponse = await this.innertube.actions.execute("/player", {
719
+ videoId,
720
+ contentCheckOk: true,
721
+ racyCheckOk: true,
722
+ playbackContext: {
723
+ adPlaybackContext: {
724
+ pyv: true
725
+ },
726
+ contentPlaybackContext: {
727
+ signatureTimestamp: this.innertube.session.player?.signature_timestamp
728
+ }
729
+ }
730
+ });
731
+ const cpn = web.Utils.generateRandomString(16);
732
+ const videoInfo = new web.YT.VideoInfo([playerResponse], this.innertube.actions, cpn);
733
+ if (videoInfo.playability_status?.status !== "OK") {
734
+ throw new Error(`Cannot play video: ${videoInfo.playability_status?.reason}`);
735
+ }
736
+ const isLive = videoInfo.basic_info.is_live;
737
+ const isPostLiveDVR = !!videoInfo.basic_info.is_post_live_dvr || videoInfo.basic_info.is_live_content && !!(videoInfo.streaming_data?.dash_manifest_url || videoInfo.streaming_data?.hls_manifest_url);
738
+ this.sabrAdapter = new sabrStreamingAdapter.SabrStreamingAdapter({
739
+ playerAdapter: new ShakaPlayerAdapter(),
740
+ clientInfo: {
741
+ osName: this.innertube.session.context.client.osName,
742
+ osVersion: this.innertube.session.context.client.osVersion,
743
+ clientName: parseInt(web.Constants.CLIENT_NAME_IDS[this.innertube.session.context.client.clientName]),
744
+ clientVersion: this.innertube.session.context.client.clientVersion
745
+ }
746
+ });
747
+ this.sabrAdapter.onMintPoToken(async () => {
748
+ if (!this.playbackWebPoToken) {
749
+ if (isLive) {
750
+ await this.mintContentWebPO();
751
+ } else {
752
+ this.mintContentWebPO().then();
753
+ }
754
+ }
755
+ return this.playbackWebPoToken || this.coldStartToken || "";
756
+ });
757
+ this.sabrAdapter.onReloadPlayerResponse(async (reloadContext) => {
758
+ const reloadedInfo = await this.innertube.actions.execute("/player", {
759
+ videoId,
760
+ contentCheckOk: true,
761
+ racyCheckOk: true,
762
+ playbackContext: {
763
+ adPlaybackContext: {
764
+ pyv: true
765
+ },
766
+ contentPlaybackContext: {
767
+ signatureTimestamp: this.innertube.session.player?.signature_timestamp
768
+ },
769
+ reloadPlaybackContext: reloadContext
770
+ }
771
+ });
772
+ const parsedInfo = new web.YT.VideoInfo([reloadedInfo], this.innertube.actions, cpn);
773
+ this.sabrAdapter.setStreamingURL(await this.innertube.session.player.decipher(parsedInfo.streaming_data?.server_abr_streaming_url));
774
+ this.sabrAdapter.setUstreamerConfig(videoInfo.player_config?.media_common_config.media_ustreamer_request_config?.video_playback_ustreamer_config);
775
+ });
776
+ this.sabrAdapter.attach(this.player);
777
+ if (videoInfo.streaming_data && !isPostLiveDVR && !isLive) {
778
+ this.sabrAdapter.setStreamingURL(await this.innertube.session.player.decipher(videoInfo.streaming_data?.server_abr_streaming_url));
779
+ this.sabrAdapter.setUstreamerConfig(videoInfo.player_config?.media_common_config.media_ustreamer_request_config?.video_playback_ustreamer_config);
780
+ this.sabrAdapter.setServerAbrFormats(videoInfo.streaming_data.adaptive_formats.map(utils.buildSabrFormat));
781
+ }
782
+ let manifestUri;
783
+ if (videoInfo.streaming_data) {
784
+ if (isLive) {
785
+ manifestUri = videoInfo.streaming_data.dash_manifest_url ? `${videoInfo.streaming_data.dash_manifest_url}/mpd_version/7` : videoInfo.streaming_data.hls_manifest_url;
786
+ } else if (isPostLiveDVR) {
787
+ manifestUri = videoInfo.streaming_data.hls_manifest_url || `${videoInfo.streaming_data.dash_manifest_url}/mpd_version/7`;
788
+ } else {
789
+ manifestUri = `data:application/dash+xml;base64,${btoa(await videoInfo.toDash({
790
+ manifest_options: {
791
+ is_sabr: true,
792
+ captions_format: "vtt",
793
+ include_thumbnails: false
794
+ }
795
+ }))}`;
796
+ }
797
+ }
798
+ if (!manifestUri)
799
+ throw new Error("Could not find a valid manifest URI.");
800
+ await this.player.load(manifestUri);
801
+ return videoInfo.basic_info;
802
+ } catch (e) {
803
+ console.error("[TubePlayer]", "Error loading video:", e);
804
+ if (!this.isRetrying && this.initOptions) {
805
+ console.warn("[TubePlayer] Load failed. Retrying with cache disabled to fetch fresh player script...");
806
+ this.isRetrying = true;
807
+ try {
808
+ await this.initialize({ ...this.initOptions, cache: false });
809
+ return this.loadVideo(videoId);
810
+ } catch (retryError) {
811
+ console.error("[TubePlayer] Retry failed:", retryError);
812
+ } finally {
813
+ this.isRetrying = false;
814
+ }
815
+ }
816
+ throw e;
817
+ }
818
+ }
819
+ // Track retry state to prevent infinite loops
820
+ isRetrying = false;
821
+ async mintContentWebPO() {
822
+ if (!this.playbackWebPoTokenContentBinding || this.playbackWebPoTokenCreationLock) return;
823
+ this.playbackWebPoTokenCreationLock = true;
824
+ try {
825
+ this.coldStartToken = botguardService.mintColdStartToken(this.playbackWebPoTokenContentBinding);
826
+ if (!botguardService.isInitialized()) await botguardService.reinit();
827
+ if (botguardService.integrityTokenBasedMinter) {
828
+ this.playbackWebPoToken = await botguardService.integrityTokenBasedMinter.mintAsWebsafeString(decodeURIComponent(this.playbackWebPoTokenContentBinding));
829
+ }
830
+ } catch (err) {
831
+ console.error("[TubePlayer]", "Error minting WebPO token", err);
832
+ } finally {
833
+ this.playbackWebPoTokenCreationLock = false;
834
+ }
835
+ }
836
+ destroy() {
837
+ this.player.destroy();
838
+ this.sabrAdapter?.dispose();
839
+ botguardService.dispose();
840
+ this.ui.destroy();
841
+ this.videoElement.remove();
842
+ }
843
+ };
844
+
845
+ exports.ShakaPlayerAdapter = ShakaPlayerAdapter;
846
+ exports.TubePlayer = TubePlayer;
847
+ exports.asMap = asMap;
848
+ exports.botguardService = botguardService;
849
+ exports.createRecoverableError = createRecoverableError;
850
+ exports.fetchFunction = fetchFunction;
851
+ exports.headersToGenericObject = headersToGenericObject;
852
+ exports.makeResponse = makeResponse;
853
+ //# sourceMappingURL=index.cjs.map
854
+ //# sourceMappingURL=index.cjs.map