Streaming API и Prefetch¶
src/torrent/api.ts — единственная точка входа фронтенда для запуска аудио stream-ов. Он обрабатывает оба пути — RuTracker (vozduxan/libtorrent) и SoulSeek, управляет cache файлов .torrent и координирует prefetch следующего трека.
Источники: src/torrent/api.ts, src/torrent/torrentSession.ts
streamUrl(magnet, fileIdx, opts)¶
Главная функция, вызываемая плеером, когда трек нужно начать воспроизводить.
export interface StreamUrlOpts {
source?: string; // "rutracker" | "soulseek"
torrentId?: string | number;
slskUsername?: string;
slskFilepath?: string;
slskFilesize?: number;
}
Путь SoulSeek¶
if (src === "soulseek") {
const ready = await soulseekPrepareStream(slskUsername, slskFilepath, slskFilesize ?? 0);
return ready?.url ?? "";
}
Вызывает soulseek_prepare_stream (Tauri invoke), возвращает http://127.0.0.1:<port>/slsk/<token>.
Путь RuTracker / vozduxan¶
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 добавляет набор URL открытых трекеров к magnet URI. Это позволяет избежать задержки поиска только через DHT для свежих magnet-ссылок.
torrentFileB64 — содержимое файла .torrent в кодировке base64. Его передача в torrent_prepare_stream позволяет пропустить ожидание metadata_received_alert в libtorrent — neegde может начать буферизацию сразу, не дожидаясь обмена метаданными через DHT. Для topic-ов RuTracker бинарник скачивается с dl.php (см. Torrent Details).
Cache файлов .torrent: _cachedTorrentFileB64¶
Cache позволяет не перекачивать файлы .torrent для треков из одного торрента, которые пользователь последовательно воспроизводит.
const _torrentFileCache = new Map<string, Promise<string | null>>();
const TORRENT_FILE_CACHE_MAX = 30;
const _TORRENT_LS_PREFIX = "torrent_file_b64_v1_";
Cache — это Map<topicId, Promise<b64string>>. Хранение promise-ов (а не разрешённых значений) означает, что если два трека из одного торрента начинают стриминг одновременно, инициируется только одна загрузка — оба вызывающих ожидают одного и того же promise.
Сохранение: записи также записываются в localStorage (префикс ключа torrent_file_b64_v1_<topicId>). При инициализации модуля _loadPersistedTorrentFiles читает их обратно, чтобы cache сохранялся между перезагрузками страницы и перезапусками приложения без повторных запросов к dl.php.
LRU-вытеснение: когда in-memory cache превышает 30 записей, самая старая (первая в порядке вставки в Map) удаляется. Когда localStorage превышает 30 записей, самые старые ключи удаляются.
prefetchNextInQueue(current, next, opts)¶
Вызывается плеером, когда прогресс воспроизведения достигает ~22% или после 30 секунд (в зависимости от того, что наступит раньше). Предварительно прогревает окно приоритетов кусков vozduxan для следующего трека, прежде чем пользователь до него доберётся.
const result = await invoke("torrent_prefetch_next_track", {
currentMagnet: m0,
currentFileIdx: current.fileIdx,
nextMagnet: m1,
nextFileIdx: next.fileIdx,
nextTorrentFileB64, // null для SoulSeek (концепция torrent-файла отсутствует)
warmOnly, // true = использовать warm_prefetch_token (слот track+2)
});
warmOnly управляет тем, какой token bucket использует сторона Rust:
- false → prefetch_token (track+1, реальный следующий трек)
- true → warm_prefetch_token (track+2, прогрев второго по счёту трека)
Оба вызова исходят из usePrefetch.ts, который отслеживает позицию в очереди и запускает:
1. prefetchNextInQueue(current, next, { warmOnly: false }) — на 22% или 30с
2. prefetchNextInQueue(current, secondNext, { warmOnly: true }) — на более позднем пороге
Разделение по warmOnly гарантирует, что прогрев второго по счёту трека никогда не вытеснит уже прогретый реальный следующий трек (см. token bucket-ы обёртки Rust).
torrentFileB64ForTrack(track)¶
Вспомогательная функция, используемая prefetchNextInQueue для загрузки файла .torrent следующего трека перед вызовом Tauri-команды. Таким образом, payload в base64 включается в команду prefetch, что позволяет также избежать ожидания метаданных в libtorrent для prefetch-торрента.
Срабатывает только для источников RuTracker с непустым torrentId. Для треков SoulSeek сразу возвращает null.
releaseTorrentStreamUrl(token)¶
В src/torrent/torrentSession.ts:
export async function releaseTorrentStreamUrl(token: string): Promise<void> {
await invoke("torrent_release_stream", { token });
}
Вызывается плеером, когда трек заканчивается, пропускается или компонент размонтируется. Освобождает stream token vozduxan, что запускает ожидание потока приоритетов (~100 мс) в C++ и помечает stream как idle.
Логирование отладки¶
Все вызовы streamUrl и prefetchNextInQueue логируются в консоль отладки приложения через appDebugLog("stream", msg). Ключевые точки логирования:
torrent file b64: cache hit/downloading from rutracker— показывает, использовался ли cache.torrentstreamUrl: invoking prepare— до вызова Tauri, показывает fileIdx и наличие torrent-файлаstreamUrl: prepare OK/FAILED— результат с затраченным временемprefetchNextInQueue: start— показывает warm vs next, fileIdx, torrentIdprefetchNextInQueue: OK/FAILED— вид результата и затраченное время