diff --git a/crates/librqbit/src/http_api.rs b/crates/librqbit/src/http_api.rs index 120b4ed6..7d1e5786 100644 --- a/crates/librqbit/src/http_api.rs +++ b/crates/librqbit/src/http_api.rs @@ -26,6 +26,7 @@ use crate::torrent_state::peer::stats::snapshot::PeerStatsFilter; type ApiState = Api; use crate::api::Result; +use crate::ApiError; /// An HTTP server for the API. pub struct HttpApi { @@ -128,6 +129,45 @@ impl HttpApi { state.api_torrent_details(idx).map(axum::Json) } + async fn torrent_playlist( + State(state): State, + headers: HeaderMap, + Path(idx): Path, + ) -> Result { + let host = headers + .get("host") + .ok_or_else(|| { + ApiError::new_from_text(StatusCode::BAD_REQUEST, "Missing host header") + })? + .to_str() + .context("hostname is not string")?; + + let playlist_items = state + .api_torrent_details(idx)? + .files + .into_iter() + .enumerate() + .filter_map(|(file_idx, f)| { + let is_playable = mime_guess::from_path(&f.name) + .first() + .map(|mime| { + mime.type_() == mime_guess::mime::VIDEO + || mime.type_() == mime_guess::mime::AUDIO + }) + .unwrap_or(false); + if is_playable { + let file_name = urlencoding::encode(&f.name); + Some(format!( + "http://{host}/torrents/{idx}/stream/{file_idx}/{file_name}" + )) + } else { + None + } + }); + + Ok(playlist_items.collect::>().join("\r\n")) + } + async fn torrent_haves( State(state): State, Path(idx): Path, @@ -289,6 +329,7 @@ impl HttpApi { .route("/torrents/:id/stats/v1", get(torrent_stats_v1)) .route("/torrents/:id/peer_stats", get(peer_stats)) .route("/torrents/:id/stream/:file_id", get(torrent_stream_file)) + .route("/torrents/:id/playlist", get(torrent_playlist)) .route( "/torrents/:id/stream/:file_id/*filename", get(torrent_stream_file), diff --git a/crates/librqbit/webui/src/components/buttons/TorrentActions.tsx b/crates/librqbit/webui/src/components/buttons/TorrentActions.tsx index 13ca07f5..f2bbe116 100644 --- a/crates/librqbit/webui/src/components/buttons/TorrentActions.tsx +++ b/crates/librqbit/webui/src/components/buttons/TorrentActions.tsx @@ -3,7 +3,7 @@ import { TorrentStats } from "../../api-types"; import { APIContext, RefreshTorrentStatsContext } from "../../context"; import { IconButton } from "./IconButton"; import { DeleteTorrentModal } from "../modal/DeleteTorrentModal"; -import { FaCog, FaPause, FaPlay, FaTrash } from "react-icons/fa"; +import { FaCog, FaPause, FaPlay, FaTrash, FaClipboardList } from "react-icons/fa"; import { useErrorStore } from "../../stores/errorStore"; export const TorrentActions: React.FC<{ @@ -94,6 +94,11 @@ export const TorrentActions: React.FC<{ + {alert("Open this playlist link in external player like VLC")}}> + + + + );