Streambert Download Manager: Handling M3U8 Streams with External Tools

Streambert captures HLS playlist URLs through a custom Electron session, then delegates the actual downloading to an external binary (vid-dl-cli-only) that fetches fragments, merges them with ffmpeg, and reports progress back via IPC.

Streambert is an Electron-based streaming application that automates the detection and downloading of HLS (HTTP Live Streaming) content. The Streambert download manager leverages a sophisticated IPC architecture to bridge the renderer process UI with a standalone command-line downloader, enabling robust handling of .m3u8 playlists and external tool integration.

Intercepting M3U8 URLs in the Player Session

When a video page loads, Streambert creates a custom playerSession with specific webRequest filters to intercept media resources before they reach the player.

In src/index.js, the main process registers onBeforeRequest listeners for both blocked hosts and media URLs:

const MEDIA_URLS = [
  "*://*/*.m3u8*",
  "*://*/*.m3u8",
  "*://*/*.vtt*",
  "*://*/*.vtt",
];
playerSession.webRequest.onBeforeRequest(
  { urls: [...BLOCKED_HOSTS, ...MEDIA_URLS] },
  (details, callback) => {
    const { url } = details;
    if (url.includes('.m3u8')) {
      mw.webContents.send('m3u8-found', url);
    } else if (url.includes('.vtt')) {
      const { extractSubtitleLang } = require('./src/ipc/subtitles');
      mw.webContents.send('subtitle-found', {
        url,
        lang: extractSubtitleLang(url),
      });
    }
    callback({});
  },
);
  • .m3u8 detection: Triggers the m3u8-found event sent to the renderer, enabling the download UI.
  • .vtt detection: Extracts language tags via extractSubtitleLang() from src/utils/subtitles.js and emits subtitle-found.

This interception happens transparently to the user, allowing the app to present download options immediately when HLS content is detected.

Validating the External Downloader Binary

Before executing any download, Streambert verifies that the user has configured a valid external tool. The check-downloader IPC handler in src/ipc/downloads.js scans the specified directory for an executable binary:

ipcMain.handle('check-downloader', (_, folderPath) => {
  if (!folderPath) return { exists: false, reason: 'no_folder' };
  const binary = entries.find(e => {
    if (e === '_internal' || e.startsWith('.')) return false;
    const stat = fs.statSync(path.join(folderPath, e));
    return process.platform === 'win32' ? e.endsWith('.exe')
         : !!(stat.mode & 0o111);
  });
  if (!binary) return { exists: false, reason: 'no_executable' };
  return { exists: true, binaryPath: path.join(folderPath, binary) };
});

The validation checks for platform-appropriate executables (.exe on Windows, files with execute permissions on Unix systems) and excludes internal folders like _internal.

Spawning Downloads via IPC

The run-download handler orchestrates the actual download by spawning the external vid-dl-cli-only binary as a child process. Located in src/ipc/downloads.js, this handler manages process state, logging, and progress tracking:

ipcMain.handle('run-download', (_, {
  binaryPath,
  m3u8Url,
  name,
  downloadPath,
}) => {
  const id = crypto.randomUUID();
  const logPath = path.join(os.tmpdir(), `streambert_dl_${id}.log`);

  const entry = { id, name, m3u8Url, downloadPath };
  downloads.push(entry);

  const args = [
    '--cli',
    m3u8Url,
    '-f', 'mp4 (with Audio)',
    '-r', 'best',
    '-b', '320',
    '-n', name,
    '-d', downloadPath,
  ];
  const proc = spawn(binaryPath, args, { 
    stdio: ['ignore','pipe','pipe'] 
  });
  activeProcs.set(id, proc);
  // ... progress handling
});

Each download receives a unique UUID, creates a temporary log file in os.tmpdir(), and stores process references in activeProcs for lifecycle management.

Real-Time Progress Parsing

The download manager parses stdout from the external tool to provide granular progress updates. The parser in src/ipc/downloads.js handles multiple output formats:

Fragment-based HLS progress:

const fragMatch = trimmed.match(/\(frag\s+(\d+)\/(\d+)\)/);
if (fragMatch) {
  const current = parseInt(fragMatch[1]);
  const total = parseInt(fragMatch[2]);
  update.progress = Math.min(99,
    Math.round((current / total) * 100));
  update.lastMessage = `Fragment ${current} / ${total}`;
}

Direct download statistics: When encountering [download] prefixed lines, the parser extracts percentage, total size, and download speed using regex patterns.

FFmpeg integration: The parser also monitors for Duration: and size= lines during the final muxing phase to accurately track the merge progress.

All updates are emitted to the renderer via download-progress events containing the download ID, percentage, speed, and status messages.

Post-Processing and Cleanup

Upon successful completion (exit code 0), the download manager performs several cleanup operations:

  1. Log removal: Deletes the temporary log file created in os.tmpdir().
  2. File discovery: If the binary doesn't explicitly report the output path, Streambert scans the download directory for the most recently modified video file matching known extensions.
  3. Safe renaming: Sanitizes the media title and renames the file appropriately.
  4. Subtitle fetching: Processes any subtitle URLs passed from the UI via downloadSubtitleFile, supporting HTTP(S) and local file: schemes, saving .srt, .vtt, or .ass files alongside the video.

If the external tool fails, the manager captures the last meaningful line from stderr and surfaces it to the user through the UI.

Session Persistence and Safety

Streambert maintains download state across sessions through downloads.json, which persists completed entries while filtering out active or error states. The killAllDownloads() function ensures graceful shutdown by terminating child processes and removing temporary artifacts (.part, .tmp files) when the application exits or when the user triggers a reset-app command.

Summary

  • Streambert download manager uses a custom Electron playerSession to intercept .m3u8 and .vtt URLs before they reach the video player.
  • The check-downloader IPC handler validates the presence of the vid-dl-cli-only binary before allowing downloads to start.
  • Downloads run in isolated child processes spawned via spawn() in src/ipc/downloads.js, with real-time stdout parsing for fragment-level progress updates.
  • Post-processing includes automatic file discovery, safe renaming, subtitle downloads, and cleanup of temporary log files.
  • State persistence and emergency cleanup ensure system hygiene even during unexpected shutdowns.

Frequently Asked Questions

How does Streambert detect M3U8 streams without browser extensions?

Streambert creates a custom Electron session (playerSession) with webRequest.onBeforeRequest filters targeting *.m3u8* and *.vtt* patterns. When the player loads a page, this API intercepts network requests before they complete, allowing the main process to emit m3u8-found events to the renderer without requiring external browser extensions or user scripts.

What external tool does Streambert require for downloading?

Streambert requires the vid-dl-cli-only binary, a standalone command-line downloader capable of processing HLS playlists, downloading fragments, and merging them with ffmpeg. The application validates this binary through the check-downloader IPC handler, which checks for executable permissions (or .exe extension on Windows) in the user-specified directory.

Can Streambert download subtitles separately from video?

Yes. When the player session detects .vtt URLs, it extracts language codes using extractSubtitleLang() from src/utils/subtitles.js and emits subtitle-found events. During download initiation, users can pass subtitle arrays to the run-download handler, which fetches them via downloadSubtitleFile and saves them with appropriate extensions alongside the video file.

How does the download manager handle partial failures or cancellations?

The download manager tracks active processes in an activeProcs Map using UUIDs. When a user cancels a download or the app shuts down, killAllDownloads() terminates the associated child process and removes temporary files including log files and partial downloads (.part files). Failed downloads capture the last stderr line for error reporting while preserving the attempt in the downloads store until explicitly cleared.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →