Skip to content

Commit 751cb9e

Browse files
committed
update
1 parent 59a68fa commit 751cb9e

File tree

2 files changed

+263
-0
lines changed

2 files changed

+263
-0
lines changed

crates/rs-gui/try-wry/Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,19 @@ path = "examples/try01b.rs"
1919
name = "t02"
2020
path = "examples/try02.rs"
2121

22+
# multiwindow
23+
[[bin]]
24+
name = "t03"
25+
path = "examples/try03.rs"
26+
2227

2328
[dependencies]
29+
getrandom = "0.3.1"
30+
http = "1.2.0"
31+
http-range = "0.1.5"
32+
percent-encoding = "2.3.1"
33+
pollster = "0.4.0"
2434
tao = "0.32.6"
35+
wgpu = "24.0.1"
2536
winit = "0.30.9"
2637
wry = "0.50.3"
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
2+
// SPDX-License-Identifier: Apache-2.0
3+
// SPDX-License-Identifier: MIT
4+
5+
use std::{
6+
io::{Read, Seek, SeekFrom, Write},
7+
path::PathBuf,
8+
};
9+
10+
use http::{StatusCode, header};
11+
use http_range::HttpRange;
12+
use tao::{
13+
event::{Event, WindowEvent},
14+
event_loop::{ControlFlow, EventLoop},
15+
window::WindowBuilder,
16+
};
17+
use wry::{
18+
WebViewBuilder,
19+
http::{Request, Response, header::*},
20+
};
21+
22+
fn main() -> wry::Result<()> {
23+
let event_loop = EventLoop::new();
24+
let window = WindowBuilder::new().build(&event_loop).unwrap();
25+
26+
let builder = WebViewBuilder::new()
27+
.with_custom_protocol("wry".into(), move |_webview_id, request| {
28+
match wry_protocol(request) {
29+
Ok(r) => r.map(Into::into),
30+
Err(e) => http::Response::builder()
31+
.header(CONTENT_TYPE, "text/plain")
32+
.status(500)
33+
.body(e.to_string().as_bytes().to_vec())
34+
.unwrap()
35+
.map(Into::into),
36+
}
37+
})
38+
.with_custom_protocol("stream".into(), move |_webview_id, request| {
39+
match stream_protocol(request) {
40+
Ok(r) => r.map(Into::into),
41+
Err(e) => http::Response::builder()
42+
.header(CONTENT_TYPE, "text/plain")
43+
.status(500)
44+
.body(e.to_string().as_bytes().to_vec())
45+
.unwrap()
46+
.map(Into::into),
47+
}
48+
})
49+
// tell the webview to load the custom protocol
50+
.with_url("wry://localhost");
51+
52+
#[cfg(any(
53+
target_os = "windows",
54+
target_os = "macos",
55+
target_os = "ios",
56+
target_os = "android"
57+
))]
58+
let _webview = builder.build(&window)?;
59+
#[cfg(not(any(
60+
target_os = "windows",
61+
target_os = "macos",
62+
target_os = "ios",
63+
target_os = "android"
64+
)))]
65+
let _webview = {
66+
use tao::platform::unix::WindowExtUnix;
67+
use wry::WebViewBuilderExtUnix;
68+
let vbox = window.default_vbox().unwrap();
69+
builder.build_gtk(vbox)?
70+
};
71+
72+
event_loop.run(move |event, _, control_flow| {
73+
*control_flow = ControlFlow::Wait;
74+
75+
if let Event::WindowEvent { event: WindowEvent::CloseRequested, .. } = event {
76+
*control_flow = ControlFlow::Exit
77+
}
78+
});
79+
}
80+
81+
fn wry_protocol(
82+
request: Request<Vec<u8>>,
83+
) -> Result<http::Response<Vec<u8>>, Box<dyn std::error::Error>> {
84+
let path = request.uri().path();
85+
// Read the file content from file path
86+
let root = PathBuf::from("examples/streaming");
87+
let path = if path == "/" {
88+
"index.html"
89+
} else {
90+
// removing leading slash
91+
&path[1..]
92+
};
93+
let content = std::fs::read(std::fs::canonicalize(root.join(path))?)?;
94+
95+
// Return asset contents and mime types based on file extentions
96+
// If you don't want to do this manually, there are some crates for you.
97+
// Such as `infer` and `mime_guess`.
98+
let mimetype = if path.ends_with(".html") || path == "/" {
99+
"text/html"
100+
} else if path.ends_with(".js") {
101+
"text/javascript"
102+
} else {
103+
unimplemented!();
104+
};
105+
106+
Response::builder()
107+
.header(CONTENT_TYPE, mimetype)
108+
.body(content)
109+
.map_err(Into::into)
110+
}
111+
112+
fn stream_protocol(
113+
request: http::Request<Vec<u8>>,
114+
) -> Result<http::Response<Vec<u8>>, Box<dyn std::error::Error>> {
115+
// skip leading `/`
116+
let path = percent_encoding::percent_decode(request.uri().path()[1..].as_bytes())
117+
.decode_utf8_lossy()
118+
.to_string();
119+
120+
let mut file = std::fs::File::open(path)?;
121+
122+
// get file length
123+
let len = {
124+
let old_pos = file.stream_position()?;
125+
let len = file.seek(SeekFrom::End(0))?;
126+
file.seek(SeekFrom::Start(old_pos))?;
127+
len
128+
};
129+
130+
let mut resp = Response::builder().header(CONTENT_TYPE, "video/mp4");
131+
132+
// if the webview sent a range header, we need to send a 206 in return
133+
// Actually only macOS and Windows are supported. Linux will ALWAYS return empty headers.
134+
let http_response = if let Some(range_header) = request.headers().get("range") {
135+
let not_satisfiable = || {
136+
Response::builder()
137+
.status(StatusCode::RANGE_NOT_SATISFIABLE)
138+
.header(header::CONTENT_RANGE, format!("bytes */{len}"))
139+
.body(vec![])
140+
};
141+
142+
// parse range header
143+
let ranges = if let Ok(ranges) = HttpRange::parse(range_header.to_str()?, len) {
144+
ranges
145+
.iter()
146+
// map the output back to spec range <start-end>, example: 0-499
147+
.map(|r| (r.start, r.start + r.length - 1))
148+
.collect::<Vec<_>>()
149+
} else {
150+
return Ok(not_satisfiable()?);
151+
};
152+
153+
/// The Maximum bytes we send in one range
154+
const MAX_LEN: u64 = 1000 * 1024;
155+
156+
if ranges.len() == 1 {
157+
let &(start, mut end) = ranges.first().unwrap();
158+
159+
// check if a range is not satisfiable
160+
//
161+
// this should be already taken care of by HttpRange::parse
162+
// but checking here again for extra assurance
163+
if start >= len || end >= len || end < start {
164+
return Ok(not_satisfiable()?);
165+
}
166+
167+
// adjust end byte for MAX_LEN
168+
end = start + (end - start).min(len - start).min(MAX_LEN - 1);
169+
170+
// calculate number of bytes needed to be read
171+
let bytes_to_read = end + 1 - start;
172+
173+
// allocate a buf with a suitable capacity
174+
let mut buf = Vec::with_capacity(bytes_to_read as usize);
175+
// seek the file to the starting byte
176+
file.seek(SeekFrom::Start(start))?;
177+
// read the needed bytes
178+
file.take(bytes_to_read).read_to_end(&mut buf)?;
179+
180+
resp = resp.header(CONTENT_RANGE, format!("bytes {start}-{end}/{len}"));
181+
resp = resp.header(CONTENT_LENGTH, end + 1 - start);
182+
resp = resp.status(StatusCode::PARTIAL_CONTENT);
183+
resp.body(buf)
184+
} else {
185+
let mut buf = Vec::new();
186+
let ranges = ranges
187+
.iter()
188+
.filter_map(|&(start, mut end)| {
189+
// filter out unsatisfiable ranges
190+
//
191+
// this should be already taken care of by HttpRange::parse
192+
// but checking here again for extra assurance
193+
if start >= len || end >= len || end < start {
194+
None
195+
} else {
196+
// adjust end byte for MAX_LEN
197+
end = start + (end - start).min(len - start).min(MAX_LEN - 1);
198+
Some((start, end))
199+
}
200+
})
201+
.collect::<Vec<_>>();
202+
203+
let boundary = random_boundary();
204+
let boundary_sep = format!("\r\n--{boundary}\r\n");
205+
let boundary_closer = format!("\r\n--{boundary}\r\n");
206+
207+
resp = resp.header(CONTENT_TYPE, format!("multipart/byteranges; boundary={boundary}"));
208+
209+
for (end, start) in ranges {
210+
// a new range is being written, write the range boundary
211+
buf.write_all(boundary_sep.as_bytes())?;
212+
213+
// write the needed headers `Content-Type` and `Content-Range`
214+
buf.write_all(format!("{CONTENT_TYPE}: video/mp4\r\n").as_bytes())?;
215+
buf.write_all(
216+
format!("{CONTENT_RANGE}: bytes {start}-{end}/{len}\r\n").as_bytes(),
217+
)?;
218+
219+
// write the separator to indicate the start of the range body
220+
buf.write_all("\r\n".as_bytes())?;
221+
222+
// calculate number of bytes needed to be read
223+
let bytes_to_read = end + 1 - start;
224+
225+
let mut local_buf = vec![0_u8; bytes_to_read as usize];
226+
file.seek(SeekFrom::Start(start))?;
227+
file.read_exact(&mut local_buf)?;
228+
buf.extend_from_slice(&local_buf);
229+
}
230+
// all ranges have been written, write the closing boundary
231+
buf.write_all(boundary_closer.as_bytes())?;
232+
233+
resp.body(buf)
234+
}
235+
} else {
236+
resp = resp.header(CONTENT_LENGTH, len);
237+
let mut buf = Vec::with_capacity(len as usize);
238+
file.read_to_end(&mut buf)?;
239+
resp.body(buf)
240+
};
241+
242+
http_response.map_err(Into::into)
243+
}
244+
245+
fn random_boundary() -> String {
246+
let mut x = [0_u8; 30];
247+
getrandom::fill(&mut x).expect("failed to get random bytes");
248+
(x[..]).iter().map(|&x| format!("{x:x}")).fold(String::new(), |mut a, x| {
249+
a.push_str(x.as_str());
250+
a
251+
})
252+
}

0 commit comments

Comments
 (0)