tokwatchr 0.4.4 → 0.5.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.d.mts +11 -1
- package/dist/index.mjs +17 -0
- package/package.json +1 -1
- package/src/TikTokLiveDownloader.ts +22 -0
- package/src/index.ts +1 -0
- package/src/types.ts +11 -0
package/dist/index.d.mts
CHANGED
|
@@ -88,7 +88,17 @@ interface CookieJarLike {
|
|
|
88
88
|
setCookie(raw: string, url: string): Promise<void>;
|
|
89
89
|
getCookieString(url: string): Promise<string>;
|
|
90
90
|
}
|
|
91
|
+
interface WaitingInfo {
|
|
92
|
+
/** The username being waited on. */
|
|
93
|
+
username: string;
|
|
94
|
+
/** Which phase we're waiting in: "room" = looking for a room, "stream" = stream not active. */
|
|
95
|
+
phase: "room" | "stream";
|
|
96
|
+
/** Seconds elapsed since this phase started. */
|
|
97
|
+
elapsed: number;
|
|
98
|
+
}
|
|
91
99
|
interface TikTokLiveDownloaderEvents {
|
|
100
|
+
/** Emitted periodically while waiting for a room or stream to become active. */
|
|
101
|
+
waiting: [info: WaitingInfo];
|
|
92
102
|
/** Emitted when a live stream is detected and we're about to start recording. */
|
|
93
103
|
start: [info: StreamInfo];
|
|
94
104
|
/** Emitted periodically (every ~1s) during recording with current stats. */
|
|
@@ -330,4 +340,4 @@ declare function renderFilename(template: string, values: {
|
|
|
330
340
|
part?: number;
|
|
331
341
|
}): string;
|
|
332
342
|
//#endregion
|
|
333
|
-
export { AbortError, type CookieJarLike, type CreateClientOptions, DownloadFailedError, type DownloadFunctionOptions, type DownloadResult, type DownloadStats, type DownloaderState, FfmpegError, type OutputFormat, type QualityOption, RoomResolveError, type RoomResolveOptions, StreamFetchError, type StreamInfo, type StreamInfoOptions, type StreamQualityKey, TikTokLiveDownloader, type TikTokLiveDownloaderEvents, type TikTokLiveDownloaderOptions, TikTokLiveError, UserNotFoundError, UserOfflineError, buildQualityOption, createClient, download, fetchStreamInfo, parseQualities, renderFilename, resolveRoomId, selectQuality };
|
|
343
|
+
export { AbortError, type CookieJarLike, type CreateClientOptions, DownloadFailedError, type DownloadFunctionOptions, type DownloadResult, type DownloadStats, type DownloaderState, FfmpegError, type OutputFormat, type QualityOption, RoomResolveError, type RoomResolveOptions, StreamFetchError, type StreamInfo, type StreamInfoOptions, type StreamQualityKey, TikTokLiveDownloader, type TikTokLiveDownloaderEvents, type TikTokLiveDownloaderOptions, TikTokLiveError, UserNotFoundError, UserOfflineError, type WaitingInfo, buildQualityOption, createClient, download, fetchStreamInfo, parseQualities, renderFilename, resolveRoomId, selectQuality };
|
package/dist/index.mjs
CHANGED
|
@@ -808,6 +808,11 @@ var TikTokLiveDownloader = class {
|
|
|
808
808
|
this.emit("complete", finalResults);
|
|
809
809
|
return finalResults[finalResults.length - 1];
|
|
810
810
|
} catch (err) {
|
|
811
|
+
if (err instanceof StreamFetchError) this.emit("waiting", {
|
|
812
|
+
username: this.username,
|
|
813
|
+
phase: "stream",
|
|
814
|
+
elapsed: 0
|
|
815
|
+
});
|
|
811
816
|
if (pendingRemuxes.length > 0) await Promise.allSettled(pendingRemuxes);
|
|
812
817
|
this.setState("done");
|
|
813
818
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
@@ -835,10 +840,16 @@ var TikTokLiveDownloader = class {
|
|
|
835
840
|
async resolveRoomIdWithRetry() {
|
|
836
841
|
const interval = this.options.checkInterval;
|
|
837
842
|
const maxInterval = Math.max(interval, 18e4);
|
|
843
|
+
const waitStart = Date.now();
|
|
838
844
|
while (true) {
|
|
839
845
|
this.abortController.signal?.throwIfAborted();
|
|
840
846
|
const roomId = await this.resolveRoomIdOnce();
|
|
841
847
|
if (roomId) return roomId;
|
|
848
|
+
this.emit("waiting", {
|
|
849
|
+
username: this.username,
|
|
850
|
+
phase: "room",
|
|
851
|
+
elapsed: (Date.now() - waitStart) / 1e3
|
|
852
|
+
});
|
|
842
853
|
await sleep(maxInterval);
|
|
843
854
|
}
|
|
844
855
|
}
|
|
@@ -859,12 +870,18 @@ var TikTokLiveDownloader = class {
|
|
|
859
870
|
async pollStreamInfo(roomId) {
|
|
860
871
|
const interval = this.options.checkInterval;
|
|
861
872
|
const maxInterval = Math.max(interval, 18e4);
|
|
873
|
+
const waitStart = Date.now();
|
|
862
874
|
while (true) {
|
|
863
875
|
this.abortController.signal?.throwIfAborted();
|
|
864
876
|
try {
|
|
865
877
|
return await this.fetchStreamInfo(roomId);
|
|
866
878
|
} catch (err) {
|
|
867
879
|
if (err instanceof StreamFetchError) {
|
|
880
|
+
this.emit("waiting", {
|
|
881
|
+
username: this.username,
|
|
882
|
+
phase: "stream",
|
|
883
|
+
elapsed: (Date.now() - waitStart) / 1e3
|
|
884
|
+
});
|
|
868
885
|
await sleep(maxInterval);
|
|
869
886
|
continue;
|
|
870
887
|
}
|
package/package.json
CHANGED
|
@@ -320,6 +320,16 @@ export class TikTokLiveDownloader {
|
|
|
320
320
|
// biome-ignore lint/style/noNonNullAssertion: at least one result
|
|
321
321
|
return finalResults[finalResults.length - 1]!;
|
|
322
322
|
} catch (err) {
|
|
323
|
+
// One-shot waiting feedback for startRecording() users
|
|
324
|
+
// when the stream isn't active yet.
|
|
325
|
+
if (err instanceof StreamFetchError) {
|
|
326
|
+
this.emit("waiting", {
|
|
327
|
+
username: this.username,
|
|
328
|
+
phase: "stream",
|
|
329
|
+
elapsed: 0,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
323
333
|
// Await any background remuxes before shutting down,
|
|
324
334
|
// so they can finish producing .mp4 and delete temp .ts files.
|
|
325
335
|
if (pendingRemuxes.length > 0) {
|
|
@@ -368,6 +378,7 @@ export class TikTokLiveDownloader {
|
|
|
368
378
|
private async resolveRoomIdWithRetry(): Promise<string> {
|
|
369
379
|
const interval = this.options.checkInterval;
|
|
370
380
|
const maxInterval = Math.max(interval, 180_000);
|
|
381
|
+
const waitStart = Date.now();
|
|
371
382
|
|
|
372
383
|
while (true) {
|
|
373
384
|
this.abortController.signal?.throwIfAborted();
|
|
@@ -377,6 +388,11 @@ export class TikTokLiveDownloader {
|
|
|
377
388
|
return roomId;
|
|
378
389
|
}
|
|
379
390
|
|
|
391
|
+
this.emit("waiting", {
|
|
392
|
+
username: this.username,
|
|
393
|
+
phase: "room",
|
|
394
|
+
elapsed: (Date.now() - waitStart) / 1_000,
|
|
395
|
+
});
|
|
380
396
|
await sleep(maxInterval);
|
|
381
397
|
}
|
|
382
398
|
}
|
|
@@ -401,6 +417,7 @@ export class TikTokLiveDownloader {
|
|
|
401
417
|
private async pollStreamInfo(roomId: string): Promise<StreamInfo> {
|
|
402
418
|
const interval = this.options.checkInterval;
|
|
403
419
|
const maxInterval = Math.max(interval, 180_000);
|
|
420
|
+
const waitStart = Date.now();
|
|
404
421
|
|
|
405
422
|
while (true) {
|
|
406
423
|
this.abortController.signal?.throwIfAborted();
|
|
@@ -409,6 +426,11 @@ export class TikTokLiveDownloader {
|
|
|
409
426
|
return await this.fetchStreamInfo(roomId);
|
|
410
427
|
} catch (err) {
|
|
411
428
|
if (err instanceof StreamFetchError) {
|
|
429
|
+
this.emit("waiting", {
|
|
430
|
+
username: this.username,
|
|
431
|
+
phase: "stream",
|
|
432
|
+
elapsed: (Date.now() - waitStart) / 1_000,
|
|
433
|
+
});
|
|
412
434
|
await sleep(maxInterval);
|
|
413
435
|
continue;
|
|
414
436
|
}
|
package/src/index.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -114,7 +114,18 @@ export interface CookieJarLike {
|
|
|
114
114
|
|
|
115
115
|
// ─── Events ───────────────────────────────────────────────
|
|
116
116
|
|
|
117
|
+
export interface WaitingInfo {
|
|
118
|
+
/** The username being waited on. */
|
|
119
|
+
username: string;
|
|
120
|
+
/** Which phase we're waiting in: "room" = looking for a room, "stream" = stream not active. */
|
|
121
|
+
phase: "room" | "stream";
|
|
122
|
+
/** Seconds elapsed since this phase started. */
|
|
123
|
+
elapsed: number;
|
|
124
|
+
}
|
|
125
|
+
|
|
117
126
|
export interface TikTokLiveDownloaderEvents {
|
|
127
|
+
/** Emitted periodically while waiting for a room or stream to become active. */
|
|
128
|
+
waiting: [info: WaitingInfo];
|
|
118
129
|
/** Emitted when a live stream is detected and we're about to start recording. */
|
|
119
130
|
start: [info: StreamInfo];
|
|
120
131
|
/** Emitted periodically (every ~1s) during recording with current stats. */
|