11use super :: command:: with_history_manager;
2- use crate :: { global_store, logic_cb, logic:: toast, slint_generatedAppWindow:: AppWindow } ;
2+ use crate :: {
3+ global_store, logic:: toast, logic_cb,
4+ slint_generatedAppWindow:: AppWindow ,
5+ } ;
36use slint:: ComponentHandle ;
47use std:: { sync:: Arc , time:: Duration } ;
58use video_editor:: {
6- preview:: playback:: { PlaybackController , PlaybackState } ,
79 preview:: config:: PreviewConfig ,
10+ preview:: playback:: { PlaybackController , PlaybackState } ,
811 tracks:: unified_mixer:: UnifiedMixerConfig ,
912} ;
1013
@@ -18,98 +21,74 @@ pub fn init(ui: &AppWindow) {
1821 logic_cb ! ( video_editor_calc_timeline_offset, ui, index, total_index) ;
1922}
2023
21- // Get or create the playback controller
22- fn get_playback_controller ( ui : & AppWindow ) -> Arc < std:: sync:: Mutex < PlaybackController > > {
23- // Get preview config from store
24- let new_project_config = global_store ! ( ui) . get_video_editor_new_project_config ( ) ;
25- let ui_preview_config = new_project_config. preview_config ;
26-
27- // Convert UI preview config to UnifiedMixerConfig, then to PreviewConfig
28- let mixer_config: UnifiedMixerConfig = ui_preview_config. into ( ) ;
29- let preview_config: PreviewConfig = mixer_config. into ( ) ;
30-
31- let manager = with_history_manager ( |state| Arc :: new ( state. tracks_manager . clone ( ) ) ) ;
32- let fps = preview_config. frame_rate ( ) ;
33-
34- Arc :: new ( std:: sync:: Mutex :: new ( PlaybackController :: new ( manager, fps) ) )
35- }
36-
3724fn video_editor_preview_toggle ( ui : & AppWindow ) {
3825 let controller = get_playback_controller ( ui) ;
39- let mut controller = controller. lock ( ) . unwrap ( ) ;
26+ let mut ctrl = controller. lock ( ) . unwrap ( ) ;
4027
41- match controller . state ( ) {
28+ match ctrl . state ( ) {
4229 PlaybackState :: Playing => {
43- // Pause
44- controller. pause ( ) ;
30+ ctrl. pause ( ) ;
4531 global_store ! ( ui) . set_video_editor_is_previewing ( false ) ;
4632 toast:: async_toast_info ( ui. as_weak ( ) , "Paused" . to_string ( ) ) ;
4733 }
4834 PlaybackState :: Stopped | PlaybackState :: Paused => {
49- // Start/resume playing
50- controller. play ( ) ;
35+ ctrl. play ( ) ;
5136 global_store ! ( ui) . set_video_editor_is_previewing ( true ) ;
5237 toast:: async_toast_info ( ui. as_weak ( ) , "Playing" . to_string ( ) ) ;
5338
54- // Start background thread to update UI during playback
55- start_playback_update_thread ( ui. as_weak ( ) , get_playback_controller ( ui) ) ;
39+ start_playback_update_thread ( ui. as_weak ( ) , controller. clone ( ) ) ;
5640 }
5741 }
5842}
5943
6044fn video_editor_preview_rewind ( ui : & AppWindow ) {
6145 let controller = get_playback_controller ( ui) ;
62- let mut controller = controller. lock ( ) . unwrap ( ) ;
46+ let mut ctrl = controller. lock ( ) . unwrap ( ) ;
6347
64- // Rewind by 5 seconds
65- let current_position = controller. position ( ) ;
48+ let current_position = ctrl. position ( ) ;
6649 let rewind_amount = Duration :: from_secs ( 5 ) ;
6750
6851 if current_position >= rewind_amount {
6952 let new_position = current_position - rewind_amount;
70- controller . set_position ( new_position) ;
53+ ctrl . set_position ( new_position) ;
7154 global_store ! ( ui) . set_video_editor_timeline_offset ( new_position. as_millis ( ) as i32 ) ;
7255 toast:: async_toast_info ( ui. as_weak ( ) , "Rewind" . to_string ( ) ) ;
7356 } else {
74- controller . set_position ( Duration :: ZERO ) ;
57+ ctrl . set_position ( Duration :: ZERO ) ;
7558 global_store ! ( ui) . set_video_editor_timeline_offset ( 0 ) ;
7659 toast:: async_toast_info ( ui. as_weak ( ) , "Already at start" . to_string ( ) ) ;
7760 }
7861}
7962
8063fn video_editor_preview_fast_forward ( ui : & AppWindow ) {
8164 let controller = get_playback_controller ( ui) ;
82- let mut controller = controller. lock ( ) . unwrap ( ) ;
65+ let mut ctrl = controller. lock ( ) . unwrap ( ) ;
8366
84- // Fast forward by 5 seconds
85- let current_position = controller. position ( ) ;
67+ let current_position = ctrl. position ( ) ;
8668 let total_duration = with_history_manager ( |state| state. tracks_manager . duration ) ;
8769 let forward_amount = Duration :: from_secs ( 5 ) ;
8870
8971 let new_position = current_position. saturating_add ( forward_amount) ;
9072 if new_position < total_duration {
91- controller . set_position ( new_position) ;
73+ ctrl . set_position ( new_position) ;
9274 global_store ! ( ui) . set_video_editor_timeline_offset ( new_position. as_millis ( ) as i32 ) ;
9375 toast:: async_toast_info ( ui. as_weak ( ) , "Fast forward" . to_string ( ) ) ;
9476 } else {
95- controller . set_position ( total_duration) ;
77+ ctrl . set_position ( total_duration) ;
9678 global_store ! ( ui) . set_video_editor_timeline_offset ( total_duration. as_millis ( ) as i32 ) ;
9779 toast:: async_toast_info ( ui. as_weak ( ) , "Already at end" . to_string ( ) ) ;
9880 }
9981}
10082
10183fn video_editor_preview_jump_previous_segment ( ui : & AppWindow ) {
102- // Find previous segment end position
10384 let current_offset = global_store ! ( ui) . get_video_editor_timeline_offset ( ) ;
10485 let current_position = Duration :: from_millis ( current_offset as u64 ) ;
10586
10687 let prev_segment_end = with_history_manager ( |state| {
10788 let mut prev_end = Duration :: ZERO ;
10889
109- // Search all tracks for the segment that ends before current position
11090 for track in state. tracks_manager . iter ( ) {
111- let segments = track. segments ( ) ;
112- for segment in segments {
91+ for segment in track. segments ( ) {
11392 let segment_end = segment. timeline_offset + segment. duration ;
11493 if segment_end < current_position && segment_end > prev_end {
11594 prev_end = segment_end;
@@ -129,17 +108,14 @@ fn video_editor_preview_jump_previous_segment(ui: &AppWindow) {
129108}
130109
131110fn video_editor_preview_jump_next_segment ( ui : & AppWindow ) {
132- // Find next segment start position
133111 let current_offset = global_store ! ( ui) . get_video_editor_timeline_offset ( ) ;
134112 let current_position = Duration :: from_millis ( current_offset as u64 ) ;
135113
136114 let next_segment_start = with_history_manager ( |state| {
137115 let mut next_start = None ;
138116
139- // Search all tracks for the segment that starts after current position
140117 for track in state. tracks_manager . iter ( ) {
141- let segments = track. segments ( ) ;
142- for segment in segments {
118+ for segment in track. segments ( ) {
143119 if segment. timeline_offset > current_position {
144120 if next_start. is_none ( ) || segment. timeline_offset < next_start. unwrap ( ) {
145121 next_start = Some ( segment. timeline_offset ) ;
@@ -162,24 +138,18 @@ fn video_editor_preview_jump_next_segment(ui: &AppWindow) {
162138fn video_editor_preview_change_volume ( ui : & AppWindow , value : f32 ) {
163139 let value = value. clamp ( 0.0 , 100.0 ) ;
164140
165- // Update UI state for persistence
166- // Note: volume is stored in UI state as preview_volumn (note the spelling)
167141 let ui_state = global_store ! ( ui) . get_video_editor_ui_state ( ) ;
168142 let mut state: super :: common_type:: VideoEditorUIState = ui_state. into ( ) ;
169143 state. preview_volumn = value;
170144 let ui_state_new: crate :: slint_generatedAppWindow:: VideoEditorUIState = state. into ( ) ;
171145 global_store ! ( ui) . set_video_editor_ui_state ( ui_state_new) ;
172-
173- // Volume control is now persisted in UI state
174- // Actual audio volume control would require:
175- // 1. Adding a GainFilter to audio segments during playback
176- // 2. Or implementing volume control in the UnifiedMixer
177- // 3. Passing the gain value to the audio output pipeline
178- // For now, the volume setting is saved and can be used during export
179146}
180147
181- fn video_editor_calc_timeline_offset ( _ui : & AppWindow , index : i32 , _total_index : i32 ) -> slint:: SharedString {
182- // Calculate timeline offset in SRT timestamp format: "HH:MM:SS,mmm"
148+ fn video_editor_calc_timeline_offset (
149+ _ui : & AppWindow ,
150+ index : i32 ,
151+ _total_index : i32 ,
152+ ) -> slint:: SharedString {
183153 let total_ms = index;
184154
185155 let hours = total_ms / 3600000 ;
@@ -194,36 +164,39 @@ fn video_editor_calc_timeline_offset(_ui: &AppWindow, index: i32, _total_index:
194164 . into ( )
195165}
196166
197- // Background thread to update UI during playback
167+ // Helper functions
168+
169+ fn get_playback_controller ( ui : & AppWindow ) -> Arc < std:: sync:: Mutex < PlaybackController > > {
170+ let new_project_config = global_store ! ( ui) . get_video_editor_new_project_config ( ) ;
171+ let ui_preview_config = new_project_config. preview_config ;
172+
173+ let mixer_config: UnifiedMixerConfig = ui_preview_config. into ( ) ;
174+ let preview_config: PreviewConfig = mixer_config. into ( ) ;
175+
176+ let manager = with_history_manager ( |state| Arc :: new ( state. tracks_manager . clone ( ) ) ) ;
177+ let fps = preview_config. frame_rate ( ) ;
178+
179+ Arc :: new ( std:: sync:: Mutex :: new ( PlaybackController :: new ( manager, fps) ) )
180+ }
181+
198182fn start_playback_update_thread (
199183 ui_weak : slint:: Weak < AppWindow > ,
200184 controller : Arc < std:: sync:: Mutex < PlaybackController > > ,
201185) {
202186 std:: thread:: spawn ( move || {
203- // Frame rendering requires implementing:
204- // 1. UnifiedVideoTracksCompositorIterator for video rendering
205- // 2. UnifiedAudioTracksMixerIterator for audio playback
206- // 3. Converting rendered frames to Slint images
207- // 4. Audio output integration
208- //
209- // For now, we update timeline position during playback
210- // Full preview implementation would need a dedicated render pipeline
211-
212187 let frame_rate = controller. lock ( ) . unwrap ( ) . frame_rate ( ) as f32 ;
213188 let frame_duration = Duration :: from_secs_f64 ( 1.0 / frame_rate as f64 ) ;
214189
215190 loop {
216- // Check if still playing
217191 let ( is_playing, percentage, position) = {
218192 let ctrl = controller. lock ( ) . unwrap ( ) ;
219- let state = ctrl . state ( ) ;
220- let percentage = ctrl. percentage ( ) ;
221- let position = ctrl. position ( ) ;
222-
223- ( state == PlaybackState :: Playing , percentage , position )
193+ (
194+ ctrl. state ( ) == PlaybackState :: Playing ,
195+ ctrl. percentage ( ) ,
196+ ctrl . position ( ) ,
197+ )
224198 } ;
225199
226- // Check if playback is finished (reached 100%)
227200 if is_playing && percentage >= 1.0 {
228201 let _ = ui_weak. upgrade_in_event_loop ( move |ui| {
229202 global_store ! ( ui) . set_video_editor_is_previewing ( false ) ;
@@ -236,15 +209,13 @@ fn start_playback_update_thread(
236209 break ;
237210 }
238211
239- // Update timeline position
240212 let position_ms = position. as_millis ( ) as i32 ;
241213 let ui_weak_clone = ui_weak. clone ( ) ;
242214 let _ = ui_weak_clone. upgrade_in_event_loop ( move |ui| {
243215 global_store ! ( ui) . set_video_editor_timeline_offset ( position_ms) ;
244216 } ) ;
245217
246- // Sleep for frame duration
247218 std:: thread:: sleep ( frame_duration) ;
248219 }
249220 } ) ;
250- }
221+ }
0 commit comments