Стриминг SoulSeek¶
Потоковое воспроизведение аудио через SoulSeek следует той же схеме, что и путь через vozduxan, с точки зрения фронтенда: streamUrl() возвращает локальный HTTP URL, и элемент <audio> воспроизводит с него. Разница — целиком на стороне бэкенда.
См. также: Передача файлов — полный протокол P2P-скачивания.
Полный поток воспроизведения¶
Пользователь кликает на трек SoulSeek
│
▼ streamUrl(magnet, fileIdx, { source: "soulseek", slskUsername, slskFilepath, slskFilesize })
│
▼ soulseekPrepareStream(username, filepath, filesize)
│ ≡ invoke("soulseek_prepare_stream", { username, filepath, filesize })
│
▼ Rust: download_and_stream(session, username, filepath, filesize, token)
│ │
│ ├── современный путь: QueueUpload → TransferRequest → TransferResponse → F connection
│ │ (при неудаче откатывается к устаревшему TransferRequest)
│ │
│ ├── download_loop: читает из F socket → neegde_slsk_<token>.tmp
│ │
│ └── http_server_loop на 127.0.0.1:<random_port>
│
▼ Возвращает { url: "http://127.0.0.1:<port>/slsk/<token>", token: "<n>" }
│
▼ <audio src={url} /> начинает воспроизведение
│ HTTP Range-запросы → http_server_loop → читает temp-файл, опрашивает счётчик downloaded
│
▼ Трек завершился / пользователь пропустил
invoke("soulseek_release_stream", { token })
→ прерывает задачу загрузки + HTTP-задачу
→ удаляет neegde_slsk_<token>.tmp
Ключевые отличия от пути через vozduxan¶
| Аспект | vozduxan (RuTracker) | SoulSeek |
|---|---|---|
| Источник загрузки | libtorrent через DHT/трекеры | Одноранговый P2P TCP |
| Локальный сервер | C++ HTTP-сервер (vozduxan) | Rust HTTP-сервер (transfer.rs) |
| Stream URL | http://127.0.0.1:<port>/stream/<token> |
http://127.0.0.1:<port>/slsk/<token> |
| Поддержка перемотки | Полная (окно приоритетов кусков сдвигается) | Полная (Range-запросы, опрос temp-файла) |
| Prefetch | Token buckets (current/next/warm) | Не реализован — треки SoulSeek однораздаточные |
| Release | torrent_release_stream → ожидание C++ |
soulseek_release_stream → прерывание + удаление файла |
| Тип token | Строка (vozduxan UUID) | Строка (u32 как строка) |
Команда soulseek_prepare_stream¶
const ready = await invoke<{ url: string; token: string }>(
"soulseek_prepare_stream",
{ username, filepath, filesize }
);
// ready.url = "http://127.0.0.1:<port>/slsk/<token>"
// ready.token = "<u32 as string>"
Rust-обработчик:
- Получает активную
Session(возвращает ошибку, если отключён или session мертва). - Генерирует новый
token = next_token(). - Вызывает
transfer::download_and_stream(session, username, filepath, filesize, token). - Сохраняет
ActiveStream { temp_path, download_abort, http_abort }вSoulSeekState.streamsс ключомtoken.to_string(). - Возвращает
{ url, token }.
Команда soulseek_release_stream¶
Находит ActiveStream по token, прерывает обе задачи и удаляет temp-файл. Вызывается:
- Когда плеер переходит на следующий трек (releaseTorrentStreamUrl).
- Когда SoulSeek-session заменяется через soulseek_login (все потоки сбрасываются в soulseek_logout).
HTTP-сервер для SoulSeek¶
HTTP-сервер в transfer.rs реализует подмножество HTTP/1.1, необходимое для конвейера Web Audio:
OPTIONS→204 No Contentс CORS-заголовками (preflight для cross-origin из WebView).GETбез Range →200 OKсContent-Length: file_size,Accept-Ranges: bytes.GETс Range →206 Partial Contentс корректным заголовкомContent-Range.
Поведение при перемотке¶
Когда аудиоэлемент перематывает, он отправляет Range-запрос для нового байтового смещения. HTTP-сервер:
- Проверяет
downloaded.load(Acquire)— количество байт, уже записанных во временный файл. - Если
downloaded > range_start, сразу отдаёт данные из temp-файла. - Если нет — крутится в poll-цикле с интервалом 80 мс, пока
downloaded > range_startилиcomplete = true. - Если данные не поступают в течение 60 секунд — возвращает
503 Service Unavailable.
Это означает, что перемотка назад мгновенна (данные уже загружены), тогда как перемотка далеко вперёд буфера может ненадолго застопориться.
HTTP-сервер читает из temp-файла в spawn_blocking через std::fs::File::seek + std::fs::File::read, чтобы не блокировать async executor. Размер чанка при чтении — HTTP_CHUNK = 32 КБ.
Расположение temp-файлов¶
Temp-файлы называются neegde_slsk_<token>.tmp и помещаются в std::env::temp_dir(). На macOS это /var/folders/…/T/. Они создаются в начале download_loop и удаляются командой soulseek_release_stream. Если приложение завершилось аварийно до release, файлы остаются на диске, но как правило очищаются ОС при следующей перезагрузке (temp-директория на APFS с авто-очисткой на macOS).
Переключение на альтернативных peers¶
Фронтенд хранит до 5 альтернативных peers в raw.alternativePeers для каждой сущности трека SoulSeek (см. Ранжирование peers). Если soulseek_prepare_stream выбрасывает ошибку (timeout, отказ, NAT), плеер автоматически повторяет попытку со следующим альтернативным peer:
// В Player.vue (упрощённо)
for (const alt of track.alternativePeers ?? []) {
try {
url = await streamUrl(magnet, 0, {
source: "soulseek",
slskUsername: alt.slskUsername,
slskFilepath: alt.slskFilepath,
slskFilesize: alt.size,
});
break;
} catch { /* пробуем следующего */ }
}
Этот failover прозрачен для пользователя — аудио начинается, как только любой peer принимает запрос.