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

दस्तावेज़

वेब व्यू और वीडियो

Web view और video player प्लैटफ़ॉर्म के अपने web engine और media player को सामान्य views के रूप में embed करते हैं: Rust में web_view और video (nsh_core::prelude), TSX में nsh से WebView और Video। NSH native views से render करता है, इसलिए ये आपके layout में किसी भी अन्य element की तरह रहते हैं: ये अपने parent के साथ scroll होते हैं, rounded corners और clipping स्वीकार करते हैं, और अन्य views layering की समस्या के बिना इन पर overlap हो सकते हैं।

ComponentiOSAndroidmacOSLinuxWindows
WebViewWKWebViewandroid.webkit.WebViewWKWebViewWebKitGTK 6.0 का WebKitWebViewWebView2, एक child window में
VideoAVPlayerLayer; controls के साथ AVPlayerViewControllerTextureView पर MediaPlayer; controls के साथ MediaControllerAVPlayerLayer; controls के साथ AVKit का AVPlayerViewGtkPicture में GTK का media stream (GStreamer); controls के साथ GtkVideoMedia Foundation का media engine, frames Direct2D से बनाए जाते हैं; controls के साथ NSH का अपना control bar

इनके पीछे के frameworks (WebKit, AVFoundation) तब लोड होते हैं जब कोई app पहली बार इनका उपयोग करता है, इसलिए जो apps कभी web page या video नहीं दिखाते, उन्हें launch पर इनकी कोई कीमत नहीं चुकानी पड़ती।

macOS पर web view का अपना कोई scroll view नहीं होता: scrollable={false} के साथ माउस व्हील और ट्रैकपैड पेज के बजाय उसके आसपास के app के scroll views को scroll करते हैं; पेज स्वयं अब भी अपने कीबोर्ड और अपनी scripts से scroll होता है।

Linux पर web view एक WebKitGTK पेज है जो अपने view के भीतर बनाया जाता है: उसके ऊपर NSH की सामग्री (dialog, sheet) उसे ढक लेती है, और उसके parents उसे clip करते हैं। webview capability वहाँ frames के navigations पर भी लागू होती है (जिस host को अनुमति नहीं है, उसका iframe खाली रहता है), और scrollable: false पेज को स्वयं scroll होने से रोकता है (उसके भीतर के elements फिर भी scroll होते हैं)। Apps के लिए WebKitGTK 6.0 इंस्टॉल होना आवश्यक है; उसके बिना बना runtime एक सादा placeholder दिखाता है। Videos वे formats चलाते हैं जिन्हें इंस्टॉल किए गए GStreamer plugins decode करते हैं; rate समर्थित नहीं है (वे 1× पर चलते हैं), और onEnd केवल उन videos के लिए आता है जो loop नहीं होते।

Windows पर web view एक child window में WebView2 control होता है जो अपने view का अनुसरण करता है: उसे उसके parents और rounded corners से clip किया जाता है, पर उसके ऊपर बनी NSH की सामग्री (dialog, sheet) उसके नीचे ही रहती है। इसके लिए WebView2 Runtime आवश्यक है (Windows 11 का हिस्सा; Windows 10 पर Edge के साथ इंस्टॉल होता है), अन्यथा यह एक placeholder दिखाता है; HTML के लिए baseUrl, scrollable और allowsBackForwardNavigationGestures समर्थित नहीं हैं। Videos सामान्य सामग्री हैं (किसी भी view की तरह clip, transform और cover किए जाते हैं) और वे formats चलाते हैं जिन्हें इंस्टॉल किए गए Media Foundation codecs decode करते हैं; rate काम करता है, कोई poster नहीं है, और controls चालू होने पर control bar हमेशा दिखता है।

#WebView

use nsh_core::prelude::*;

#[component]
pub fn App() -> impl IntoElement {
    web_view("https://en.m.wikipedia.org/wiki/Swift_(programming_language)").flex_1()
}

#स्रोत

Web view src और html में से एक दिखाता है:

Propsक्या दिखाता है
src="https://…"एक web page।
html="<!doctype html>…"एक string से HTML, जो src के बजाय दिखाया जाता है।
baseUrl="https://example.com/" के साथ htmlऐसा HTML जिसके relative URLs baseUrl के विरुद्ध resolve होते हैं।

Rust में, web_view(url) एक page दिखाता है और web_view_html(html, base_url) HTML दिखाता है (base_url एक Option<&str>); URL और HTML एक signal या closure हो सकते हैं, जो उसके पढ़े गए मान बदलने पर page को फिर से load करता है। src या html बदलने से नया page load होता है।

#Props

Rust में हर prop snake case में एक builder method है (injected_java_script, java_script_enabled, scrollable, user_agent; injectedJavaScriptBeforeContentLoaded के लिए injected_java_script_before_content, iOS के allowsBackForwardNavigationGestures के लिए back_forward_gestures) और हर event एक on_… method है (on_navigation_state_change) जिसका listener एक WebNavigation पाता है (url, title, loading, can_go_back, can_go_forward) या, on_error और on_message के लिए, एक &str।

PropTypeDefaultविवरण
srcstringदिखाए जाने वाले page का URL।
htmlstringInline HTML, जो src के बजाय दिखाया जाता है।
baseUrlstringhtml के relative URLs किसके विरुद्ध resolve होते हैं।
injectedJavaScriptstringहर page के load होने के बाद चलता है।
injectedJavaScriptBeforeContentLoadedstringहर page की अपनी scripts से पहले चलता है।
javaScriptEnabledbooleantruePages scripts चलाते हैं।
scrollablebooleantrueउपयोगकर्ता page को scroll कर सकता है।
userAgentstringBrowser की user agent string।
ios={{ allowsBackForwardNavigationGestures }}booleaniOS: page history में swipe करके पीछे और आगे जाएँ।
desktop={{ allowsBackForwardNavigationGestures }}booleanmacOS: पीछे और आगे जाने के लिए trackpad पर दो उँगलियों से swipe करें; Windows: touchscreen पर swipe करें; Linux: touchpad पर swipe करें।

Events:

EventPayloadकब
onLoadStartWebViewNavigationएक page का loading शुरू हुआ।
onLoadWebViewNavigationएक page का loading पूरा हुआ।
onError{ message }Loading विफल हुआ।
onNavigationStateChangeWebViewNavigationURL, title, loading state या history बदली।
onMessage{ data: string }Page ने एक message post किया (नीचे)।

WebViewNavigation { url, title, loading, canGoBack, canGoForward } है।

#Back और forward वाला browser

Web view का ref (Rust में .element_ref(&web) से उस पर लगाया गया element_ref(), TSX में ref) में reload, go_back / goBack, go_forward / goForward, stop_loading / stopLoading, inject_java_script / injectJavaScript(script), post_message / postMessage(data) और send_input / sendInput(input) हैं (नीचे):

use nsh_core::prelude::*;

pub fn browser() -> impl IntoElement {
    let web = element_ref();
    let nav = signal(None::<WebNavigation>);
    let button = move |label: &'static str, enabled: fn(&WebNavigation) -> bool, action: fn(&ElementRef)| {
        let enabled = move || nav.with(|n| n.as_ref().is_some_and(enabled));
        text(label)
            .p(px(12.0))
            .text_color(move || if enabled() { rgb(0x0969da) } else { rgb(0x8c959f) })
            .disabled(move || !enabled())
            .on_press(move || action(&web))
    };
    view()
        .flex_1()
        .pt(px(50.0))
        .child(
            text(move || {
                nav.with(|n| match n {
                    Some(n) if n.loading => "Loading…".to_owned(),
                    Some(n) => n.title.clone(),
                    None => String::new(),
                })
            })
            .number_of_lines(1)
            .px(px(12.0))
            .text_color(rgb(0x57606a)),
        )
        .child(
            web_view("https://developer.mozilla.org/")
                .element_ref(&web)
                .flex_1()
                .on_navigation_state_change(move |n: &WebNavigation| nav.set(Some(n.clone())))
                .back_forward_gestures(true),
        )
        .child(
            view()
                .flex_row()
                .justify_around()
                .child(button("Back", |n| n.can_go_back, |web| web.go_back()))
                .child(button("Forward", |n| n.can_go_forward, |web| web.go_forward()))
                .child(button("Reload", |_| true, |web| web.reload())),
        )
}

#Page और app के बीच messaging

Pages window.nsh.postMessage(string) से app से बात करते हैं, जो on_message (onMessage) के रूप में पहुँचता है। App ref के post_message (postMessage(string)) से page से बात करता है, जो page में window पर message event के रूप में पहुँचता है। Messages strings होते हैं; संरचित डेटा के लिए JSON भेजें।

use nsh_core::prelude::*;

const PAGE: &str = r#"<!doctype html>
<html><head><meta name="viewport" content="width=device-width"></head>
<body style="font-family: -apple-system, sans-serif; margin: 16px">
  <button onclick="window.nsh.postMessage(JSON.stringify({ type: 'hello', at: Date.now() }))">
    Post to the app
  </button>
  <p id="got">Nothing from the app yet.</p>
  <script>
    window.addEventListener("message", (e) => {
      document.getElementById("got").textContent = "The app says: " + e.data;
    });
  </script>
</body></html>"#;

#[component]
pub fn App() -> impl IntoElement {
    let web = element_ref();
    let last = signal(String::new());
    view()
        .flex_1()
        .pt(px(50.0))
        .gap(px(12.0))
        .child(
            web_view_html(PAGE, None)
                .element_ref(&web)
                .h(px(240.0))
                .on_message(move |data: &str| {
                    let message: serde_json::Value = serde_json::from_str(data).unwrap_or_default();
                    last.set(message["type"].as_str().unwrap_or_default().to_owned());
                }),
        )
        .child(
            text("Post to the page")
                .p(px(16.0))
                .text_color(rgb(0x0969da))
                .on_press(move || web.post_message("hi from nsh")),
        )
        .child(text(move || format!("Last message: {}", last.get())).px(px(16.0)))
}

Ref का inject_java_script (injectJavaScript(script)) किसी भी समय मौजूदा page में code चलाता है; injected_java_script और injected_java_script_before_content (injectedJavaScript, injectedJavaScriptBeforeContentLoaded) हर page load पर, page की अपनी scripts से पहले या बाद में, चलते हैं, जो hooks इंस्टॉल करने की जगह है:

web_view("https://example.com")
    .injected_java_script_before_content("window.isNativeApp = true;")
    .injected_java_script("window.nsh.postMessage(document.title);")
    .on_message(|title: &str| log::info!("title {title}"))

#उपयोगकर्ता के अपने input के रूप में input

injectJavaScript केवल synthetic events बना सकता है, जिन्हें page untrusted मानता है (event.isTrusted false होता है) और कुछ widgets उन्हें अनदेखा कर देते हैं। Ref का send_input (sendInput(input)) page को वैसा input देता है जैसा उपयोगकर्ता के अपने हाथ देते, इसलिए उसके events trusted होते हैं:

  • { type: "click", x, y } viewport के एक बिंदु पर click करता है, CSS pixels में (जो getBoundingClientRect() बताता है)।
  • { type: "text", text } page के focus वाले element में text टाइप करता है, एक बार में एक key: हर अक्षर US layout की उसकी key से, line break Enter के रूप में, और जो कोई US key टाइप नहीं करती (é, CJK, emoji) वह किसी key के बिना (code "Unidentified" होता है)।
  • { type: "key", key } Enter, Tab, Escape या Backspace दबाता है।
use nsh_core::{WebInput, WebKey, prelude::*};
use std::time::Duration;

fn search(web: &ElementRef) {
    web.send_input(WebInput::Click { x: px(120.0), y: px(40.0) });
    let web = *web;
    set_timeout(Duration::from_millis(50), move || {
        web.send_input(WebInput::Text("espresso".into()));
        web.send_input(WebInput::Key(WebKey::Enter));
    });
}

Rust में ये WebInput::Click { x, y }, WebInput::Text(text) और WebInput::Key(WebKey::Enter) हैं।

Trusted input में user activation होती है (यह windows खोल सकता है, clipboard में लिख सकता है, system का file panel खोल सकता है या किसी page का confirm बटन दबा सकता है), इसलिए यह अपने आप में एक अलग capability है: app को nsh.json के capabilities में "webview": { "input": true } चाहिए (webview), तब भी जब वह कोई capability घोषित नहीं करता, अन्यथा core call को छोड़ देता है।

Click की तरह, sendInput keyboard focus को app के अपने Input से हटाकर web view को दे देता है। Page input को अपनी गति से संभालता है: जब अगला call शुरू होता है तब भी किसी click के events रास्ते में हो सकते हैं, इसलिए उसके बाद टाइप करने से पहले click को थमने दें (लगभग एक frame)। macOS input पहुँचाता है; अन्य platforms अभी इसे अनदेखा करते हैं।

इस पर निर्भर कुछ भी देने से पहले पूछें कि क्या यह यहाँ काम करता है: web_input_available() (WebView.inputAvailable()) केवल वहीं true resolve होता है जहाँ platform input पहुँचाता है और app के पास webview.input है, इसलिए जो platforms call को अनदेखा करते हैं और जिन apps के पास grant नहीं है, उन पर यह false होता है।

use nsh_core::prelude::*;

let input = signal(false);
spawn(async move { input.set(web_input_available().await) });

#Web view कौन-सी sites दिखा सकता है

यदि आपका nsh.json "capabilities" घोषित करता है, तो web view केवल webview के अंतर्गत सूचीबद्ध hosts को दिखा सकता है और उन पर navigate कर सकता है:

{
  "capabilities": {
    "webview": { "hosts": ["example.com", "*.example.com"] }
  }
}

यह जाँच web view के src और page द्वारा किए गए हर top-level navigation (links, redirects, location बदलाव) को कवर करती है; इसका निर्णय native स्तर पर तब होता है जब platform पूछता है कि क्या navigation आगे बढ़ सकता है। Page के अपने subresources (scripts, images, frames) web content का अपना मामला हैं और उनकी जाँच नहीं की जाती। *.example.com केवल subdomains से मेल खाता है; bare domain के लिए example.com भी सूचीबद्ध करें। जिस app में "capabilities" key नहीं है वह कोई भी page दिखा सकता है। देखें Sandbox and capabilities।

#Video

use nsh_core::prelude::*;

pub fn player() -> impl IntoElement {
    let player = element_ref();
    let paused = signal(true);
    let time = signal(String::new());
    let link = |label: &'static str| text(label).text_color(rgb(0x0969da));
    view()
        .flex_1()
        .justify_center()
        .p(px(16.0))
        .gap(px(12.0))
        .child(
            video("https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4")
                .element_ref(&player)
                .paused(paused)
                .repeat(true)
                .object_fit(ResizeMode::Contain)
                .h(px(220.0))
                .rounded(px(12.0))
                .overflow_hidden()
                .bg(rgb(0x000000))
                .on_progress(move |e: &VideoProgress| {
                    time.set(format!("{:.1} / {:.1} s", e.current_time, e.duration))
                })
                .on_end(move || paused.set(true)),
        )
        .child(
            view()
                .flex_row()
                .gap(px(16.0))
                .items_center()
                .child(
                    text(move || if paused.get() { "Play" } else { "Pause" })
                        .text_color(rgb(0x0969da))
                        .font_weight(FontWeight::SEMIBOLD)
                        .on_press(move || paused.update(|p| *p = !*p)),
                )
                .child(link("Restart").on_press(move || player.seek(0.0)))
                .child(text(move || time.get()).text_color(rgb(0x57606a))),
        )
}

Playback props से नियंत्रित होता है: paused, muted, volume और rate को signals से सेट करें और player उनका अनुसरण करता है। Ref में seek(seconds) है।

Rust में, video(url) player बनाता है (URL एक मान, signal या closure) और हर prop snake case में एक builder method है (paused, repeat(true), object_fit(ResizeMode::Contain) (objectFit style), progressUpdateInterval के लिए progress_interval); on_load को एक VideoInfo मिलता है (duration, size), on_progress को एक VideoProgress (current_time, duration) और on_buffer को एक bool।

PropTypeDefaultविवरण
srcstringVideo का URL।
pausedbooleanfalsePlayback रुका हुआ है।
mutedbooleanfalseकोई आवाज़ नहीं।
volumenumber10..1।
ratenumber1Playback की गति।
repeatbooleanfalseअंत पर शुरू से फिर चलता है।
objectFit (style), object-* classes"cover" | "contain" | "fill" | "none" | "scale-down""contain"चित्र view में कैसे fit होता है, CSS के object-fit की तरह (class="object-cover")।
controlsbooleanfalsePlatform के playback controls।
progressUpdateIntervalnumber250onProgress events के बीच मिलीसेकंड।
EventPayloadकब
onLoadStartLoading शुरू हुआ।
onLoad{ duration, size }Video तैयार है: सेकंड में उसकी लंबाई और उसका आकार।
onProgress{ currentTime, duration }चलते समय, हर progressUpdateInterval ms पर।
onBuffer{ isBuffering }Playback डेटा की प्रतीक्षा में रुका, या फिर शुरू हुआ।
onEndPlayback अंत तक पहुँचा।
onError{ message }Loading या playback विफल हुआ।

controls के साथ, platform अपना play बटन, scrubber और full-screen बटन खुद बनाता है। इसके बिना, video के ऊपर या नीचे views से अपने बनाएँ; चूँकि video आपके layout में एक native view है, overlays स्वाभाविक रूप से काम करते हैं:

view()
    .h(px(220.0))
    .child(video(url).paused(paused).flex_1())
    .child(
        view()
            .absolute()
            .inset_0()
            .items_center()
            .justify_center()
            .on_press(move || paused.update(|p| *p = !*p))
            .child(when(move || paused.get(), || {
                text("▶").text_size(px(48.0)).text_color(rgb(0xffffff))
            })),
    )

Video sources network requests हैं: घोषित capabilities के साथ, video का host nsh.json में network के अंतर्गत अनुमत होना चाहिए, जैसा fetch और remote images के लिए होता है। अस्वीकृत source कुछ नहीं दिखाता, और अस्वीकृति एक बार log की जाती है, साथ में जोड़ने वाली key भी।

#संबंधित