Skip to content

Streaming API & Prefetch

src/torrent/api.ts is the frontend's single entry point for starting audio streams. It handles both RuTracker (vozduxan/libtorrent) and SoulSeek paths, manages the .torrent file cache, and orchestrates next-track prefetching.

Source: src/torrent/api.ts, src/torrent/torrentSession.ts


streamUrl(magnet, fileIdx, opts)

The main function called by the player when a track needs to start playing.

export interface StreamUrlOpts {
  source?: string;           // "rutracker" | "soulseek"
  torrentId?: string | number;
  slskUsername?: string;
  slskFilepath?: string;
  slskFilesize?: number;
}

SoulSeek path

if (src === "soulseek") {
  const ready = await soulseekPrepareStream(slskUsername, slskFilepath, slskFilesize ?? 0);
  return ready?.url ?? "";
}

Calls soulseek_prepare_stream (Tauri invoke), returns http://127.0.0.1:<port>/slsk/<token>.

RuTracker / vozduxan path

const m = enrichMagnetWithOpenTrackers(magnet);
let torrentFileB64 = null;
if (src === "rutracker" && tid !== "") {
  torrentFileB64 = await _cachedTorrentFileB64(tid);
}
const ready = await invoke("torrent_prepare_stream", { magnet: m, fileIdx, torrentFileB64 });
return ready?.url ?? "";

enrichMagnetWithOpenTrackers appends a set of open tracker URLs to the magnet URI. This avoids DHT-only discovery latency for freshly parsed magnets.

torrentFileB64 is base64-encoded .torrent file content. Providing it to torrent_prepare_stream skips the metadata_received_alert wait in libtorrent — neegde can start buffering immediately without waiting for peer DHT metadata exchange. For RuTracker topics the binary is downloaded from dl.php (see Torrent Details).


.torrent file cache: _cachedTorrentFileB64

The cache avoids re-downloading .torrent files for tracks in the same torrent that the user plays sequentially.

const _torrentFileCache = new Map<string, Promise<string | null>>();
const TORRENT_FILE_CACHE_MAX = 30;
const _TORRENT_LS_PREFIX = "torrent_file_b64_v1_";

The cache is a Map<topicId, Promise<b64string>>. Storing promises (not resolved values) means that if two tracks from the same torrent start streaming simultaneously, only one download is initiated — both callers await the same promise.

Persistence: entries are also written to localStorage (key prefix torrent_file_b64_v1_<topicId>). On module init, _loadPersistedTorrentFiles reads them back so the cache survives page reloads and app restarts without hitting dl.php again.

LRU eviction: when the in-memory cache exceeds 30 entries, the oldest (insertion-order first in Map) is dropped. When localStorage exceeds 30 entries, the oldest keys are deleted.


prefetchNextInQueue(current, next, opts)

Called by the Player when playback progress reaches ~22% or after 30 seconds (whichever comes first). Warms the vozduxan piece priority window for the next track before the user reaches it.

const result = await invoke("torrent_prefetch_next_track", {
  currentMagnet: m0,
  currentFileIdx: current.fileIdx,
  nextMagnet: m1,
  nextFileIdx: next.fileIdx,
  nextTorrentFileB64,   // null for SoulSeek (no torrent file concept)
  warmOnly,             // true = use warm_prefetch_token (track+2 slot)
});

warmOnly controls which token bucket the Rust side uses: - falseprefetch_token (track+1, the real next track) - truewarm_prefetch_token (track+2, second-ahead warm-up)

Both calls originate from usePrefetch.ts which monitors queue position and triggers: 1. prefetchNextInQueue(current, next, { warmOnly: false }) — at 22% or 30s 2. prefetchNextInQueue(current, secondNext, { warmOnly: true }) — at a later threshold

The warmOnly separation ensures warming the second-ahead track never evicts the already-warmed real next track (see Rust Wrapper token buckets).


torrentFileB64ForTrack(track)

A helper used by prefetchNextInQueue to fetch the .torrent file for the next track before calling the Tauri command. This way the base64 payload is included in the prefetch command, avoiding the metadata-wait in libtorrent for the prefetched torrent too.

Only fires for RuTracker sources with a non-empty torrentId. SoulSeek tracks return null immediately.


releaseTorrentStreamUrl(token)

In src/torrent/torrentSession.ts:

export async function releaseTorrentStreamUrl(token: string): Promise<void> {
  await invoke("torrent_release_stream", { token });
}

Called by the Player when a track ends, is skipped, or the component unmounts. Releases the vozduxan stream token, which triggers the ~100 ms priority thread join in C++ and marks the stream as idle.


Debug logging

All streamUrl and prefetchNextInQueue calls log to the in-app debug console via appDebugLog("stream", msg). Key log points:

  • torrent file b64: cache hit / downloading from rutracker — shows whether the .torrent cache was used
  • streamUrl: invoking prepare — before the Tauri call, shows fileIdx and whether torrent file was provided
  • streamUrl: prepare OK / FAILED — result with elapsed time
  • prefetchNextInQueue: start — shows warm vs next, fileIdx, torrentId
  • prefetchNextInQueue: OK / FAILED — result kind and elapsed time