npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

tokwatchr

v0.8.0

Published

Download TikTok livestreams. Given a username, download the livestream.

Readme

tokwatchr

Download TikTok livestreams — given a username, download the livestream.

npm version npm downloads TypeScript Node.js License

InstallQuick startAPIHow it worksAdvanced usage

A TypeScript library for downloading TikTok livestreams. Pass a username, it records the stream in crash-safe .ts segments, applies EBU R128 audio normalization, and remuxes to .mp4. Uses impit for browser TLS fingerprint emulation to bypass bot detection, and ffmpeg for audio normalization and container remuxing.

[!NOTE] This project is a reverse-engineering effort and is not affiliated with TikTok. Use at your own risk.

Features

  • One-shot or event-driven — use the download() function for simplicity, or TikTokLiveDownloader for full control with progress and segment events.
  • Browser TLS emulation — uses impit with Chrome fingerprints to bypass TikTok's bot detection.
  • System ffmpeg — auto-detects ffmpeg on PATH; falls back to raw FLV download if not found.
  • EBU R128 audio normalization — two-pass loudnorm (equivalent to ffmpeg-normalize --preset streaming-video), always applied.
  • Crash-safe .ts intermediate — saves stream as MPEG-TS first (playable at any cut point), then remuxes to .mp4.
  • Automatic quality selection — picks the best available quality (1080p → 720p → 540p → 360p).
  • Segment mode — split long streams into configurable parts (e.g. 20min each) for reliability.
  • Wait-for-live mode — polls periodically and starts recording when the user goes live.
  • Graceful stop & abort — stop cleanly (keeps partial file) or abort immediately with AbortSignal support.
  • Proxy & cookie support — HTTP/SOCKS proxies and cookie jars for authenticated streams.
  • Standalone utilities — use resolveRoomId(), fetchStreamInfo(), or createClient() independently.

Install

npm install tokwatchr
# or
bun add tokwatchr

[!TIP] System requirements: ffmpeg must be installed on your system for audio normalization and .mp4 output. Without it, the library falls back to raw FLV download. On macOS brew install ffmpeg, on Ubuntu sudo apt install ffmpeg.

Quick start

One-shot download

import { download } from "tokwatchr";

const result = await download("officialgeilegisela", {
  output: "./recordings",
});

console.log(`Saved to ${result.filePath}`);
// → ./recordings/officialgeilegisela=20260604_143022.mp4

With progress events

import { TikTokLiveDownloader } from "tokwatchr";

const d = new TikTokLiveDownloader("tv_asahi_news", {
  output: "./vods",
  maxDuration: 7_200, // 2 hours
});

d.on("progress", (stats) => {
  console.log(
    `${stats.downloadedMB.toFixed(1)}MB @ ${stats.speedMBps.toFixed(1)}MB/s`,
  );
});

d.on("complete", (results) => {
  for (const r of results) {
    console.log(`Done: ${r.filePath} (${r.sizeMB.toFixed(1)}MB)`);
  }
});

d.on("error", (err) => {
  console.error("Recording failed:", err.message);
});

await d.start();

Segmented recording (20min parts, non-blocking remux)

const d = new TikTokLiveDownloader("username", {
  output: "./recordings",
  maxSegmentDuration: 1200, // 20 minutes per segment
});

d.on("segment", (result, partNum) => {
  console.log(`Part ${partNum} done: ${result.filePath}`);
});

d.on("complete", (results) => {
  console.log(`All ${results.length} segments complete`);
});

await d.start();

API

download(username, options?)

Functional shorthand. Returns a Promise<DownloadResult> (last segment when segmented).

import { download } from "tokwatchr";

const result = await download("username", {
  output: "./vods",
  quality: "best",
  onProgress: (s) => console.log(s.downloadedMB),
});

new TikTokLiveDownloader(username, options?)

Class-based API with events and lifecycle control.

import { TikTokLiveDownloader } from "tokwatchr";

const d = new TikTokLiveDownloader("username", {
  output: "./vods",
  quality: "hd1",
  format: "ts",  // keep as .ts (no remux)
  proxyUrl: "socks5://localhost:1080",
});

Events

| Event | Payload | Description | |---|---|---| | start | StreamInfo | Stream URL resolved, recording starting | | progress | DownloadStats | Emitted every ~1s during recording | | segment | [result: DownloadResult, partNumber: number] | A segment completed (only when maxSegmentDuration is set) | | complete | DownloadResult[] | All segments done, remuxed files ready | | error | Error | An error occurred | | stop | — | Recording was stopped via stop() |

Methods

| Method | Returns | Description | |---|---|---| | start() | Promise<DownloadResult> | Wait for live, then record | | startRecording() | Promise<DownloadResult> | Record now (fails if not live) | | waitForLive() | Promise<StreamInfo> | Just wait, don't record | | stop() | Promise<void> | Graceful stop (remuxes pending segments) | | abort() | void | Immediate abort | | state | DownloaderState | "idle" | "waiting" | "recording" | "stopping" | "done" |

Options

interface TikTokLiveDownloaderOptions {
  output?: string;             // Output directory (default: process.cwd())
  filename?: string;           // Template: {username}, {date}, {time}, {title}, {part}
  quality?: "best" | "worst"   // Quality preference (default: "best")
           | "fullhd1" | "hd1" | "sd2" | "sd1";
  format?: "mp4" | "mkv" | "ts" | "flv";  // Output container (default: "mp4")
  useFfmpeg?: boolean;         // Auto-detects system ffmpeg (default: true if found)
  ffmpegPath?: string;         // Custom ffmpeg binary path
  ffmpegArgs?: string[];       // Extra ffmpeg args (default: ["-c", "copy"])
  bitrate?: string;            // Re-encode bitrate (e.g. "1M")
  maxDuration?: number;        // Seconds before auto-stop (default: Infinity)
  maxSegmentDuration?: number; // Split into segments this many seconds long
  checkInterval?: number;      // Poll interval for wait-for-live (ms, default: 30_000)
  proxyUrl?: string;           // HTTP/SOCKS proxy URL
  cookieJar?: CookieJarLike;   // tough-cookie compatible jar
  browser?: Browser;           // impit browser preset (default: "chrome")
  timeout?: number;            // Request timeout ms (default: 30_000)
  headers?: Record<string, string>;  // Extra HTTP headers
  signal?: AbortSignal;        // External cancellation
  // Callbacks (functional shorthand):
  onStart?: (info: StreamInfo) => void;
  onProgress?: (stats: DownloadStats) => void;
  onError?: (err: Error) => void;
}

Types

interface StreamInfo {
  roomId: string;
  username: string;
  title: string;
  qualities: QualityOption[];
  selectedQuality: QualityOption;
  streamUrl: string;
  viewerCount: number;
  startedAt: Date;
}

interface DownloadStats {
  downloadedBytes: number;
  downloadedMB: number;
  duration: number;        // seconds elapsed
  speed: number;           // bytes/sec
  speedMBps: number;
  quality: StreamQualityKey;
  state: DownloaderState;
}

interface DownloadResult {
  filePath: string;
  sizeBytes: number;
  sizeMB: number;
  duration: number;        // seconds of content
  username: string;
  roomId: string;
  quality: StreamQualityKey;
  format: OutputFormat;    // "mp4" | "mkv" | "ts" | "flv"
  startedAt: Date;
  endedAt: Date;
}

Error classes

import {
  TikTokLiveError,        // Base error class
  UserOfflineError,       // User is not live
  RoomResolveError,       // Could not find room ID
  StreamFetchError,       // Could not get stream URL
  DownloadFailedError,    // Download failed mid-stream
  FfmpegError,            // ffmpeg subprocess error
  AbortError,             // Request was aborted
} from "tokwatchr";

How it works

Username
  │
  ├─ GET @{user}/live              (HTML scrape for roomId)
  │    └─ fallback: /api-live/user/room/
  │
  ▼
Room ID
  │
  ├─ GET /webcast/room/info/       (fetch stream URLs + qualities)
  │
  ▼
FLV endpoint  ────►  1080p | 720p | 540p | 360p
  │
  ├─ With ffmpeg:
  │    ffmpeg -i <flv_url> -c copy segment.ts   (crash-safe TS)
  │      → measure loudness with loudnorm
  │      → remux with AAC encode + EBU R128 normalization
  │      → segment.mp4  (final output)
  │
  └─ Without ffmpeg:
       HTTP stream → file.flv

The download process:

  1. Room ID resolution — scrapes the user's TikTok live page for the room ID embedded in SIGI_STATE. Falls back to the api-live/user/room/ API endpoint.
  2. Stream URL fetch — calls webcast/room/info/ to get available stream qualities. Selects the best available (1080p → 720p → 540p → 360p).
  3. Download to .ts — saves the raw stream as MPEG-TS, which is playable even if truncated mid-stream.
  4. Remux with normalization — two-pass EBU R128 loudnorm to -14 LUFS (streaming standard), AAC encode at 128k, video copied without re-encode.
  5. Segment loop — if maxSegmentDuration is set, the process repeats: download, remux, emit segment, check for live, next segment.

All HTTP requests use impit with Chrome TLS fingerprint emulation to bypass bot detection.

Advanced usage

Standalone utilities

import { resolveRoomId, fetchStreamInfo, createClient } from "tokwatchr";

const impit = createClient({ browser: "chrome" });

const roomId = await resolveRoomId("username", impit);
const info = await fetchStreamInfo(roomId, "username", impit, {
  quality: "best",
});

console.log(info.streamUrl); // FLV URL

Custom filename template

import { renderFilename } from "tokwatchr";

const name = renderFilename("{username}={date}_{time}", {
  username: "testuser",
  title: "My Stream Title",
});
// → "testuser=20260604_143022"

Segmented download with custom part template

const d = new TikTokLiveDownloader("username", {
  maxSegmentDuration: 600,  // 10 min segments
  filename: "{username}_{title}_part{part}",
});
// → "officialgeilegisela_Live_Stream_part1.mp4"
// → "officialgeilegisela_Live_Stream_part2.mp4"

Using a proxy

const d = new TikTokLiveDownloader("username", {
  proxyUrl: "http://user:pass@proxy:8080",
  browser: "chrome",
});

Authenticated streams (cookies)

import { CookieJar } from "tough-cookie";

const jar = new CookieJar();
await jar.setCookie("sessionid=abc123", "https://www.tiktok.com");

const d = new TikTokLiveDownloader("username", {
  cookieJar: jar,
});

Abort via AbortSignal

const controller = new AbortController();

const d = new TikTokLiveDownloader("username", {
  signal: controller.signal,
});

setTimeout(() => controller.abort(), 10_000); // 10s timeout
await d.start().catch((err) => {
  if (err.name === "AbortError") {
    console.log("Timed out");
  }
});

Using your own ffmpeg

const d = new TikTokLiveDownloader("username", {
  ffmpegPath: "/usr/local/bin/ffmpeg",
  ffmpegArgs: ["-c:v", "libx264", "-preset", "fast", "-c:a", "aac"],
  bitrate: "2M",
});