-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
/
main.rs
132 lines (113 loc) · 4.34 KB
/
main.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// Copyright 2019-2022 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
#![cfg_attr(
all(not(debug_assertions), target_os = "windows"),
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())
}