mirror of
https://github.com/tauri-apps/tauri.git
synced 2024-12-21 01:32:03 +03:00
130 lines
4.3 KiB
Rust
130 lines
4.3 KiB
Rust
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
|
|
|
fn main() {
|
|
use std::{
|
|
cmp::min,
|
|
io::{Read, Seek, SeekFrom},
|
|
path::PathBuf,
|
|
process::{Command, Stdio},
|
|
};
|
|
use tauri::http::{HttpRange, ResponseBuilder};
|
|
|
|
let video_file = PathBuf::from("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());
|
|
}
|
|
|
|
tauri::Builder::default()
|
|
.invoke_handler(tauri::generate_handler![video_uri])
|
|
.register_uri_scheme_protocol("stream", move |_app, request| {
|
|
// prepare our response
|
|
let mut response = ResponseBuilder::new();
|
|
// get the file path
|
|
let path = request.uri().strip_prefix("stream://localhost/").unwrap();
|
|
let path = percent_encoding::percent_decode(path.as_bytes())
|
|
.decode_utf8_lossy()
|
|
.to_string();
|
|
|
|
if path != "example/test_video.mp4" {
|
|
// return error 404 if it's not out video
|
|
return response.mimetype("text/plain").status(404).body(Vec::new());
|
|
}
|
|
|
|
// read our file
|
|
let mut content = std::fs::File::open(&video_file)?;
|
|
let mut buf = Vec::new();
|
|
|
|
// default status code
|
|
let mut status_code = 200;
|
|
|
|
// if the webview sent a range header, we need to send a 206 in return
|
|
// Actually only macOS and Windows are supported. Linux will ALWAYS return empty headers.
|
|
if let Some(range) = request.headers().get("range") {
|
|
// Get the file size
|
|
let file_size = content.metadata().unwrap().len();
|
|
|
|
// we parse the range header with tauri helper
|
|
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 = min(file_size - range.start, 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;
|
|
// partial content
|
|
status_code = 206;
|
|
|
|
// Only macOS and Windows are supported, if you set headers in linux they are ignored
|
|
response = response
|
|
.header("Connection", "Keep-Alive")
|
|
.header("Accept-Ranges", "bytes")
|
|
.header("Content-Length", real_length)
|
|
.header(
|
|
"Content-Range",
|
|
format!("bytes {}-{}/{}", range.start, last_byte, file_size),
|
|
);
|
|
|
|
// FIXME: Add ETag support (caching on the webview)
|
|
|
|
// 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)?;
|
|
}
|
|
}
|
|
|
|
response.mimetype("video/mp4").status(status_code).body(buf)
|
|
})
|
|
.run(tauri::generate_context!(
|
|
"../../examples/streaming/tauri.conf.json"
|
|
))
|
|
.expect("error while running tauri application");
|
|
}
|
|
|
|
// returns the scheme and the path of the video file
|
|
// we're using this just to allow using the custom `stream` protocol or tauri built-in `asset` protocol
|
|
#[tauri::command]
|
|
fn video_uri() -> (&'static str, std::path::PathBuf) {
|
|
#[cfg(feature = "protocol-asset")]
|
|
{
|
|
let mut path = std::env::current_dir().unwrap();
|
|
path.push("test_video.mp4");
|
|
("asset", path)
|
|
}
|
|
|
|
#[cfg(not(feature = "protocol-asset"))]
|
|
("stream", "example/test_video.mp4".into())
|
|
}
|