feat: enhance Jellyfin stream URL generation and improve NumberInput component
This commit is contained in:
@@ -153,13 +153,15 @@ impl IMediaProvider for JellyfinMediaProvider {
|
|||||||
Ok(body.items.into_iter().next().and_then(map_jellyfin_item))
|
Ok(body.items.into_iter().next().and_then(map_jellyfin_item))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build a direct-play stream URL for a Jellyfin item.
|
/// Build a stream URL for a Jellyfin item.
|
||||||
///
|
///
|
||||||
/// Uses `static=true` to request the original file without transcoding.
|
/// Requests H.264 video + AAC audio in an MP4 container so that all
|
||||||
/// The API key is embedded in the URL so the player does not need separate auth.
|
/// major browsers can play it natively. Jellyfin will direct-play if the
|
||||||
|
/// source already matches; otherwise it transcodes on the fly.
|
||||||
|
/// The API key is embedded in the URL so the player needs no separate auth.
|
||||||
async fn get_stream_url(&self, item_id: &MediaItemId) -> DomainResult<String> {
|
async fn get_stream_url(&self, item_id: &MediaItemId) -> DomainResult<String> {
|
||||||
Ok(format!(
|
Ok(format!(
|
||||||
"{}/Videos/{}/stream?static=true&api_key={}",
|
"{}/Videos/{}/stream?videoCodec=h264&audioCodec=aac&container=mp4&api_key={}",
|
||||||
self.config.base_url,
|
self.config.base_url,
|
||||||
item_id.as_ref(),
|
item_id.as_ref(),
|
||||||
self.config.api_key,
|
self.config.api_key,
|
||||||
|
|||||||
@@ -66,12 +66,14 @@ function NumberInput({
|
|||||||
onChange,
|
onChange,
|
||||||
min,
|
min,
|
||||||
max,
|
max,
|
||||||
|
step,
|
||||||
placeholder,
|
placeholder,
|
||||||
}: {
|
}: {
|
||||||
value: number | "";
|
value: number | "";
|
||||||
onChange: (v: number | "") => void;
|
onChange: (v: number | "") => void;
|
||||||
min?: number;
|
min?: number;
|
||||||
max?: number;
|
max?: number;
|
||||||
|
step?: number | "any";
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
@@ -79,6 +81,7 @@ function NumberInput({
|
|||||||
type="number"
|
type="number"
|
||||||
min={min}
|
min={min}
|
||||||
max={max}
|
max={max}
|
||||||
|
step={step}
|
||||||
value={value}
|
value={value}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
@@ -229,11 +232,12 @@ function BlockEditor({ block, onChange, onRemove }: BlockEditorProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<Field label="Start time" hint="24-hour format HH:MM">
|
<Field label="Start time">
|
||||||
<TextInput
|
<input
|
||||||
|
type="time"
|
||||||
value={block.start_time.slice(0, 5)}
|
value={block.start_time.slice(0, 5)}
|
||||||
onChange={(v) => setField("start_time", v + ":00")}
|
onChange={(e) => setField("start_time", e.target.value + ":00")}
|
||||||
placeholder="20:00"
|
className="w-full rounded-md border border-zinc-700 bg-zinc-800 px-3 py-2 text-sm text-zinc-100 focus:border-zinc-500 focus:outline-none"
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
<Field label="Duration (minutes)">
|
<Field label="Duration (minutes)">
|
||||||
@@ -443,6 +447,7 @@ function RecyclePolicyEditor({ policy, onChange }: RecyclePolicyEditorProps) {
|
|||||||
}
|
}
|
||||||
min={0}
|
min={0}
|
||||||
max={1}
|
max={1}
|
||||||
|
step={0.01}
|
||||||
placeholder="0.1"
|
placeholder="0.1"
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|||||||
Reference in New Issue
Block a user