@@ -5,12 +5,18 @@ use crate::{
55 tr:: tr,
66 } ,
77 logic_cb,
8- slint_generatedAppWindow:: { AppWindow , Source as UISource , SourceType } ,
8+ slint_generatedAppWindow:: {
9+ AppWindow , Fps as UIFps , RecordStatus as UIRecordStatus , Resolution as UIResolution ,
10+ Source as UISource , SourceType ,
11+ } ,
912 toast_warn,
1013} ;
1114use anyhow:: { Result , bail} ;
1215use once_cell:: sync:: Lazy ;
13- use recorder:: { AudioRecorder , SpeakerRecorder , StreamingAudioRecorder , bounded} ;
16+ use recorder:: {
17+ AudioRecorder , FPS , RecorderConfig , RecordingSession , Resolution , SpeakerRecorder ,
18+ StreamingAudioRecorder , bounded,
19+ } ;
1420use slint:: { ComponentHandle , Model , SharedString , ToSharedString , VecModel , Weak } ;
1521use std:: {
1622 path:: PathBuf ,
@@ -26,6 +32,9 @@ struct Cache {
2632 desktop_speaker_amplification : Option < Arc < AtomicI32 > > ,
2733 input_audio_amplification : Option < Arc < AtomicI32 > > ,
2834 input_streaming_audio_recorder : Option < StreamingAudioRecorder > ,
35+
36+ recorder_stop_sig : Option < Arc < AtomicBool > > ,
37+ merge_stop_sig : Option < Arc < AtomicBool > > ,
2938}
3039
3140static CACHE : Lazy < Mutex < Cache > > = Lazy :: new ( || Mutex :: new ( Cache :: default ( ) ) ) ;
@@ -326,11 +335,156 @@ fn inner_input_audio_changed(ui: &AppWindow, name: SharedString) -> Result<()> {
326335 Ok ( ( ) )
327336}
328337
329- fn start_recording ( ui : & AppWindow ) { }
338+ fn start_recording ( ui : & AppWindow ) {
339+ let all_config = config:: all ( ) ;
340+
341+ if all_config. recorder . save_dir . is_empty ( )
342+ || !PathBuf :: from ( all_config. recorder . save_dir . clone ( ) ) . exists ( )
343+ || !PathBuf :: from ( all_config. recorder . save_dir . clone ( ) ) . is_dir ( )
344+ {
345+ let ui_weak = ui. as_weak ( ) ;
346+ tokio:: spawn ( async move {
347+ let Some ( dir) = picker_directory ( ui_weak. clone ( ) , & tr ( "Choose save directory" ) , "" )
348+ else {
349+ return ;
350+ } ;
351+
352+ let mut all = config:: all ( ) ;
353+ all. recorder . save_dir = dir. to_string_lossy ( ) . to_string ( ) ;
354+ _ = config:: save ( all) ;
355+ } ) ;
356+ return ;
357+ }
358+
359+ let ui_weak = ui. as_weak ( ) ;
360+ thread:: spawn ( move || {
361+ if let Err ( e) = inner_start_recording ( ui_weak. clone ( ) ) {
362+ toast:: async_toast_warn ( ui_weak, e. to_string ( ) ) ;
363+ }
364+ } ) ;
365+ }
366+
367+ fn inner_start_recording ( ui_weak : Weak < AppWindow > ) -> Result < ( ) > {
368+ log:: info!( "start recording..." ) ;
369+ let all_config = config:: all ( ) ;
330370
331- fn stop_recording ( ui : & AppWindow ) { }
371+ if all_config. control . screen . is_empty ( ) {
372+ bail ! ( "available screen no found" ) ;
373+ }
374+
375+ let screen_info = capture:: available_screens ( ) ?
376+ . into_iter ( )
377+ . find ( |item| item. name == all_config. control . screen ) ;
378+
379+ if screen_info. is_none ( ) {
380+ bail ! ( "no found screen: {}" , all_config. control. screen) ;
381+ }
382+
383+ let screen_info = screen_info. unwrap ( ) ;
384+ log:: debug!( "screen_info: {screen_info:?}" ) ;
385+
386+ let resolution = if matches ! ( all_config. recorder. resolution, UIResolution :: Original ) {
387+ Resolution :: Original ( (
388+ screen_info. logical_size . width as u32 ,
389+ screen_info. logical_size . height as u32 ,
390+ ) )
391+ } else {
392+ all_config. recorder . resolution . into ( )
393+ } ;
332394
333- fn stop_merge_tracks ( ui : & AppWindow ) { }
395+ let input_audio_name = if all_config. control . input_audio . is_empty ( ) {
396+ None
397+ } else {
398+ Some ( all_config. control . input_audio )
399+ } ;
400+
401+ RecordingSession :: init ( ) ?;
402+
403+ let config = RecorderConfig :: new (
404+ all_config. control . screen . clone ( ) ,
405+ screen_info. logical_size . clone ( ) ,
406+ RecorderConfig :: make_filename ( & all_config. recorder . save_dir ) ,
407+ )
408+ . with_enable_frame_channel_user ( true )
409+ . with_enable_recording_speaker ( true )
410+ . with_include_cursor ( all_config. recorder . show_cursor )
411+ . with_remove_cache_files ( all_config. recorder . remove_temporary_files )
412+ . with_audio_device_name ( input_audio_name)
413+ . with_fps ( all_config. recorder . fps . clone ( ) . into ( ) )
414+ . with_resolution ( resolution) ;
415+
416+ config. validate ( ) ?;
417+ log:: info!( "Recording configuration: {:#?}" , config) ;
418+
419+ let mut session = RecordingSession :: new ( config) ;
420+ session. start ( ) ?;
421+
422+ ui_weak. upgrade_in_event_loop ( move |ui| {
423+ global_store ! ( ui) . set_record_status ( UIRecordStatus :: Recording ) ;
424+ } ) ;
425+
426+ let stop_sig = session. stop_sig ( ) . clone ( ) ;
427+ let stop_sig_merge = session. get_stop_combine_tracks ( ) ;
428+ {
429+ let mut cache = CACHE . lock ( ) . unwrap ( ) ;
430+ cache. recorder_stop_sig = Some ( stop_sig) ;
431+ cache. merge_stop_sig = Some ( stop_sig_merge) ;
432+ }
433+
434+ let frame_receiver_user = session. get_frame_receiver_user ( ) ;
435+ thread:: spawn ( move || {
436+ if let Some ( rx) = frame_receiver_user {
437+ while let Ok ( frame) = rx. recv ( ) {
438+ log:: debug!(
439+ "frame_receiver_user frame len: {} bytes" ,
440+ frame. cb_data. data. pixel_data. len( )
441+ ) ;
442+ }
443+ log:: info!( "exit frame_receiver_user" ) ;
444+ } else {
445+ log:: info!( "frame_receiver_user is none" ) ;
446+ }
447+ } ) ;
448+
449+ let ui_weak_clone = ui_weak. clone ( ) ;
450+ session. wait ( move |v| {
451+ log:: debug!( "combine tracks progress: {}%" , ( v * 100.0 ) as u32 ) ;
452+ _ = ui_weak_clone. upgrade_in_event_loop ( move |ui| {
453+ global_store ! ( ui) . set_record_status ( UIRecordStatus :: Mergeing ) ;
454+ global_store ! ( ui) . set_merge_tracks_progress ( v) ;
455+ } ) ;
456+ } ) ?;
457+
458+ ui_weak. upgrade_in_event_loop ( move |ui| {
459+ global_store ! ( ui) . set_record_status ( UIRecordStatus :: Stopped ) ;
460+ } ) ;
461+
462+ log:: info!( "Recording completed successfully!" ) ;
463+
464+ Ok ( ( ) )
465+ }
466+
467+ fn stop_recording ( ui : & AppWindow ) {
468+ let stop_sig = CACHE . lock ( ) . unwrap ( ) . recorder_stop_sig . take ( ) ;
469+ if let Some ( sig) = stop_sig {
470+ sig. store ( true , Ordering :: Relaxed ) ;
471+ } else {
472+ log:: warn!( "recorder_stop_sig is None" ) ;
473+ }
474+
475+ global_store ! ( ui) . set_record_status ( UIRecordStatus :: Mergeing ) ;
476+ }
477+
478+ fn stop_merge_tracks ( ui : & AppWindow ) {
479+ let stop_sig = CACHE . lock ( ) . unwrap ( ) . merge_stop_sig . take ( ) ;
480+ if let Some ( sig) = stop_sig {
481+ sig. store ( true , Ordering :: Relaxed ) ;
482+ } else {
483+ log:: warn!( "merge_stop_sig is None" ) ;
484+ }
485+
486+ global_store ! ( ui) . set_record_status ( UIRecordStatus :: Stopped ) ;
487+ }
334488
335489pub fn picker_directory ( ui : Weak < AppWindow > , title : & str , filename : & str ) -> Option < PathBuf > {
336490 let result = native_dialog:: DialogBuilder :: file ( )
@@ -351,3 +505,25 @@ pub fn picker_directory(ui: Weak<AppWindow>, title: &str, filename: &str) -> Opt
351505 _ => None ,
352506 }
353507}
508+
509+ impl From < UIResolution > for Resolution {
510+ fn from ( entry : UIResolution ) -> Self {
511+ match entry {
512+ UIResolution :: P720 => Resolution :: P720 ,
513+ UIResolution :: P1080 => Resolution :: P1080 ,
514+ UIResolution :: P2K => Resolution :: P2K ,
515+ UIResolution :: P4K => Resolution :: P4K ,
516+ _ => unreachable ! ( ) ,
517+ }
518+ }
519+ }
520+
521+ impl From < UIFps > for FPS {
522+ fn from ( entry : UIFps ) -> Self {
523+ match entry {
524+ UIFps :: Fps24 => FPS :: Fps24 ,
525+ UIFps :: Fps25 => FPS :: Fps25 ,
526+ UIFps :: Fps30 => FPS :: Fps30 ,
527+ }
528+ }
529+ }
0 commit comments