main.rs 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. // Copyright 2019-2024 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
  5. use http::{header::*, response::Builder as ResponseBuilder, status::StatusCode};
  6. use http_range::HttpRange;
  7. use std::{
  8. io::{Read, Seek, SeekFrom, Write},
  9. path::PathBuf,
  10. process::{Command, Stdio},
  11. };
  12. fn get_stream_response(
  13. request: http::Request<Vec<u8>>,
  14. ) -> Result<http::Response<Vec<u8>>, Box<dyn std::error::Error>> {
  15. // skip leading `/`
  16. let path = percent_encoding::percent_decode(request.uri().path()[1..].as_bytes())
  17. .decode_utf8_lossy()
  18. .to_string();
  19. // return error 404 if it's not our video
  20. if path != "streaming_example_test_video.mp4" {
  21. return Ok(ResponseBuilder::new().status(404).body(Vec::new())?);
  22. }
  23. let mut file = std::fs::File::open(&path)?;
  24. // get file length
  25. let len = {
  26. let old_pos = file.stream_position()?;
  27. let len = file.seek(SeekFrom::End(0))?;
  28. file.seek(SeekFrom::Start(old_pos))?;
  29. len
  30. };
  31. let mut resp = ResponseBuilder::new().header(CONTENT_TYPE, "video/mp4");
  32. // if the webview sent a range header, we need to send a 206 in return
  33. let http_response = if let Some(range_header) = request.headers().get("range") {
  34. let not_satisfiable = || {
  35. ResponseBuilder::new()
  36. .status(StatusCode::RANGE_NOT_SATISFIABLE)
  37. .header(CONTENT_RANGE, format!("bytes */{len}"))
  38. .body(vec![])
  39. };
  40. // parse range header
  41. let ranges = if let Ok(ranges) = HttpRange::parse(range_header.to_str()?, len) {
  42. ranges
  43. .iter()
  44. // map the output back to spec range <start-end>, example: 0-499
  45. .map(|r| (r.start, r.start + r.length - 1))
  46. .collect::<Vec<_>>()
  47. } else {
  48. return Ok(not_satisfiable()?);
  49. };
  50. /// The Maximum bytes we send in one range
  51. const MAX_LEN: u64 = 1000 * 1024;
  52. if ranges.len() == 1 {
  53. let &(start, mut end) = ranges.first().unwrap();
  54. // check if a range is not satisfiable
  55. //
  56. // this should be already taken care of by HttpRange::parse
  57. // but checking here again for extra assurance
  58. if start >= len || end >= len || end < start {
  59. return Ok(not_satisfiable()?);
  60. }
  61. // adjust end byte for MAX_LEN
  62. end = start + (end - start).min(len - start).min(MAX_LEN - 1);
  63. // calculate number of bytes needed to be read
  64. let bytes_to_read = end + 1 - start;
  65. // allocate a buf with a suitable capacity
  66. let mut buf = Vec::with_capacity(bytes_to_read as usize);
  67. // seek the file to the starting byte
  68. file.seek(SeekFrom::Start(start))?;
  69. // read the needed bytes
  70. file.take(bytes_to_read).read_to_end(&mut buf)?;
  71. resp = resp.header(CONTENT_RANGE, format!("bytes {start}-{end}/{len}"));
  72. resp = resp.header(CONTENT_LENGTH, end + 1 - start);
  73. resp = resp.status(StatusCode::PARTIAL_CONTENT);
  74. resp.body(buf)
  75. } else {
  76. let mut buf = Vec::new();
  77. let ranges = ranges
  78. .iter()
  79. .filter_map(|&(start, mut end)| {
  80. // filter out unsatisfiable ranges
  81. //
  82. // this should be already taken care of by HttpRange::parse
  83. // but checking here again for extra assurance
  84. if start >= len || end >= len || end < start {
  85. None
  86. } else {
  87. // adjust end byte for MAX_LEN
  88. end = start + (end - start).min(len - start).min(MAX_LEN - 1);
  89. Some((start, end))
  90. }
  91. })
  92. .collect::<Vec<_>>();
  93. let boundary = random_boundary();
  94. let boundary_sep = format!("\r\n--{boundary}\r\n");
  95. let boundary_closer = format!("\r\n--{boundary}\r\n");
  96. resp = resp.header(
  97. CONTENT_TYPE,
  98. format!("multipart/byteranges; boundary={boundary}"),
  99. );
  100. for (end, start) in ranges {
  101. // a new range is being written, write the range boundary
  102. buf.write_all(boundary_sep.as_bytes())?;
  103. // write the needed headers `Content-Type` and `Content-Range`
  104. buf.write_all(format!("{CONTENT_TYPE}: video/mp4\r\n").as_bytes())?;
  105. buf.write_all(format!("{CONTENT_RANGE}: bytes {start}-{end}/{len}\r\n").as_bytes())?;
  106. // write the separator to indicate the start of the range body
  107. buf.write_all("\r\n".as_bytes())?;
  108. // calculate number of bytes needed to be read
  109. let bytes_to_read = end + 1 - start;
  110. let mut local_buf = vec![0_u8; bytes_to_read as usize];
  111. file.seek(SeekFrom::Start(start))?;
  112. file.read_exact(&mut local_buf)?;
  113. buf.extend_from_slice(&local_buf);
  114. }
  115. // all ranges have been written, write the closing boundary
  116. buf.write_all(boundary_closer.as_bytes())?;
  117. resp.body(buf)
  118. }
  119. } else {
  120. resp = resp.header(CONTENT_LENGTH, len);
  121. let mut buf = Vec::with_capacity(len as usize);
  122. file.read_to_end(&mut buf)?;
  123. resp.body(buf)
  124. };
  125. http_response.map_err(Into::into)
  126. }
  127. fn random_boundary() -> String {
  128. let mut x = [0_u8; 30];
  129. getrandom::getrandom(&mut x).expect("failed to get random bytes");
  130. (x[..])
  131. .iter()
  132. .map(|&x| format!("{x:x}"))
  133. .fold(String::new(), |mut a, x| {
  134. a.push_str(x.as_str());
  135. a
  136. })
  137. }
  138. fn download_video() {
  139. let video_file = PathBuf::from("streaming_example_test_video.mp4");
  140. if !video_file.exists() {
  141. let video_url =
  142. "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4";
  143. // Downloading with curl this saves us from adding
  144. // a Rust HTTP client dependency.
  145. println!("Downloading {video_url}");
  146. let status = Command::new("curl")
  147. .arg("-L")
  148. .arg("-o")
  149. .arg(&video_file)
  150. .arg(video_url)
  151. .stdout(Stdio::inherit())
  152. .stderr(Stdio::inherit())
  153. .output()
  154. .unwrap();
  155. assert!(status.status.success());
  156. assert!(video_file.exists());
  157. }
  158. }
  159. fn main() {
  160. download_video();
  161. tauri::Builder::default()
  162. .register_asynchronous_uri_scheme_protocol("stream", move |_ctx, request, responder| {
  163. match get_stream_response(request) {
  164. Ok(http_response) => responder.respond(http_response),
  165. Err(e) => responder.respond(
  166. ResponseBuilder::new()
  167. .status(StatusCode::INTERNAL_SERVER_ERROR)
  168. .header(CONTENT_TYPE, "text/plain")
  169. .body(e.to_string().as_bytes().to_vec())
  170. .unwrap(),
  171. ),
  172. }
  173. })
  174. .run(tauri::generate_context!(
  175. "../../examples/streaming/tauri.conf.json"
  176. ))
  177. .expect("error while running tauri application");
  178. }