main.rs 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. // Copyright 2019-2021 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. #![cfg_attr(
  5. all(not(debug_assertions), target_os = "windows"),
  6. windows_subsystem = "windows"
  7. )]
  8. fn main() {
  9. use std::{
  10. io::{Read, Seek, SeekFrom},
  11. path::PathBuf,
  12. process::{Command, Stdio},
  13. };
  14. use tauri::http::{HttpRange, ResponseBuilder};
  15. let video_file = PathBuf::from("test_video.mp4");
  16. let video_url =
  17. "http://distribution.bbb3d.renderfarming.net/video/mp4/bbb_sunflower_1080p_30fps_normal.mp4";
  18. if !video_file.exists() {
  19. // Downloading with curl this saves us from adding
  20. // a Rust HTTP client dependency.
  21. println!("Downloading {}", video_url);
  22. let status = Command::new("curl")
  23. .arg("-L")
  24. .arg("-o")
  25. .arg(&video_file)
  26. .arg(video_url)
  27. .stdout(Stdio::inherit())
  28. .stderr(Stdio::inherit())
  29. .output()
  30. .unwrap();
  31. assert!(status.status.success());
  32. assert!(video_file.exists());
  33. }
  34. tauri::Builder::default()
  35. .register_uri_scheme_protocol("stream", move |_app, request| {
  36. // prepare our response
  37. let mut response = ResponseBuilder::new();
  38. // get the wanted path
  39. let path = request.uri().replace("stream://", "");
  40. if path != "example/test_video.mp4" {
  41. // return error 404 if it's not out video
  42. return response.mimetype("text/plain").status(404).body(Vec::new());
  43. }
  44. // read our file
  45. let mut content = std::fs::File::open(&video_file)?;
  46. let mut buf = Vec::new();
  47. // default status code
  48. let mut status_code = 200;
  49. // if the webview sent a range header, we need to send a 206 in return
  50. // Actually only macOS and Windows are supported. Linux will ALWAYS return empty headers.
  51. if let Some(range) = request.headers().get("range") {
  52. // Get the file size
  53. let file_size = content.metadata().unwrap().len();
  54. // we parse the range header with tauri helper
  55. let range = HttpRange::parse(range.to_str().unwrap(), file_size).unwrap();
  56. // let support only 1 range for now
  57. let first_range = range.first();
  58. if let Some(range) = first_range {
  59. let mut real_length = range.length;
  60. // prevent max_length;
  61. // specially on webview2
  62. if range.length > file_size / 3 {
  63. // max size sent (400ko / request)
  64. // as it's local file system we can afford to read more often
  65. real_length = 1024 * 400;
  66. }
  67. // last byte we are reading, the length of the range include the last byte
  68. // who should be skipped on the header
  69. let last_byte = range.start + real_length - 1;
  70. // partial content
  71. status_code = 206;
  72. // Only macOS and Windows are supported, if you set headers in linux they are ignored
  73. response = response
  74. .header("Connection", "Keep-Alive")
  75. .header("Accept-Ranges", "bytes")
  76. .header("Content-Length", real_length)
  77. .header(
  78. "Content-Range",
  79. format!("bytes {}-{}/{}", range.start, last_byte, file_size),
  80. );
  81. // FIXME: Add ETag support (caching on the webview)
  82. // seek our file bytes
  83. content.seek(SeekFrom::Start(range.start))?;
  84. content.take(real_length).read_to_end(&mut buf)?;
  85. } else {
  86. content.read_to_end(&mut buf)?;
  87. }
  88. }
  89. response.mimetype("video/mp4").status(status_code).body(buf)
  90. })
  91. .run(tauri::generate_context!(
  92. "../../examples/streaming/src-tauri/tauri.conf.json"
  93. ))
  94. .expect("error while running tauri application");
  95. }