streamify-audio 2.2.13 → 2.3.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.
@@ -11,6 +11,50 @@ try {
11
11
 
12
12
  const { createAudioResource, StreamType } = voiceModule || {};
13
13
 
14
+ class StreamURLCache {
15
+ constructor(defaultTTL = 1800000) {
16
+ this._cache = new Map();
17
+ this._defaultTTL = defaultTTL;
18
+ this._cleanupInterval = setInterval(() => this._cleanup(), 300000);
19
+ if (this._cleanupInterval.unref) this._cleanupInterval.unref();
20
+ }
21
+
22
+ get(key) {
23
+ const entry = this._cache.get(key);
24
+ if (!entry) return null;
25
+ if (Date.now() > entry.expiry) {
26
+ this._cache.delete(key);
27
+ return null;
28
+ }
29
+ return { url: entry.url, headers: entry.headers };
30
+ }
31
+
32
+ set(key, url, headers = null, ttl = this._defaultTTL) {
33
+ this._cache.set(key, {
34
+ url,
35
+ headers,
36
+ expiry: Date.now() + ttl
37
+ });
38
+ }
39
+
40
+ invalidate(key) {
41
+ this._cache.delete(key);
42
+ }
43
+
44
+ get size() {
45
+ return this._cache.size;
46
+ }
47
+
48
+ _cleanup() {
49
+ const now = Date.now();
50
+ for (const [key, entry] of this._cache) {
51
+ if (now > entry.expiry) this._cache.delete(key);
52
+ }
53
+ }
54
+ }
55
+
56
+ const urlCache = new StreamURLCache();
57
+
14
58
  class StreamController {
15
59
  constructor(track, filters, config) {
16
60
  this.track = track;
@@ -23,54 +67,120 @@ class StreamController {
23
67
  this.startTime = null;
24
68
  this.ytdlpError = '';
25
69
  this.ffmpegError = '';
26
-
27
- // Timing metrics
70
+
71
+ this._videoId = null;
72
+ this._directStreamUrl = null;
73
+ this._directStreamHeaders = null;
74
+ this._useDirectUrl = false;
75
+ this._prepared = false;
76
+ this._ffmpegSpawnTime = null;
77
+
28
78
  this.metrics = {
29
79
  metadata: 0,
80
+ urlExtract: 0,
30
81
  spawn: 0,
31
82
  firstByte: 0,
32
83
  total: 0
33
84
  };
34
85
  }
35
86
 
36
- async create(seekPosition = 0) {
37
- if (this.destroyed) {
38
- throw new Error('Stream already destroyed');
39
- }
40
-
41
- if (this.resource) {
42
- return this.resource;
43
- }
87
+ async prepare() {
88
+ if (this.destroyed) throw new Error('Stream already destroyed');
44
89
 
45
90
  this.startTime = Date.now();
46
- const startTimestamp = this.startTime;
47
91
  const source = this.track.source || 'youtube';
48
-
49
92
  let videoId = this.track._resolvedId || this.track.id;
50
93
 
51
- // Skip metadata resolution if we already have it
52
94
  if (source === 'spotify' && !this.track._resolvedId) {
53
95
  log.info('STREAM', `Resolving Spotify track to YouTube: ${this.track.title}`);
54
96
  try {
55
97
  const spotify = require('../providers/spotify');
56
98
  videoId = await spotify.resolveToYouTube(this.track.id, this.config);
57
99
  this.track._resolvedId = videoId;
58
- this.metrics.metadata = Date.now() - startTimestamp;
100
+ this.metrics.metadata = Date.now() - this.startTime;
59
101
  } catch (error) {
60
- log.error('STREAM', `Spotify resolution failed: ${error.message}`);
61
102
  throw new Error(`Failed to resolve Spotify track: ${error.message}`);
62
103
  }
63
104
  } else {
64
- this.metrics.metadata = 0; // Already resolved
105
+ this.metrics.metadata = 0;
65
106
  }
66
107
 
67
108
  if (!videoId || videoId === 'undefined') {
68
- throw new Error(`Invalid track ID: ${videoId} (source: ${source}, title: ${this.track.title})`);
109
+ throw new Error(`Invalid track ID: ${videoId} (source: ${source})`);
110
+ }
111
+
112
+ this._videoId = videoId;
113
+ const isYouTube = source === 'youtube' || source === 'spotify';
114
+ const isLive = this.track.isLive === true || this.track.duration === 0;
115
+ const isLocal = source === 'local';
116
+
117
+ if (isLocal || isLive || !isYouTube) {
118
+ this._useDirectUrl = false;
119
+ this._prepared = true;
120
+ return;
121
+ }
122
+
123
+ const cacheKey = `${videoId}:${this.config.ytdlp.format}`;
124
+ const cached = urlCache.get(cacheKey);
125
+
126
+ if (cached) {
127
+ log.info('STREAM', `URL cache hit for ${videoId}`);
128
+ this._directStreamUrl = cached.url;
129
+ this._directStreamHeaders = cached.headers;
130
+ this._useDirectUrl = true;
131
+ this._prepared = true;
132
+ return;
133
+ }
134
+
135
+ const extractStart = Date.now();
136
+ const maxRetries = this.config.stream?.maxRetries || 2;
137
+
138
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
139
+ try {
140
+ const extractedUrl = await this._extractUrl(videoId, this.config, isYouTube);
141
+ this.metrics.urlExtract = Date.now() - extractStart;
142
+
143
+ const ttl = (this.config.stream?.urlCacheTTL || 1800) * 1000;
144
+ urlCache.set(cacheKey, extractedUrl, this.track._headers, ttl);
145
+
146
+ this._directStreamUrl = extractedUrl;
147
+ this._directStreamHeaders = this.track._headers;
148
+ this._useDirectUrl = true;
149
+ this._prepared = true;
150
+
151
+ log.info('STREAM', `URL extracted for ${videoId} (${this.metrics.urlExtract}ms, attempt ${attempt})`);
152
+ return;
153
+ } catch (error) {
154
+ log.warn('STREAM', `URL extraction attempt ${attempt}/${maxRetries} failed: ${error.message}`);
155
+ if (attempt === maxRetries) {
156
+ log.warn('STREAM', `All extraction attempts failed for ${videoId}, falling back to pipe mode`);
157
+ this._useDirectUrl = false;
158
+ this._prepared = true;
159
+ return;
160
+ }
161
+ }
162
+ }
163
+ }
164
+
165
+ async start(seekPosition = 0) {
166
+ if (this.destroyed) throw new Error('Stream already destroyed');
167
+ if (this.resource) return this.resource;
168
+
169
+ if (!this._prepared) {
170
+ await this.prepare();
69
171
  }
70
172
 
71
- log.info('STREAM', `Creating stream for ${videoId} (${source})`);
72
-
73
- // Log Filter Chain Trace (No data impact)
173
+ if (!this.startTime) this.startTime = Date.now();
174
+ const startTimestamp = this.startTime;
175
+
176
+ const videoId = this._videoId || this.track._resolvedId || this.track.id;
177
+ const source = this.track.source || 'youtube';
178
+ const isLive = this.track.isLive === true || this.track.duration === 0;
179
+ const isLocal = source === 'local';
180
+ const isYouTube = source === 'youtube' || source === 'spotify';
181
+
182
+ log.info('STREAM', `Creating stream for ${videoId} (${source}, mode: ${this._useDirectUrl ? 'direct' : isLocal ? 'local' : 'pipe'})`);
183
+
74
184
  const filterNames = Object.keys(this.filters).filter(k => k !== 'start' && k !== '_trigger' && k !== 'volume');
75
185
  if (filterNames.length > 0) {
76
186
  const chain = filterNames.map(name => {
@@ -78,30 +188,14 @@ class StreamController {
78
188
  let displayVal = typeof val === 'object' ? JSON.stringify(val) : val;
79
189
  if (displayVal === true || displayVal === 'true') displayVal = 'ON';
80
190
  return `[${name.toUpperCase()} (${displayVal})]`;
81
- }).join(' ');
191
+ }).join(' > ');
82
192
  log.info('STREAM', `Filter Chain: ${chain}`);
83
193
  }
84
194
 
85
- const isYouTube = source === 'youtube' || source === 'spotify';
86
- const isLive = this.track.isLive === true || this.track.duration === 0;
87
- const isLocal = source === 'local';
88
-
89
- let ytdlp;
90
- let ffmpegIn;
91
-
92
195
  const env = { ...process.env, PATH: '/usr/local/bin:/root/.deno/bin:' + process.env.PATH };
93
- const spawnStart = Date.now();
196
+ const bufferSize = this.config.stream?.bufferSize || '1M';
94
197
 
95
- if (isLocal) {
96
- // Skip yt-dlp for local files
97
- ffmpegIn = 'pipe:0'; // We'll just pass the file path to ffmpeg -i
98
- this.metrics.spawn = Date.now() - spawnStart;
99
- } else if (this.track._directUrl && !isLive && seekPosition === 0) {
100
- // OPTIMIZATION: Bypass yt-dlp if we already have a direct stream URL
101
- ffmpegIn = this.track._directUrl;
102
- log.info('STREAM', `Bypassing yt-dlp, using direct URL for ${videoId}`);
103
- this.metrics.spawn = 0;
104
- } else {
198
+ if (!this._useDirectUrl && !isLocal) {
105
199
  let url;
106
200
  if (source === 'soundcloud') {
107
201
  url = this.track.uri || `https://api.soundcloud.com/tracks/${videoId}/stream`;
@@ -111,13 +205,7 @@ class StreamController {
111
205
  url = `https://www.youtube.com/watch?v=${videoId}`;
112
206
  }
113
207
 
114
- let formatString;
115
- if (isLive) {
116
- formatString = 'bestaudio*/best';
117
- } else {
118
- formatString = this.config.ytdlp.format;
119
- }
120
-
208
+ const formatString = isLive ? 'bestaudio*/best' : this.config.ytdlp.format;
121
209
  const ytdlpArgs = [
122
210
  '-f', formatString,
123
211
  '--no-playlist',
@@ -125,7 +213,7 @@ class StreamController {
125
213
  '--no-warnings',
126
214
  '--no-cache-dir',
127
215
  '--no-mtime',
128
- '--buffer-size', '16K',
216
+ '--buffer-size', bufferSize,
129
217
  '--quiet',
130
218
  '--retries', '3',
131
219
  '--fragment-retries', '3',
@@ -156,13 +244,11 @@ class StreamController {
156
244
  }
157
245
 
158
246
  this.ytdlp = spawn(this.config.ytdlpPath, ytdlpArgs, { env });
159
- ffmpegIn = 'pipe:0';
160
247
  }
161
248
 
162
249
  const ffmpegFilters = { ...this.filters };
163
250
  const ffmpegArgs = buildFfmpegArgs(ffmpegFilters, this.config);
164
-
165
- // Input injection - only override if NOT using pipe:0 (buildFfmpegArgs already includes -i pipe:0)
251
+
166
252
  if (isLocal) {
167
253
  const filePath = this.track.absolutePath || videoId.replace('file://', '');
168
254
  const pipeIndex = ffmpegArgs.indexOf('pipe:0');
@@ -173,23 +259,29 @@ class StreamController {
173
259
  ffmpegArgs.splice(pipeIndex - 1, 0, '-ss', seekSeconds);
174
260
  }
175
261
  }
176
- } else if (ffmpegIn !== 'pipe:0') {
177
- // Direct URL - replace the pipe:0 input with the direct URL
262
+ } else if (this._useDirectUrl) {
178
263
  const pipeIndex = ffmpegArgs.indexOf('pipe:0');
179
264
  if (pipeIndex > 0) {
180
- ffmpegArgs[pipeIndex] = ffmpegIn;
181
- if (this.track._headers) {
182
- const headers = Object.entries(this.track._headers)
265
+ if (seekPosition > 0) {
266
+ const seekSeconds = (seekPosition / 1000).toFixed(3);
267
+ ffmpegArgs.splice(pipeIndex - 1, 0, '-ss', seekSeconds);
268
+ }
269
+
270
+ const newPipeIndex = ffmpegArgs.indexOf('pipe:0');
271
+ ffmpegArgs[newPipeIndex] = this._directStreamUrl;
272
+
273
+ if (this._directStreamHeaders) {
274
+ const headers = Object.entries(this._directStreamHeaders)
183
275
  .map(([k, v]) => `${k}: ${v}`)
184
276
  .join('\r\n');
185
- ffmpegArgs.splice(pipeIndex - 1, 0, '-headers', headers);
277
+ ffmpegArgs.splice(newPipeIndex - 1, 0, '-headers', headers);
186
278
  }
187
279
  }
188
280
  }
189
- // else: pipe:0 - buildFfmpegArgs already has -i pipe:0, nothing to change
190
281
 
282
+ this._ffmpegSpawnTime = Date.now();
191
283
  this.ffmpeg = spawn(this.config.ffmpegPath, ffmpegArgs, { env });
192
- this.metrics.spawn = Date.now() - spawnStart;
284
+ this.metrics.spawn = Date.now() - this._ffmpegSpawnTime;
193
285
 
194
286
  if (this.ytdlp) {
195
287
  this.ffmpeg.stdin.on('error', (err) => {
@@ -212,8 +304,6 @@ class StreamController {
212
304
  this.ytdlpError += msg;
213
305
  if (msg.includes('ERROR:') && !msg.includes('Retrying') && !msg.includes('Broken pipe')) {
214
306
  log.error('YTDLP', msg.trim());
215
- } else if (!msg.includes('[download]') && !msg.includes('ETA') && !msg.includes('[youtube]') && !msg.includes('Retrying fragment') && !msg.includes('Got error')) {
216
- log.debug('YTDLP', msg.trim());
217
307
  }
218
308
  });
219
309
 
@@ -236,7 +326,17 @@ class StreamController {
236
326
  }
237
327
  });
238
328
 
239
- await this._waitForData(isLive);
329
+ try {
330
+ await this._waitForData();
331
+ } catch (error) {
332
+ if (this._useDirectUrl && (error.message.includes('timed out') || error.message.includes('403'))) {
333
+ const cacheKey = `${videoId}:${this.config.ytdlp.format}`;
334
+ urlCache.invalidate(cacheKey);
335
+ log.warn('STREAM', `Invalidated cached URL for ${videoId} after error`);
336
+ }
337
+ this.destroy();
338
+ throw error;
339
+ }
240
340
 
241
341
  if (this.destroyed || !this.ffmpeg) {
242
342
  throw new Error('Stream destroyed during initialization');
@@ -244,37 +344,104 @@ class StreamController {
244
344
 
245
345
  this.resource = createAudioResource(this.ffmpeg.stdout, {
246
346
  inputType: StreamType.OggOpus,
247
- inlineVolume: true,
248
- silencePaddingFrames: 10
347
+ inlineVolume: false,
348
+ silencePaddingFrames: 5
249
349
  });
250
350
 
251
351
  const elapsed = Date.now() - startTimestamp;
252
352
  this.metrics.total = elapsed;
253
-
254
- log.info('STREAM', `Ready ${elapsed}ms | Metrics: [Metadata: ${this.metrics.metadata}ms | Spawn: ${this.metrics.spawn}ms | FirstByte: ${this.metrics.firstByte}ms]`);
353
+
354
+ log.info('STREAM', `Ready ${elapsed}ms | Metrics: [Metadata: ${this.metrics.metadata}ms | URLExtract: ${this.metrics.urlExtract}ms | Spawn: ${this.metrics.spawn}ms | FirstByte: ${this.metrics.firstByte}ms]`);
255
355
 
256
356
  return this.resource;
257
357
  }
258
358
 
259
- _waitForData(isLive = false) {
359
+ async create(seekPosition = 0) {
360
+ return this.start(seekPosition);
361
+ }
362
+
363
+ _extractUrl(videoId, config, isYouTube) {
364
+ return new Promise((resolve, reject) => {
365
+ const url = `https://www.youtube.com/watch?v=${videoId}`;
366
+ const args = [
367
+ '--get-url',
368
+ '-f', config.ytdlp.format,
369
+ '--no-playlist',
370
+ '--no-check-certificates',
371
+ '--no-warnings',
372
+ '--no-cache-dir',
373
+ url
374
+ ];
375
+
376
+ if (isYouTube) {
377
+ args.push('--extractor-args', 'youtube:player_client=web_creator');
378
+ }
379
+
380
+ if (config.cookiesPath) {
381
+ args.unshift('--cookies', config.cookiesPath);
382
+ }
383
+
384
+ if (config.sponsorblock?.enabled !== false && isYouTube) {
385
+ const categories = config.sponsorblock?.categories || ['sponsor', 'selfpromo'];
386
+ args.push('--sponsorblock-remove', categories.join(','));
387
+ }
388
+
389
+ const env = { ...process.env, PATH: '/usr/local/bin:/root/.deno/bin:' + process.env.PATH };
390
+ const proc = spawn(config.ytdlpPath, args, { env });
391
+
392
+ let stdout = '';
393
+ let stderr = '';
394
+ const timeout = setTimeout(() => {
395
+ proc.kill('SIGKILL');
396
+ reject(new Error('URL extraction timed out'));
397
+ }, 15000);
398
+
399
+ proc.stdout.on('data', (data) => { stdout += data; });
400
+ proc.stderr.on('data', (data) => { stderr += data; });
401
+
402
+ proc.on('close', (code) => {
403
+ clearTimeout(timeout);
404
+ if (code !== 0) {
405
+ return reject(new Error(`yt-dlp --get-url failed (code ${code}): ${stderr.slice(-200)}`));
406
+ }
407
+ const urls = stdout.trim().split('\n').filter(Boolean);
408
+ if (urls.length === 0) {
409
+ return reject(new Error('yt-dlp returned no URLs'));
410
+ }
411
+ resolve(urls[0]);
412
+ });
413
+
414
+ proc.on('error', (err) => {
415
+ clearTimeout(timeout);
416
+ reject(new Error(`yt-dlp spawn failed: ${err.message}`));
417
+ });
418
+ });
419
+ }
420
+
421
+ _waitForData() {
260
422
  const ffmpeg = this.ffmpeg;
261
423
  const ytdlp = this.ytdlp;
424
+ const isLive = this.track.isLive === true || this.track.duration === 0;
425
+ const timeoutMs = isLive
426
+ ? (this.config.stream?.liveDataTimeout || 15000)
427
+ : (this.config.stream?.dataTimeout || 8000);
262
428
 
263
429
  return new Promise((resolve, reject) => {
264
430
  if (!ffmpeg) return resolve();
265
431
 
266
- const timeoutMs = isLive ? 30000 : 15000;
432
+ let resolved = false;
433
+
267
434
  const timeout = setTimeout(() => {
268
- log.warn('STREAM', `Timeout waiting for data, proceeding anyway`);
269
- resolve();
435
+ if (resolved) return;
436
+ resolved = true;
437
+ if (ffmpeg.stdout) ffmpeg.stdout.removeListener('readable', onReadable);
438
+ reject(new Error(`Stream timed out after ${timeoutMs}ms waiting for audio data`));
270
439
  }, timeoutMs);
271
440
 
272
- let resolved = false;
273
-
274
441
  const onReadable = () => {
275
442
  if (resolved) return;
276
443
  resolved = true;
277
- this.metrics.firstByte = Date.now() - (this.startTime + this.metrics.metadata + this.metrics.spawn);
444
+ this.metrics.firstByte = Date.now() - this._ffmpegSpawnTime;
278
445
  clearTimeout(timeout);
279
446
  if (ffmpeg.stdout) ffmpeg.stdout.removeListener('readable', onReadable);
280
447
  resolve();
@@ -284,19 +451,20 @@ class StreamController {
284
451
  ffmpeg.stdout.on('readable', onReadable);
285
452
  }
286
453
 
287
- ffmpeg.on('close', () => {
288
- if (!resolved) {
289
- resolved = true;
290
- clearTimeout(timeout);
291
- if (ffmpeg.stdout) ffmpeg.stdout.removeListener('readable', onReadable);
292
-
293
- if (this.destroyed) {
294
- return reject(new Error('Stream destroyed during initialization'));
295
- }
454
+ ffmpeg.on('close', (code) => {
455
+ if (resolved) return;
456
+ resolved = true;
457
+ clearTimeout(timeout);
458
+ if (ffmpeg.stdout) ffmpeg.stdout.removeListener('readable', onReadable);
296
459
 
297
- const sourceErr = ytdlp ? `yt-dlp stderr: ${this.ytdlpError.slice(-200) || 'none'}` : `ffmpeg stderr: ${this.ffmpegError.slice(-200) || 'none'}`;
298
- reject(new Error(`ffmpeg closed before producing data. ${sourceErr}`));
460
+ if (this.destroyed) {
461
+ return reject(new Error('Stream destroyed during initialization'));
299
462
  }
463
+
464
+ const sourceErr = ytdlp
465
+ ? `yt-dlp stderr: ${this.ytdlpError.slice(-200) || 'none'}`
466
+ : `ffmpeg stderr: ${this.ffmpegError.slice(-200) || 'none'}`;
467
+ reject(new Error(`ffmpeg closed before producing data. ${sourceErr}`));
300
468
  });
301
469
 
302
470
  if (ytdlp) {
@@ -305,12 +473,10 @@ class StreamController {
305
473
  resolved = true;
306
474
  clearTimeout(timeout);
307
475
  if (ffmpeg.stdout) ffmpeg.stdout.removeListener('readable', onReadable);
308
-
309
476
  if (this.destroyed) {
310
477
  return reject(new Error('Stream destroyed during initialization'));
311
478
  }
312
-
313
- reject(new Error(`yt-dlp failed with code ${code}`));
479
+ reject(new Error(`yt-dlp failed with code ${code}: ${this.ytdlpError.slice(-200)}`));
314
480
  }
315
481
  });
316
482
  }
@@ -355,4 +521,4 @@ function createStream(track, filters, config) {
355
521
  return new StreamController(track, filters, config);
356
522
  }
357
523
 
358
- module.exports = { createStream, StreamController };
524
+ module.exports = { createStream, StreamController, urlCache };
@@ -69,7 +69,7 @@ function buildEqualizer(bands) {
69
69
  function buildFfmpegArgs(filters = {}, config = {}) {
70
70
  filters = filters || {};
71
71
  const args = [
72
- '-thread_queue_size', '512',
72
+ '-thread_queue_size', '4096',
73
73
  '-i', 'pipe:0',
74
74
  '-vn',
75
75
  '-sn'
@@ -260,8 +260,9 @@ function buildFfmpegArgs(filters = {}, config = {}) {
260
260
  '-acodec', 'libopus',
261
261
  '-b:a', bitrate,
262
262
  '-vbr', config.audio?.vbr !== false ? 'on' : 'off',
263
- '-compression_level', (config.audio?.compressionLevel ?? 10).toString(),
264
- '-application', config.audio?.application || 'audio',
263
+ '-compression_level', (config.audio?.compressionLevel ?? 5).toString(),
264
+ '-frame_duration', '20',
265
+ '-application', config.audio?.application || 'lowdelay',
265
266
  '-f', 'ogg'
266
267
  );
267
268
  } else if (format === 'mp3') {
@@ -0,0 +1,26 @@
1
+ # Netscape HTTP Cookie File
2
+ # This file is generated by yt-dlp. Do not edit.
3
+
4
+ .youtube.com TRUE / TRUE 1800398983 __Secure-YENID 12.YTE=UfQjguVvlx3WLiJJHSjg4nPS-N-5O3Xj8RZj-wlLmRdjAvaF3TRdMzSmyUmo82Z4P-tggpd89U5d3hMDN886ZuUZUzi5XdEFEmHjL0YrTPBwPhD6pZx5CJ-3bNb3Eb_7eZuq_YlqQb0OX6Hvu2yYtWfWZPq7dZUjnJarOqJB0UaWu4jlsn27LkAmA9ecZK7Tj0NXQsIiDMR_y8pHe-kOfQ9LhLBeMnUWvR7GXsSOo_NqyfHrNnu4W8YnBd45ZUQIhwQ91X7lhuTkbBj3hYNJsWv0G7yda1NfB8j-nh8ZkordnGPIyv5QlCTd745j2DiGeS1GSa2jP2bClx7SoQxTjQ
5
+ .youtube.com TRUE / FALSE 0 PREF f6=40000000&tz=UTC&hl=en
6
+ .youtube.com TRUE / TRUE 1782082203 NID 527=aqYd85605VNHKMN7IAs-QcA6YfpJm6LGDAjKm929dxg81_ly4EHuc_A0jCgct9sjP-yDkNobeQPSES7fdZYHdzmlnHO_gGQ-oVCIbS3GQvSB89DCDEPB-toK2K62rr6iUVoTg3lcUG5xBhVXwoPPOD6VWWqegOp-IOhJhTb7Ijp_899mbKpj-4Elx8vNwUL0X-lJAe5Th-4Mxv9lDynXDH8axGjFhMHboeuTOHiIXRg10-4BoqyhAGFDhKNohiMwDc66EraGHim03-ZM
7
+ .youtube.com TRUE / TRUE 1800826170 __Secure-1PSIDTS sidts-CjUB7I_69MoGgU9hYcmJtLk5dHx1d0LjE7gLaoN86V6V3M8eyIyUH6LOPPFj2llBVQAVsulbqRAA
8
+ .youtube.com TRUE / TRUE 1800826170 __Secure-3PSIDTS sidts-CjUB7I_69MoGgU9hYcmJtLk5dHx1d0LjE7gLaoN86V6V3M8eyIyUH6LOPPFj2llBVQAVsulbqRAA
9
+ .youtube.com TRUE / FALSE 1803850170 HSID Ab4swI6vjvKUnUkhu
10
+ .youtube.com TRUE / TRUE 1803850170 SSID AaxSz8h17yLe-BmmX
11
+ .youtube.com TRUE / FALSE 1803850170 APISID pQgk6uQUwAqRtThp/Ape45AAtgBDxnzVco
12
+ .youtube.com TRUE / TRUE 1803850170 SAPISID y_RgVkrBXATC2XrL/Avlu8CWS6dwZ7LTEg
13
+ .youtube.com TRUE / TRUE 1803850170 __Secure-1PAPISID y_RgVkrBXATC2XrL/Avlu8CWS6dwZ7LTEg
14
+ .youtube.com TRUE / TRUE 1803850170 __Secure-3PAPISID y_RgVkrBXATC2XrL/Avlu8CWS6dwZ7LTEg
15
+ .youtube.com TRUE / FALSE 1803850170 SID g.a0006AhyolP4RoeyrQXcHsFnRGkuVLVIZ9QQsgpwFaXGd822GTtb98Ev6KQIph7SQPbMD4aekgACgYKAaMSARMSFQHGX2MijLBjWKdOhQm5VmUkh9wlRhoVAUF8yKqE_WWJQO8PgiZpXVV1MiB30076
16
+ .youtube.com TRUE / TRUE 1803850170 __Secure-1PSID g.a0006AhyolP4RoeyrQXcHsFnRGkuVLVIZ9QQsgpwFaXGd822GTtb9khH10B1YSjuMht-R8LNjAACgYKAQ8SARMSFQHGX2MiWX5D8_L5MAZK1TLabPQMghoVAUF8yKoFUiQLoi8zHLuskryhcx1P0076
17
+ .youtube.com TRUE / TRUE 1803850170 __Secure-3PSID g.a0006AhyolP4RoeyrQXcHsFnRGkuVLVIZ9QQsgpwFaXGd822GTtbc5VtVrqvDy9fWzO4vguctgACgYKAUkSARMSFQHGX2MinKYgh3CtvTW5E5T8SwXiERoVAUF8yKqzIJTS4ajNX5uO8nh3r0j10076
18
+ .youtube.com TRUE / TRUE 1803850170 LOGIN_INFO AFmmF2swRQIgH6IrdLYFOhW8xu4opIUHTGxAf33LzI5EXw6pdoqsh_gCIQCxYHQL4VZEyb3nE-rhXM6VU2ua99G4AhzKl4JJlzZBEQ:QUQ3MjNmeFpJOS1rVDgtM2I4cm50Z3M4NUplLTlSbXV4OFl4MTM5eUtHX3FEcVNmT2ltVFlSWmpQdlR2QTVqR29mZlUtTVh4OEhkOGw4Rkd5d2tQZm52NXJmQzdrMFJPVk5uYTFLNFFMZWlGelJhRVk1TkVaMm9Uby0xWlcwR3ozYkwwY2lWT21pV2dpU0lsWW5saFhMektISWxwUkRUcXl3
19
+ .youtube.com TRUE / FALSE 1803305074 SIDCC AKEyXzW6XAqY9tNhDWlFzlv-TcDtmpxQxQjOpTwkeLq7ddP20Ecx5ttbRGa7K7WKJVXYBIsbGTE
20
+ .youtube.com TRUE / TRUE 1803305074 __Secure-1PSIDCC AKEyXzVdeiA5hPJOoWk_iVZXhBC3-3D4ZAlHjwkIdtnQ-y_iHxOeAdYucag2Dv-EMebAtINo-Q
21
+ .youtube.com TRUE / TRUE 1803305074 __Secure-3PSIDCC AKEyXzX9LV_1LP1Z1T6SCow5CKbPxteZg-XPIOt3o2Uu6OlsIn6Z2S3gRZBgUTHpaiRcVbPYbg
22
+ .youtube.com TRUE / TRUE 1787290794 __Secure-YNID 16.YT=uhIB3j65_j5F-nN_AIVu93J6J4tqe4fviGdBQuGk0X5EW7VRESfm4qMadhbgM04D7x1YyYRXKI0UIHJjiRMWqsP5PmT_qrHoTUSn4MOLA-FPeX09ELODYdpGvps9F6B3EFC2jC4MXYiWVY6KGZRiEoYBh_KWkh4Suwgn38LgfqN0E4Xv3YAzCB390qeKtLVkaqlQDUIvhnqGBPrObW-DzawPn00gHQph8VM2Zrklq_BzRYVaZHKHs2WkKlHqLljxwOiNXcsLrIkLqGsCgKR_onVOnlWN2gd0zojNv0VyuwYgfB9GBOLzweTnTWBZmp9IPWRA39DH4zMFerdTND-Yfg
23
+ .youtube.com TRUE / TRUE 0 YSC -LHUdUOZvC4
24
+ .youtube.com TRUE / TRUE 1787321074 VISITOR_INFO1_LIVE AuEVGsoJz2Y
25
+ .youtube.com TRUE / TRUE 1787321074 VISITOR_PRIVACY_METADATA CgJVUxIEGgAgVQ%3D%3D
26
+ .youtube.com TRUE / TRUE 1787290794 __Secure-ROLLOUT_TOKEN CIbdoKq8kOipXhC__cjij6WSAxiZi8-rseySAw%3D%3D