tokwatchr 0.4.3 → 0.4.4

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.d.mts CHANGED
@@ -237,6 +237,12 @@ declare class TikTokLiveDownloader {
237
237
  private resolveRoomIdOnce;
238
238
  private resolveRoomIdWithRetry;
239
239
  private fetchStreamInfo;
240
+ /**
241
+ * Poll for stream info until the stream becomes active.
242
+ * Used in waitForLive mode when the room exists but the stream
243
+ * has not yet started broadcasting.
244
+ */
245
+ private pollStreamInfo;
240
246
  /**
241
247
  * Download a single segment. Always downloads to .ts (crash-safe).
242
248
  * Does NOT remux — that is handled by remuxSegment() running in the background.
package/dist/index.mjs CHANGED
@@ -620,7 +620,7 @@ const DEFAULT_OPTIONS = {
620
620
  bitrate: null,
621
621
  maxDuration: Infinity,
622
622
  maxSegmentDuration: Infinity,
623
- checkInterval: 3e4,
623
+ checkInterval: 18e4,
624
624
  proxyUrl: null,
625
625
  cookieJar: null,
626
626
  browser: "chrome",
@@ -773,7 +773,7 @@ var TikTokLiveDownloader = class {
773
773
  if (!roomId && waitForLive) roomId = await this.resolveRoomIdWithRetry();
774
774
  if (!roomId) throw new UserOfflineError(this.username);
775
775
  this.setState("waiting");
776
- const firstInfo = await this.fetchStreamInfo(roomId);
776
+ const firstInfo = waitForLive ? await this.pollStreamInfo(roomId) : await this.fetchStreamInfo(roomId);
777
777
  if (!(this.options.maxSegmentDuration > 0 && this.options.maxSegmentDuration < Infinity)) {
778
778
  const tsResult = await this.downloadSegment(firstInfo, 1);
779
779
  const finalResult = await this.remuxSegment(tsResult);
@@ -834,7 +834,7 @@ var TikTokLiveDownloader = class {
834
834
  }
835
835
  async resolveRoomIdWithRetry() {
836
836
  const interval = this.options.checkInterval;
837
- const maxInterval = Math.max(interval, 3e4);
837
+ const maxInterval = Math.max(interval, 18e4);
838
838
  while (true) {
839
839
  this.abortController.signal?.throwIfAborted();
840
840
  const roomId = await this.resolveRoomIdOnce();
@@ -852,6 +852,27 @@ var TikTokLiveDownloader = class {
852
852
  return info;
853
853
  }
854
854
  /**
855
+ * Poll for stream info until the stream becomes active.
856
+ * Used in waitForLive mode when the room exists but the stream
857
+ * has not yet started broadcasting.
858
+ */
859
+ async pollStreamInfo(roomId) {
860
+ const interval = this.options.checkInterval;
861
+ const maxInterval = Math.max(interval, 18e4);
862
+ while (true) {
863
+ this.abortController.signal?.throwIfAborted();
864
+ try {
865
+ return await this.fetchStreamInfo(roomId);
866
+ } catch (err) {
867
+ if (err instanceof StreamFetchError) {
868
+ await sleep(maxInterval);
869
+ continue;
870
+ }
871
+ throw err;
872
+ }
873
+ }
874
+ }
875
+ /**
855
876
  * Download a single segment. Always downloads to .ts (crash-safe).
856
877
  * Does NOT remux — that is handled by remuxSegment() running in the background.
857
878
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokwatchr",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "description": "Download TikTok livestreams. Given a username, download the livestream.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.mjs",
@@ -9,7 +9,11 @@ import { resolveRoomId } from "./api/room.js";
9
9
  import { fetchStreamInfo } from "./api/stream.js";
10
10
  import { downloadWithFfmpeg } from "./download/ffmpeg.js";
11
11
  import { downloadRawHttp } from "./download/raw-http.js";
12
- import { UserNotFoundError, UserOfflineError } from "./errors.js";
12
+ import {
13
+ StreamFetchError,
14
+ UserNotFoundError,
15
+ UserOfflineError,
16
+ } from "./errors.js";
13
17
  import type {
14
18
  DownloaderState,
15
19
  DownloadResult,
@@ -32,7 +36,7 @@ const DEFAULT_OPTIONS: ResolvedOptions = {
32
36
  bitrate: null,
33
37
  maxDuration: Infinity,
34
38
  maxSegmentDuration: Infinity,
35
- checkInterval: 30_000,
39
+ checkInterval: 180_000,
36
40
  proxyUrl: null,
37
41
  cookieJar: null,
38
42
  browser: "chrome",
@@ -259,7 +263,9 @@ export class TikTokLiveDownloader {
259
263
 
260
264
  // Phase 2: Get stream info (refreshed per segment to avoid stale URLs)
261
265
  this.setState("waiting");
262
- const firstInfo = await this.fetchStreamInfo(roomId);
266
+ const firstInfo = waitForLive
267
+ ? await this.pollStreamInfo(roomId)
268
+ : await this.fetchStreamInfo(roomId);
263
269
 
264
270
  const segmentEnabled =
265
271
  this.options.maxSegmentDuration > 0 &&
@@ -361,7 +367,7 @@ export class TikTokLiveDownloader {
361
367
 
362
368
  private async resolveRoomIdWithRetry(): Promise<string> {
363
369
  const interval = this.options.checkInterval;
364
- const maxInterval = Math.max(interval, 30_000);
370
+ const maxInterval = Math.max(interval, 180_000);
365
371
 
366
372
  while (true) {
367
373
  this.abortController.signal?.throwIfAborted();
@@ -387,6 +393,30 @@ export class TikTokLiveDownloader {
387
393
  return info;
388
394
  }
389
395
 
396
+ /**
397
+ * Poll for stream info until the stream becomes active.
398
+ * Used in waitForLive mode when the room exists but the stream
399
+ * has not yet started broadcasting.
400
+ */
401
+ private async pollStreamInfo(roomId: string): Promise<StreamInfo> {
402
+ const interval = this.options.checkInterval;
403
+ const maxInterval = Math.max(interval, 180_000);
404
+
405
+ while (true) {
406
+ this.abortController.signal?.throwIfAborted();
407
+
408
+ try {
409
+ return await this.fetchStreamInfo(roomId);
410
+ } catch (err) {
411
+ if (err instanceof StreamFetchError) {
412
+ await sleep(maxInterval);
413
+ continue;
414
+ }
415
+ throw err;
416
+ }
417
+ }
418
+ }
419
+
390
420
  /**
391
421
  * Download a single segment. Always downloads to .ts (crash-safe).
392
422
  * Does NOT remux — that is handled by remuxSegment() running in the background.