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