Merge pull request #167 from izderadicka/playlist
Playlis for playable media
This commit is contained in:
commit
e83c3d5534
2 changed files with 47 additions and 1 deletions
|
|
@ -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<ApiState>,
|
||||
headers: HeaderMap,
|
||||
Path(idx): Path<usize>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
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::<Vec<_>>().join("\r\n"))
|
||||
}
|
||||
|
||||
async fn torrent_haves(
|
||||
State(state): State<ApiState>,
|
||||
Path(idx): Path<usize>,
|
||||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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<{
|
|||
<IconButton onClick={startDeleting} disabled={disabled}>
|
||||
<FaTrash className="hover:text-red-500" />
|
||||
</IconButton>
|
||||
<IconButton onClick={() => {alert("Open this playlist link in external player like VLC")}}>
|
||||
<a target="_blank" href={"/torrents/"+id+"/playlist"}>
|
||||
<FaClipboardList className="hover:text-green-500"/>
|
||||
</a>
|
||||
</IconButton>
|
||||
<DeleteTorrentModal id={id} show={deleting} onHide={cancelDeleting} />
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue