Skip to content

Commit

Permalink
fix: Recording issue on varying resolutions + optimized thumbnail
Browse files Browse the repository at this point in the history
  • Loading branch information
richiemcilroy committed Mar 20, 2024
1 parent 38549df commit c92840d
Show file tree
Hide file tree
Showing 16 changed files with 713 additions and 167 deletions.
676 changes: 591 additions & 85 deletions apps/desktop/src-tauri/Cargo.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions apps/desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ capture = { path = "./src/capture" }
image = "0.24.9"
sentry = "0.32.2"
fix-path-env = { git = "https://github.com/tauri-apps/fix-path-env-rs" }
winit = "0.29.15"
jpeg-encoder = "0.6.0"

[features]
# this feature is used for production builds or when `devPath` points to the filesystem
Expand Down
24 changes: 20 additions & 4 deletions apps/desktop/src-tauri/src/capture/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions apps/desktop/src-tauri/src/capture/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description = "Bindings for cross-platform screen capture."
version = "0.0.1"

[dependencies]
block = "0.1"
cfg-if = "0.1"
libc = "0.2"
winapi = "0.2"
block = "0.1.6"
cfg-if = "1.0.0"
libc = "0.2.153"
winapi = "0.3.9"
4 changes: 1 addition & 3 deletions apps/desktop/src-tauri/src/capture/src/common/dxgi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ pub struct Capturer {
unsafe impl Send for Capturer {}

impl Capturer {
pub fn new(display: Display) -> io::Result<Capturer> {
let width = display.width();
let height = display.height();
pub fn new(display: Display, width: usize, height: usize) -> io::Result<Capturer> {
let inner = dxgi::Capturer::new(&display.0)?;
Ok(Capturer { inner, width, height })
}
Expand Down
6 changes: 3 additions & 3 deletions apps/desktop/src-tauri/src/capture/src/common/quartz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ pub struct Capturer {
unsafe impl Send for Capturer {}

impl Capturer {
pub fn new(display: Display) -> io::Result<Capturer> {
pub fn new(display: Display, width: usize, height: usize) -> io::Result<Capturer> {
let frame = Arc::new(Mutex::new(None));

let f = frame.clone();
let inner = quartz::Capturer::new(
display.0,
display.width(),
display.height(),
width,
height,
quartz::PixelFormat::Argb8888,
Default::default(),
move |inner| {
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src-tauri/src/capture/src/common/x11.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pub struct Capturer(x11::Capturer);
unsafe impl Send for Capturer {}

impl Capturer {
pub fn new(display: Display) -> io::Result<Capturer> {
pub fn new(display: Display, width: usize, height: usize) -> io::Result<Capturer> {
x11::Capturer::new(display.0).map(Capturer)
}

Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src-tauri/src/capture/src/quartz/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ impl Default for Config {
Config {
cursor: true,
letterbox: true,
throttle: 0.0166,
throttle: 0.001,
queue_length: 1
}
}
Expand Down
18 changes: 18 additions & 0 deletions apps/desktop/src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ use ffmpeg_sidecar::{
version::ffmpeg_version,
};

use winit::monitor::{MonitorHandle, VideoMode};

fn main() {
let _ = fix_path_env::fix();

Expand Down Expand Up @@ -117,6 +119,20 @@ fn main() {
..Default::default()
}));

let event_loop = winit::event_loop::EventLoop::new().expect("Failed to create event loop");
let monitor: MonitorHandle = event_loop.primary_monitor().expect("No primary monitor found");
let video_modes: Vec<VideoMode> = monitor.video_modes().collect();

let max_mode = video_modes.iter().max_by_key(|mode| mode.size().width * mode.size().height);

let (max_width, max_height) = if let Some(max_mode) = max_mode {
println!("Maximum resolution: {:?}", max_mode.size());
(max_mode.size().width, max_mode.size().height)
} else {
println!("Failed to determine maximum resolution.");
(0, 0)
};

tauri::Builder::default()
.plugin(tauri_plugin_oauth::init())
.plugin(tauri_plugin_positioner::init())
Expand All @@ -143,6 +159,8 @@ fn main() {
video_uploading_finished: Arc::new(AtomicBool::new(false)),
audio_uploading_finished: Arc::new(AtomicBool::new(false)),
data_dir: Some(data_directory),
max_screen_width: max_width as usize,
max_screen_height: max_height as usize,
};

app.manage(Arc::new(Mutex::new(recording_state)));
Expand Down
76 changes: 45 additions & 31 deletions apps/desktop/src-tauri/src/media.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
use std::io::{ErrorKind::WouldBlock, Error};
use std::time::{Instant, Duration};
use std::path::Path;
use image::{ImageBuffer, Rgba};
use image::{ImageBuffer, Rgba, ImageFormat};
use image::codecs::jpeg::JpegEncoder;

use tokio::io::{AsyncWriteExt};
use tokio::process::{Command, Child, ChildStdin};
Expand Down Expand Up @@ -53,15 +54,25 @@ impl MediaRecorder {
}
}

pub async fn start_media_recording(&mut self, options: RecordingOptions, audio_file_path: &str, video_file_path: &str, screenshot_file_path: &str, custom_device: Option<&str>) -> Result<(), String> {
pub async fn start_media_recording(&mut self, options: RecordingOptions, audio_file_path: &str, video_file_path: &str, screenshot_file_path: &str, custom_device: Option<&str>, max_screen_width: usize, max_screen_height: usize) -> Result<(), String> {
self.options = Some(options.clone());

println!("Custom device: {:?}", custom_device);

let host = cpal::default_host();
let devices = host.devices().expect("Failed to get devices");
let display = Display::primary().map_err(|_| "Failed to find primary display".to_string())?;
let (w, h) = (display.width(), display.height());

let mut w = max_screen_width;
let mut h = max_screen_height;

if max_screen_width > 4000 {
w = max_screen_width / 3;
h = max_screen_height / 3;
} else if max_screen_width > 2000 {
w = max_screen_width / 2;
h = max_screen_height / 2;
}

let adjusted_height = h & !1;
let capture_size = w * adjusted_height * 4;
let (audio_tx, audio_rx) = tokio::sync::mpsc::channel::<Vec<u8>>(2048);
Expand Down Expand Up @@ -273,7 +284,7 @@ impl MediaRecorder {
std::thread::spawn(move || {
println!("Starting video recording capture thread...");

let mut capturer = Capturer::new(Display::primary().expect("Failed to find primary display")).expect("Failed to start capture");
let mut capturer = Capturer::new(Display::primary().expect("Failed to find primary display"), w.try_into().unwrap(), h.try_into().unwrap()).expect("Failed to start capture");

let fps = FRAME_RATE;
let spf = Duration::from_nanos(1_000_000_000 / fps);
Expand All @@ -297,60 +308,63 @@ impl MediaRecorder {

let frame_clone = frame.to_vec();
std::thread::spawn(move || {
let mut frame_data = Vec::with_capacity(capture_size);
let mut frame_data = Vec::with_capacity(capture_size.try_into().unwrap());
let stride = w_cloned * 4;
println!("Frame length: {}", frame_clone.len());
println!("Stride: {}", stride);
let rt = tokio::runtime::Runtime::new().unwrap();

for row in 0..h {
let start = row * stride;
let end = start + stride;
let start: usize = row as usize * stride as usize;
let end: usize = start + stride as usize;
let mut row_data = frame_clone[start..end].to_vec();
for chunk in row_data.chunks_mut(4) {
chunk.swap(0, 2);
}
//print the row data length
println!("Row data length: {}", row_data.len());
frame_data.extend_from_slice(&row_data);
}

let path = Path::new(&screenshot_file_path_owned_cloned);
let image: ImageBuffer<Rgba<u8>, Vec<u8>> = ImageBuffer::from_raw(
w_cloned.try_into().unwrap(),
h.try_into().unwrap(),
w_cloned.try_into().unwrap(),
h.try_into().unwrap(),
frame_data
).expect("Failed to create image buffer");
match image.save_with_format(&path, image::ImageFormat::Jpeg) {
Ok(_) => {
rt.block_on(async {
let upload_task = tokio::spawn(upload_file(Some(options_clone), screenshot_file_path_owned_cloned.clone(), "screenshot".to_string()));
match upload_task.await {
Ok(result) => {
match result {
Ok(_) => println!("Screenshot captured and saved to {:?}", path),
Err(e) => eprintln!("Failed to upload file: {}", e),
}
},
Err(e) => eprintln!("Failed to join task: {}", e),
}
});
println!("Screenshot captured and saved to {:?}", path);
},
Err(e) => eprintln!("Failed to save screenshot: {}", e),

let mut output_file = std::fs::File::create(&path).expect("Failed to create output file");
let mut encoder = JpegEncoder::new_with_quality(&mut output_file, 20);

if let Err(e) = encoder.encode_image(&image) {
eprintln!("Failed to save screenshot: {}", e);
} else {
let screenshot_file_path_owned_cloned_copy = screenshot_file_path_owned_cloned.clone();
rt.block_on(async {
let upload_task = tokio::spawn(upload_file(Some(options_clone), screenshot_file_path_owned_cloned_copy.clone(), "screenshot".to_string()));
match upload_task.await {
Ok(result) => {
match result {
Ok(_) => println!("Screenshot captured and saved to {:?}", path),
Err(e) => eprintln!("Failed to upload file: {}", e),
}
},
Err(e) => eprintln!("Failed to join task: {}", e),
}
});
println!("Screenshot captured and saved to {:?}", path);
}
});
}
}

let mut frame_data = Vec::with_capacity(capture_size);
let mut frame_data = Vec::with_capacity(capture_size.try_into().unwrap());

match capturer.frame() {
Ok(frame) => {
let stride = w * 4;
for row in 0..adjusted_height {
let start = row * stride;
let end = start + stride;
let start: usize = row as usize * stride as usize;
let end: usize = start + stride as usize;
frame_data.extend_from_slice(&frame[start..end]);
}
if let Some(sender) = &video_channel_sender {
Expand Down
10 changes: 7 additions & 3 deletions apps/desktop/src-tauri/src/recording.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ pub struct RecordingState {
pub shutdown_flag: Arc<AtomicBool>,
pub video_uploading_finished: Arc<AtomicBool>,
pub audio_uploading_finished: Arc<AtomicBool>,
pub data_dir: Option<PathBuf>
pub data_dir: Option<PathBuf>,
pub max_screen_width: usize,
pub max_screen_height: usize,
}

unsafe impl Send for RecordingState {}
Expand Down Expand Up @@ -69,7 +71,7 @@ pub async fn start_dual_recording(
Some(options.audio_name.clone())
};

let media_recording_preparation = prepare_media_recording(&options, &audio_chunks_dir, &video_chunks_dir, &screenshot_dir, audio_name);
let media_recording_preparation = prepare_media_recording(&options, &audio_chunks_dir, &video_chunks_dir, &screenshot_dir, audio_name, state_guard.max_screen_width, state_guard.max_screen_height);
let media_recording_result = media_recording_preparation.await.map_err(|e| e.to_string())?;

state_guard.media_process = Some(media_recording_result);
Expand Down Expand Up @@ -216,11 +218,13 @@ async fn prepare_media_recording(
screenshot_dir: &Path,
video_chunks_dir: &Path,
audio_name: Option<String>,
max_screen_width: usize,
max_screen_height: usize,
) -> Result<MediaRecorder, String> {
let mut media_recorder = MediaRecorder::new();
let audio_file_path = audio_chunks_dir.to_str().unwrap();
let video_file_path = video_chunks_dir.to_str().unwrap();
let screenshot_dir_path = screenshot_dir.to_str().unwrap();
media_recorder.start_media_recording(options.clone(), audio_file_path, screenshot_dir_path, video_file_path, audio_name.as_ref().map(String::as_str)).await?;
media_recorder.start_media_recording(options.clone(), audio_file_path, screenshot_dir_path, video_file_path, audio_name.as_ref().map(String::as_str), max_screen_width, max_screen_height).await?;
Ok(media_recorder)
}
6 changes: 4 additions & 2 deletions apps/desktop/src-tauri/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use tokio::process::{ChildStderr};
use std::process::{Command};
use ffmpeg_sidecar::{
paths::sidecar_dir,
Expand All @@ -14,8 +13,11 @@ pub fn has_screen_capture_access() -> bool {
Err(_) => return false,
};

let width = display.width();
let height = display.height();

let result = panic::catch_unwind(|| {
let mut capturer = match Capturer::new(display) {
let mut capturer = match Capturer::new(display, width, height) {
Ok(capturer) => capturer,
Err(_) => return false,
};
Expand Down
11 changes: 10 additions & 1 deletion apps/desktop/src/app/camera/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,16 @@ import { Camera } from "@/components/windows/Camera";

export default function CameraPage() {
return (
<div id="app" data-tauri-drag-region style={{ borderRadius: "50%" }}>
<div
id="app"
data-tauri-drag-region
style={{
borderRadius: "50%",
background: "none !important",
outline: "none",
boxShadow: "none",
}}
>
<Camera />
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/components/windows/Camera.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export const Camera = () => {
return (
<div
data-tauri-drag-region
className="group w-full h-full bg-gray-200 rounded-full m-0 p-0 relative overflow-hidden flex items-center justify-center border-none outline-none focus:outline-none rounded-full"
className="cursor-move group w-full h-full bg-gray-200 rounded-full m-0 p-0 relative overflow-hidden flex items-center justify-center border-none outline-none focus:outline-none rounded-full"
>
{isLoading && (
<div className="w-full h-full absolute top-0 left-0 bg-gray-200 z-10 flex items-center justify-center">
Expand Down
15 changes: 0 additions & 15 deletions apps/desktop/src/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,6 @@ export function concatenateTypedArrays(
return result;
}

export const requestMediaDevicesPermission = async () => {
try {
if (typeof navigator !== "undefined" && typeof window !== "undefined") {
await navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});

console.log("Permissions to access media devices have been granted.");
}
} catch (error) {
console.error("Permissions to access media devices were denied.", error);
}
};

export const getPermissions = () => {
try {
if (typeof window !== "undefined") {
Expand Down
Loading

1 comment on commit c92840d

@vercel
Copy link

@vercel vercel bot commented on c92840d Mar 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.