wry 0.24.12

Cross-platform WebView rendering library
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

use wry::http::Response;

fn main() -> wry::Result<()> {
  use http_range::HttpRange;
  use std::{
    fs::{canonicalize, File},
    io::{Read, Seek, SeekFrom},
    path::PathBuf,
    process::{Command, Stdio},
  };
  use wry::{
    application::{
      event::{Event, StartCause, WindowEvent},
      event_loop::{ControlFlow, EventLoop},
      window::WindowBuilder,
    },
    http::{header::CONTENT_TYPE, status::StatusCode},
    webview::WebViewBuilder,
  };

  let video_file = PathBuf::from("examples/test_video.mp4");
  let video_url =
    "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4";

  if !video_file.exists() {
    // Downloading with curl this saves us from adding
    // a Rust HTTP client dependency.
    println!("Downloading {}", video_url);
    let status = Command::new("curl")
      .arg("-L")
      .arg("-o")
      .arg(&video_file)
      .arg(video_url)
      .stdout(Stdio::inherit())
      .stderr(Stdio::inherit())
      .output()
      .unwrap();

    assert!(status.status.success());
    assert!(video_file.exists());
  }

  let event_loop = EventLoop::new();
  let window = WindowBuilder::new()
    .with_title("Hello World")
    .build(&event_loop)
    .unwrap();

  let _webview = WebViewBuilder::new(window)
    .unwrap()
    .with_custom_protocol("wry".into(), move |request| {
      // remove leading slash
      let path = &request.uri().path()[1..];

      // Read the file content from file path
      let mut content = File::open(canonicalize(path)?)?;

      // Return asset contents and mime types based on file extentions
      // If you don't want to do this manually, there are some crates for you.
      // Such as `infer` and `mime_guess`.
      let mut status_code = StatusCode::OK;
      let mut buf = Vec::new();

      // guess our mimetype from the path
      let mimetype = if path.ends_with(".html") {
        "text/html"
      } else if path.ends_with(".mp4") {
        "video/mp4"
      } else {
        unimplemented!();
      };

      // prepare our http response
      let mut response = Response::builder();

      // read our range header if it exist, so we can return partial content
      if let Some(range) = request.headers().get("range") {
        // Get the file size
        let file_size = content.metadata().unwrap().len();

        // we parse the range header
        let range = HttpRange::parse(range.to_str().unwrap(), file_size).unwrap();

        // let support only 1 range for now
        let first_range = range.first();
        if let Some(range) = first_range {
          let mut real_length = range.length;

          // prevent max_length;
          // specially on webview2
          if range.length > file_size / 3 {
            // max size sent (400ko / request)
            // as it's local file system we can afford to read more often
            real_length = 1024 * 400;
          }

          // last byte we are reading, the length of the range include the last byte
          // who should be skipped on the header
          let last_byte = range.start + real_length - 1;
          status_code = StatusCode::PARTIAL_CONTENT;

          response = response.header("Connection", "Keep-Alive");
          response = response.header("Accept-Ranges", "bytes");
          // we need to overwrite our content length
          response = response.header("Content-Length", real_length);
          response = response.header(
            "Content-Range",
            format!("bytes {}-{}/{}", range.start, last_byte, file_size),
          );

          // seek our file bytes
          content.seek(SeekFrom::Start(range.start))?;
          content.take(real_length).read_to_end(&mut buf)?;
        } else {
          content.read_to_end(&mut buf)?;
        }
      } else {
        content.read_to_end(&mut buf)?;
      }

      response
        .header(CONTENT_TYPE, mimetype)
        .status(status_code)
        .body(buf.into())
        .map_err(Into::into)
    })
    // tell the webview to load the custom protocol
    .with_url("wry://localhost/examples/stream.html")?
    .build()?;

  event_loop.run(move |event, _, control_flow| {
    *control_flow = ControlFlow::Wait;

    match event {
      Event::NewEvents(StartCause::Init) => println!("Wry application started!"),
      Event::WindowEvent {
        event: WindowEvent::CloseRequested,
        ..
      } => *control_flow = ControlFlow::Exit,
      _ => {}
    }
  });
}