इस पेज का अनुवाद मशीन द्वारा किया गया है और इसमें त्रुटियाँ हो सकती हैं। मूल अंग्रेज़ी पढ़ें

एलिमेंट

Video

वीडियो व्यू प्लेटफ़ॉर्म के प्लेयर से वीडियो फ़ाइल या स्ट्रीम चलाता है: iOS और Mac Catalyst पर AVPlayer, Android पर MediaPlayer। प्लेबैक को props (paused, muted, rate, …) से नियंत्रित किया जाता है, इसलिए प्लेयर किसी भी अन्य व्यू की तरह आपकी state का अनुसरण करता है। Rust में इसे video(url) से बनाया जाता है, TSX में nsh से <Video> द्वारा।

el-video.tsx
el-video.tsxLive
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())),
        )
}

TSX में src आवश्यक है (Video API देखें)। वेब व्यू और वीडियो गाइड भी देखें।

#Props

वीडियो एक व्यू के props लेता है (इसमें कोई children नहीं होते) और साथ में ये भी:

PropRustप्रकारडिफ़ॉल्टविवरण
srcvideo(url)stringवीडियो का URL (Rust में किसी asset का uri भी: video(CLIP.uri))।
paused.paused(..)booleanfalseप्लेबैक रुका हुआ है।
muted.muted(..)booleanfalseकोई ध्वनि नहीं।
volume.volume(..)number10..1।
rate.rate(..)number1प्लेबैक की गति।
repeat.repeat(..)booleanfalseअंत में शुरुआत से दोबारा चलता है।
controls.controls(..)booleanfalseप्लेटफ़ॉर्म के प्लेबैक नियंत्रण दिखाता है।
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)number250onProgress इवेंट के बीच मिलीसेकंड।

वीडियो को उसकी styles के ज़रिए आकार दें; उसका आकार वही होता है जो उसकी styles तय करती हैं। जब आप वीडियो का अपना अनुपात चाहते हैं, तो onLoad द्वारा बताए गए आकार से aspect ratio सेट करें।

#इवेंट

इवेंटRustPayloadकब
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)
    })

#Ref मेथड

मेथडRustविवरण
seek(seconds)seek(seconds)किसी समय पर जाता है।

#Capabilities

जब ऐप capabilities घोषित करता है, तो वीडियो स्रोत उन होस्ट से लोड होते हैं जिन्हें network अनुमति देता है; किसी अन्य होस्ट का स्रोत खाली व्यू दिखाता है और code "ERR_ACCESS_DENIED" के साथ onError रिपोर्ट करता है।

#प्लेटफ़ॉर्म नोट्स

  • iOS वीडियो को AVPlayerLayer में बनाता है, या controls के साथ सिस्टम प्लेयर व्यू में; AVFoundation पहली बार वीडियो बनाए जाने पर लोड होता है।
  • Android, TextureView पर MediaPlayer से चलाता है, और controls के लिए MediaController का उपयोग करता है।