एलिमेंट
Video
वीडियो व्यू प्लेटफ़ॉर्म के प्लेयर से वीडियो फ़ाइल या स्ट्रीम चलाता है: iOS और Mac Catalyst पर AVPlayer, Android पर MediaPlayer। प्लेबैक को props (paused, muted, rate, …) से नियंत्रित किया जाता है, इसलिए प्लेयर किसी भी अन्य व्यू की तरह आपकी state का अनुसरण करता है। Rust में इसे video(url) से बनाया जाता है, TSX में nsh से <Video> द्वारा।
import { mount, signal, Text, Video, type VideoRef, View } from "nsh";
import { windowSize } from "nsh";
import { button, buttonText, ui } from "./palette";
// A short CC0 clip from MDN's examples.
const FLOWER = "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4";
function Toggle(props: { label: string; on: boolean; onPress: () => void }) {
return (
<View
onPress={props.onPress}
{...button("secondary", { on: () => props.on, size: "sm" })}
>
<Text style={buttonText("secondary", { on: () => props.on })}>{props.label}</Text>
</View>
);
}
function App() {
const paused = signal(true);
const muted = signal(true);
const repeat = signal(true);
const controls = signal(false);
const fast = signal(false);
const time = signal(0);
const duration = signal(0);
const ratio = signal(16 / 9);
const status = signal("loading");
const ends = signal(0);
let video!: VideoRef;
const wide = () => windowSize().width >= 700;
return (
<View style={{ flex: 1, backgroundColor: ui().page, padding: 20, paddingTop: 24, gap: 14 }}>
<Text style={{ fontSize: 22, fontWeight: "700", color: ui().text }}>video</Text>
<View style={{ flex: 1, flexDirection: wide() ? "row" : "column", gap: 16 }}>
<View style={{ flex: wide() ? 1 : undefined, justifyContent: "center" }}>
<Video
ref={video}
src={FLOWER}
paused={paused()}
muted={muted()}
repeat={repeat()}
controls={controls()}
rate={fast() ? 2 : 1}
progressUpdateInterval={100}
style={{ width: "100%", aspectRatio: ratio(), borderRadius: 14, overflow: "hidden", backgroundColor: "#000000" }}
onLoadStart={() => status.set("onLoadStart")}
onLoad={(e) => {
duration.set(e.duration);
ratio.set(e.size.width / e.size.height);
status.set(`onLoad ${e.size.width} × ${e.size.height}`);
}}
onProgress={(e) => time.set(e.currentTime)}
onBuffer={(e) => status.set(e.isBuffering ? "buffering" : "playing")}
onEnd={() => ends.update((n) => n + 1)}
onError={(e) => status.set(`onError ${e.message}`)}
/>
</View>
<View style={{ gap: 12, width: wide() ? 300 : undefined, justifyContent: "center" }}>
<View style={{ ...ui().inset, height: 6, borderRadius: 3, overflow: "hidden" }}>
<View style={{ height: 6, width: `${duration() ? (time() / duration()) * 100 : 0}%`, backgroundColor: ui().accent }} />
</View>
<Text style={{ color: ui().text, fontFamily: "monospace" }}>
onProgress {time().toFixed(1)} / {duration().toFixed(1)} s
</Text>
<Text style={{ color: ui().muted, fontFamily: "monospace", fontSize: 13 }}>
{status()} · onEnd {ends()}×
</Text>
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: 8 }}>
<Toggle label={paused() ? "Play" : "Pause"} on={!paused()} onPress={() => paused.set(!paused())} />
<Toggle label="muted" on={muted()} onPress={() => muted.set(!muted())} />
<Toggle label="repeat" on={repeat()} onPress={() => repeat.set(!repeat())} />
<Toggle label="rate 2" on={fast()} onPress={() => fast.set(!fast())} />
<Toggle label="controls" on={controls()} onPress={() => controls.set(!controls())} />
<Toggle label="seek(0)" on={false} onPress={() => video.seek(0)} />
</View>
</View>
</View>
</View>
);
}
mount(App);use nsh_core::prelude::*;
fn player() -> impl IntoElement {
let paused = signal(true);
let time = signal(String::new());
let player = element_ref();
view()
.gap_3()
.child(
video("https://example.com/clip.mp4")
.element_ref(&player)
.paused(paused)
.repeat(true)
.object_fit(ResizeMode::Contain)
.h(px(220.0))
.bg(black())
.on_progress(move |p: &VideoProgress| {
time.set(format!("{:.1} / {:.1} s", p.current_time, p.duration))
}),
)
.child(
view()
.flex_row()
.gap_4()
.child(
text(move || if paused.get() { "Play" } else { "Pause" })
.on_press(move || paused.update(|p| *p = !*p)),
)
.child(text("Restart").on_press(move || player.seek(0.0)))
.child(text(move || time.get())),
)
}import { signal, Text, Video, type VideoRef, View } from "nsh";
function Player() {
const paused = signal(true);
const time = signal("");
let video!: VideoRef;
return (
<View style={{ gap: 12 }}>
<Video
ref={video}
src="https://example.com/clip.mp4"
paused={paused()}
repeat
style={{ height: 220, backgroundColor: "#000000" }}
onProgress={(e) => time.set(`${e.currentTime.toFixed(1)} / ${e.duration.toFixed(1)} s`)}
/>
<View style={{ flexDirection: "row", gap: 16 }}>
<Text onPress={() => paused.set(!paused())}>{paused() ? "Play" : "Pause"}</Text>
<Text onPress={() => video.seek(0)}>Restart</Text>
<Text>{time()}</Text>
</View>
</View>
);
}TSX में src आवश्यक है (Video API देखें)। वेब व्यू और वीडियो गाइड भी देखें।
#Props
वीडियो एक व्यू के props लेता है (इसमें कोई children नहीं होते) और साथ में ये भी:
| Prop | Rust | प्रकार | डिफ़ॉल्ट | विवरण |
|---|---|---|---|---|
src | video(url) | string | वीडियो का URL (Rust में किसी asset का uri भी: video(CLIP.uri))। | |
paused | .paused(..) | boolean | false | प्लेबैक रुका हुआ है। |
muted | .muted(..) | boolean | false | कोई ध्वनि नहीं। |
volume | .volume(..) | number | 1 | 0..1। |
rate | .rate(..) | number | 1 | प्लेबैक की गति। |
repeat | .repeat(..) | boolean | false | अंत में शुरुआत से दोबारा चलता है। |
controls | .controls(..) | boolean | false | प्लेटफ़ॉर्म के प्लेबैक नियंत्रण दिखाता है। |
objectFit (style), object-* classes | .object_fit(ResizeMode::Contain) | "cover" | "contain" | "fill" | "none" | "scale-down" | "contain" | वीडियो अपने बॉक्स में कैसे समाए, CSS के object-fit (class="object-cover") की तरह। iOS, macOS और Android "none" और "scale-down" को "contain" की तरह चलाते हैं, Linux दोनों को "scale-down" की तरह। |
progressUpdateInterval | .progress_interval(ms) | number | 250 | onProgress इवेंट के बीच मिलीसेकंड। |
वीडियो को उसकी styles के ज़रिए आकार दें; उसका आकार वही होता है जो उसकी styles तय करती हैं। जब आप वीडियो का अपना अनुपात चाहते हैं, तो onLoad द्वारा बताए गए आकार से aspect ratio सेट करें।
#इवेंट
| इवेंट | Rust | Payload | कब |
|---|---|---|---|
onLoadStart | .on_load_start | कोई नहीं | लोडिंग शुरू हुई। |
onLoad | .on_load | { duration, size } (VideoInfo: duration, size) | वीडियो तैयार है: उसकी अवधि सेकंड में और उसका आकार पिक्सेल में। |
onProgress | .on_progress | { currentTime, duration } (VideoProgress: current_time, duration) | चलते समय हर progressUpdateInterval पर (सेकंड में)। |
onBuffer | .on_buffer | { isBuffering } (bool) | डेटा के लिए प्लेबैक रुका / फिर शुरू हुआ। |
onEnd | .on_end | कोई नहीं | प्लेबैक अंत तक पहुँचा (repeat के साथ, हर लूप के अंत में)। |
onError | .on_error | { message, code? } (&str, संदेश) | लोडिंग या प्लेबैक विफल रहा; ऐसे स्रोत के लिए code का मान "ERR_ACCESS_DENIED" होता है जिसकी अनुमति ऐप की capabilities नहीं देतीं। |
let ratio = signal(16.0_f32 / 9.0);
video(url)
.w_full()
.aspect_ratio(ratio)
.on_load(move |info: &VideoInfo| {
ratio.set(info.size.width.0 as f32 / info.size.height.0 as f32)
})const ratio = signal(16 / 9);
<Video src={url} style={{ width: "100%", aspectRatio: ratio() }} onLoad={(e) => ratio.set(e.size.width / e.size.height)} />;#Ref मेथड
| मेथड | Rust | विवरण |
|---|---|---|
seek(seconds) | seek(seconds) | किसी समय पर जाता है। |
#Capabilities
जब ऐप capabilities घोषित करता है, तो वीडियो स्रोत उन होस्ट से लोड होते हैं जिन्हें network अनुमति देता है; किसी अन्य होस्ट का स्रोत खाली व्यू दिखाता है और code "ERR_ACCESS_DENIED" के साथ onError रिपोर्ट करता है।
#प्लेटफ़ॉर्म नोट्स
- iOS वीडियो को
AVPlayerLayerमें बनाता है, याcontrolsके साथ सिस्टम प्लेयर व्यू में; AVFoundation पहली बार वीडियो बनाए जाने पर लोड होता है। - Android, TextureView पर MediaPlayer से चलाता है, और
controlsके लिए MediaController का उपयोग करता है।