1use std::sync::Arc;
8use std::sync::atomic::{AtomicU64, Ordering};
9use std::time::{Duration, Instant};
10
11enum ClockState {
24 Stopped,
25 Running { started_at: Instant, base: Duration },
26 Paused { frozen_at: Duration },
27}
28
29pub struct PlaybackClock {
54 state: ClockState,
55 rate: f64,
57 seek_offset: Duration,
60}
61
62impl PlaybackClock {
63 #[must_use]
65 pub fn new() -> Self {
66 Self {
67 state: ClockState::Stopped,
68 rate: 1.0,
69 seek_offset: Duration::ZERO,
70 }
71 }
72
73 pub fn start(&mut self) {
81 let base = match &self.state {
82 ClockState::Running { .. } => return,
83 ClockState::Stopped => self.seek_offset,
84 ClockState::Paused { frozen_at } => *frozen_at,
85 };
86 self.state = ClockState::Running {
87 started_at: Instant::now(),
88 base,
89 };
90 }
91
92 pub fn stop(&mut self) {
97 self.state = ClockState::Stopped;
98 self.seek_offset = Duration::ZERO;
99 }
100
101 pub fn pause(&mut self) {
106 if let ClockState::Running { started_at, base } = &self.state {
107 let elapsed = started_at.elapsed().mul_f64(self.rate);
108 self.state = ClockState::Paused {
109 frozen_at: *base + elapsed,
110 };
111 }
112 }
113
114 pub fn resume(&mut self) {
116 if let ClockState::Paused { frozen_at } = self.state {
117 self.state = ClockState::Running {
118 started_at: Instant::now(),
119 base: frozen_at,
120 };
121 }
122 }
123
124 #[must_use]
129 pub fn current_time(&self) -> Duration {
130 match &self.state {
131 ClockState::Stopped => Duration::ZERO,
132 ClockState::Paused { frozen_at } => *frozen_at,
133 ClockState::Running { started_at, base } => {
134 *base + started_at.elapsed().mul_f64(self.rate)
135 }
136 }
137 }
138
139 #[must_use]
145 pub fn current_pts(&self) -> Duration {
146 match &self.state {
147 ClockState::Stopped => self.seek_offset,
148 _ => self.current_time(),
149 }
150 }
151
152 #[must_use]
154 pub fn is_running(&self) -> bool {
155 matches!(self.state, ClockState::Running { .. })
156 }
157
158 pub fn set_rate(&mut self, rate: f64) {
163 if rate <= 0.0 {
164 return;
165 }
166 if let ClockState::Running { started_at, base } = &mut self.state {
167 let elapsed = started_at.elapsed().mul_f64(self.rate);
169 *base += elapsed;
170 *started_at = Instant::now();
171 }
172 self.rate = rate;
173 }
174
175 #[must_use]
177 pub fn rate(&self) -> f64 {
178 self.rate
179 }
180
181 pub fn set_position(&mut self, pts: Duration) {
191 self.seek_offset = pts;
193 if matches!(self.state, ClockState::Running { .. }) {
194 self.state = ClockState::Running {
196 started_at: Instant::now(),
197 base: pts,
198 };
199 } else if matches!(self.state, ClockState::Paused { .. }) {
200 self.state = ClockState::Paused { frozen_at: pts };
201 }
202 }
204}
205
206impl Default for PlaybackClock {
207 fn default() -> Self {
208 Self::new()
209 }
210}
211
212pub(crate) enum MasterClock {
219 Audio {
220 samples_consumed: Arc<AtomicU64>,
221 sample_rate: u32,
222 fallback: Option<(Instant, Duration)>,
231 },
232 System {
233 started_at: Instant,
234 base_pts: Duration,
235 },
236}
237
238impl MasterClock {
239 #[allow(clippy::cast_precision_loss)]
248 pub(crate) fn current_pts(&self) -> Duration {
249 match self {
250 Self::Audio {
251 samples_consumed,
252 sample_rate,
253 fallback,
254 } => {
255 let s = samples_consumed.load(Ordering::Relaxed);
256 let sample_pts = if s > 0 {
257 Some(Duration::from_secs_f64(s as f64 / f64::from(*sample_rate)))
258 } else {
259 None
260 };
261 let fallback_pts = fallback
262 .as_ref()
263 .map(|(started_at, base_pts)| *base_pts + started_at.elapsed());
264 match (sample_pts, fallback_pts) {
265 (Some(sp), Some(fp)) => sp.max(fp),
270 (Some(sp), None) => sp,
271 (None, Some(fp)) => fp,
272 (None, None) => Duration::ZERO,
273 }
274 }
275 Self::System {
276 started_at,
277 base_pts,
278 } => *base_pts + started_at.elapsed(),
279 }
280 }
281
282 pub(crate) fn should_sync(&self) -> bool {
293 match self {
294 Self::System { .. } => true,
295 Self::Audio {
296 samples_consumed,
297 fallback,
298 ..
299 } => samples_consumed.load(Ordering::Relaxed) > 0 || fallback.is_some(),
300 }
301 }
302
303 pub(crate) fn activate_fallback_if_no_audio(&mut self, base_pts: Duration) {
317 if let Self::Audio {
318 samples_consumed,
319 fallback,
320 ..
321 } = self
322 && samples_consumed.load(Ordering::Relaxed) == 0
323 && fallback.is_none()
324 {
325 *fallback = Some((Instant::now(), base_pts));
326 }
327 }
328
329 pub(crate) fn rearm_fallback_at(&mut self, base_pts: Duration) {
341 if let Self::Audio { fallback, .. } = self {
342 *fallback = Some((Instant::now(), base_pts));
343 }
344 }
345
346 pub(crate) fn audio_samples_snapshot(&self) -> u64 {
352 if let Self::Audio {
353 samples_consumed, ..
354 } = self
355 {
356 samples_consumed.load(Ordering::Relaxed)
357 } else {
358 0
359 }
360 }
361
362 pub(crate) fn reset(&mut self, base: Duration) {
373 match self {
374 Self::System {
375 started_at,
376 base_pts,
377 } => {
378 *started_at = Instant::now();
379 *base_pts = base;
380 }
381 Self::Audio { fallback, .. } => {
382 if fallback.is_some() {
383 *fallback = Some((Instant::now(), base));
384 }
385 }
386 }
387 }
388}
389
390#[cfg(test)]
393mod tests {
394 use super::*;
395 use std::thread;
396
397 #[test]
398 fn clock_stopped_should_return_zero() {
399 let clock = PlaybackClock::new();
401 assert_eq!(clock.current_time(), Duration::ZERO);
402
403 let mut clock = PlaybackClock::new();
405 clock.start();
406 thread::sleep(Duration::from_millis(5));
407 clock.stop();
408 assert_eq!(
409 clock.current_time(),
410 Duration::ZERO,
411 "current_time() must be ZERO after stop()"
412 );
413 }
414
415 #[test]
416 fn clock_paused_should_freeze_at_pause_time() {
417 let mut clock = PlaybackClock::new();
418 clock.start();
419 thread::sleep(Duration::from_millis(10));
420 clock.pause();
421
422 let t1 = clock.current_time();
423 thread::sleep(Duration::from_millis(10));
424 let t2 = clock.current_time();
425
426 assert_eq!(t1, t2, "current_time() must not advance while paused");
427 assert!(
428 !clock.is_running(),
429 "clock must not report running while paused"
430 );
431 }
432
433 #[test]
434 fn clock_resumed_should_continue_from_pause() {
435 let mut clock = PlaybackClock::new();
436 clock.start();
437 thread::sleep(Duration::from_millis(10));
438 clock.pause();
439 let t_paused = clock.current_time();
440
441 thread::sleep(Duration::from_millis(10));
443 assert_eq!(clock.current_time(), t_paused);
444
445 clock.resume();
446 assert!(clock.is_running());
447 thread::sleep(Duration::from_millis(10));
448
449 let t_after = clock.current_time();
450 assert!(
451 t_after > t_paused,
452 "current_time() must advance after resume(); paused={t_paused:?} after={t_after:?}"
453 );
454 }
455
456 #[test]
457 fn clock_start_should_be_noop_when_already_running() {
458 let mut clock = PlaybackClock::new();
459 clock.start();
460 thread::sleep(Duration::from_millis(10));
461 let t_before = clock.current_time();
462
463 clock.start();
465 let t_after = clock.current_time();
466
467 assert!(
468 t_after >= t_before,
469 "second start() must not reset the clock; before={t_before:?} after={t_after:?}"
470 );
471 }
472
473 #[test]
474 fn clock_resume_should_be_noop_when_not_paused() {
475 let mut clock = PlaybackClock::new();
477 clock.resume();
478 assert!(!clock.is_running());
479 assert_eq!(clock.current_time(), Duration::ZERO);
480
481 clock.start();
483 thread::sleep(Duration::from_millis(5));
484 let t = clock.current_time();
485 clock.resume(); assert!(clock.is_running());
487 assert!(clock.current_time() >= t);
488 }
489
490 #[test]
491 fn clock_default_should_equal_new() {
492 let a = PlaybackClock::new();
493 let b = PlaybackClock::default();
494 assert_eq!(a.current_time(), b.current_time());
495 assert_eq!(a.is_running(), b.is_running());
496 }
497
498 #[test]
499 fn set_rate_should_reject_non_positive_values() {
500 let mut clock = PlaybackClock::new();
501
502 clock.set_rate(0.0);
503 assert!(
504 (clock.rate() - 1.0).abs() < f64::EPSILON,
505 "rate must remain 1.0 after set_rate(0.0)"
506 );
507
508 clock.set_rate(-1.0);
509 assert!(
510 (clock.rate() - 1.0).abs() < f64::EPSILON,
511 "rate must remain 1.0 after set_rate(-1.0)"
512 );
513 }
514
515 #[test]
516 fn set_rate_should_update_rate_when_stopped_or_paused() {
517 let mut clock = PlaybackClock::new();
519 clock.set_rate(0.5);
520 assert!((clock.rate() - 0.5).abs() < f64::EPSILON);
521
522 let mut clock = PlaybackClock::new();
524 clock.start();
525 clock.pause();
526 clock.set_rate(2.0);
527 assert!((clock.rate() - 2.0).abs() < f64::EPSILON);
528 assert!(
529 !clock.is_running(),
530 "clock must remain paused after set_rate"
531 );
532 }
533
534 #[test]
535 fn set_rate_running_should_not_jump_current_time() {
536 let mut clock = PlaybackClock::new();
537 clock.start();
538 thread::sleep(Duration::from_millis(10));
539 let before = clock.current_time();
540 clock.set_rate(2.0);
541 let after = clock.current_time();
542
543 assert!(
546 after >= before,
547 "current_time() must not go backward on set_rate; before={before:?} after={after:?}"
548 );
549 assert!(
550 after - before < Duration::from_millis(20),
551 "current_time() must not jump forward on set_rate; before={before:?} after={after:?}"
552 );
553 assert!((clock.rate() - 2.0).abs() < f64::EPSILON);
554 }
555
556 #[test]
557 #[ignore = "performance thresholds are environment-dependent; run explicitly with -- --include-ignored"]
558 fn rate_two_x_should_advance_at_double_speed() {
559 let mut clock = PlaybackClock::new();
560 clock.set_rate(2.0);
561 clock.start();
562 thread::sleep(Duration::from_millis(50));
563 let elapsed = clock.current_time();
564
565 assert!(
567 elapsed >= Duration::from_millis(80),
568 "2× rate: expected ≥80 ms after 50 ms wall time, got {elapsed:?}"
569 );
570 }
571
572 #[test]
573 fn set_position_should_shift_pts_by_seek_offset() {
574 let seek_target = Duration::from_secs(30);
575
576 let mut clock = PlaybackClock::new();
578 clock.set_position(seek_target);
579 assert_eq!(
580 clock.current_pts(),
581 seek_target,
582 "current_pts() must reflect seek_offset when stopped"
583 );
584
585 clock.start();
587 let pts = clock.current_pts();
588 assert!(
589 pts >= seek_target,
590 "current_pts() must be ≥ seek target after start(); target={seek_target:?} pts={pts:?}"
591 );
592 assert!(
593 clock.is_running(),
594 "clock must be running after set_position + start()"
595 );
596 }
597
598 #[test]
599 fn set_position_while_paused_should_update_frozen_time() {
600 let mut clock = PlaybackClock::new();
601 clock.start();
602 thread::sleep(Duration::from_millis(5));
603 clock.pause();
604
605 let seek_target = Duration::from_secs(10);
606 clock.set_position(seek_target);
607
608 let pts = clock.current_pts();
609 assert_eq!(
610 pts, seek_target,
611 "frozen time must update to seek target; expected={seek_target:?} got={pts:?}"
612 );
613 assert!(
614 !clock.is_running(),
615 "clock must remain paused after set_position"
616 );
617
618 clock.resume();
620 thread::sleep(Duration::from_millis(5));
621 let pts_after = clock.current_pts();
622 assert!(
623 pts_after > seek_target,
624 "current_pts() must advance past seek target after resume(); target={seek_target:?} after={pts_after:?}"
625 );
626 }
627
628 #[test]
629 fn set_position_while_running_should_continue_from_new_position() {
630 let mut clock = PlaybackClock::new();
631 clock.start();
632 thread::sleep(Duration::from_millis(5));
633
634 let seek_target = Duration::from_secs(60);
635 clock.set_position(seek_target);
636
637 let pts = clock.current_pts();
638 assert!(
639 pts >= seek_target,
640 "current_pts() must be ≥ seek target immediately after set_position while running; \
641 target={seek_target:?} pts={pts:?}"
642 );
643 assert!(
644 clock.is_running(),
645 "clock must remain running after set_position"
646 );
647 }
648
649 #[test]
650 fn stop_should_clear_seek_offset() {
651 let mut clock = PlaybackClock::new();
652 clock.set_position(Duration::from_secs(30));
653 clock.stop();
654
655 assert_eq!(
656 clock.current_pts(),
657 Duration::ZERO,
658 "stop() must reset seek_offset to ZERO"
659 );
660 }
661
662 #[test]
665 fn master_clock_system_should_advance_from_base_pts() {
666 let clock = MasterClock::System {
667 started_at: Instant::now(),
668 base_pts: Duration::from_secs(5),
669 };
670 let pts = clock.current_pts();
671 assert!(
672 pts >= Duration::from_secs(5),
673 "pts must be >= base_pts; got {pts:?}"
674 );
675 assert!(
676 pts < Duration::from_secs(6),
677 "pts must not advance 1 s in a unit test; got {pts:?}"
678 );
679 assert!(clock.should_sync(), "System clock must always sync");
680 }
681
682 #[test]
683 fn master_clock_system_reset_should_update_base_and_time_reference() {
684 let mut clock = MasterClock::System {
685 started_at: Instant::now() - Duration::from_secs(10),
686 base_pts: Duration::ZERO,
687 };
688 assert!(
689 clock.current_pts() >= Duration::from_secs(9),
690 "clock should show ~10 s before reset"
691 );
692 clock.reset(Duration::from_secs(5));
693 let pts = clock.current_pts();
694 assert!(
695 pts >= Duration::from_secs(5),
696 "pts must be >= new base after reset; got {pts:?}"
697 );
698 assert!(
699 pts < Duration::from_secs(6),
700 "pts must not advance 1 s in a unit test after reset; got {pts:?}"
701 );
702 }
703
704 #[test]
705 fn master_clock_audio_should_not_sync_before_first_sample() {
706 let clock = MasterClock::Audio {
707 samples_consumed: Arc::new(AtomicU64::new(0)),
708 sample_rate: 48_000,
709 fallback: None,
710 };
711 assert!(
712 !clock.should_sync(),
713 "audio clock must not sync before any samples are consumed and before fallback is armed"
714 );
715 assert_eq!(
716 clock.current_pts(),
717 Duration::ZERO,
718 "audio clock PTS must be zero before any samples and before fallback is armed"
719 );
720 }
721
722 #[test]
723 fn master_clock_audio_should_sync_and_report_pts_after_samples_consumed() {
724 let consumed = Arc::new(AtomicU64::new(48_000));
725 let clock = MasterClock::Audio {
726 samples_consumed: Arc::clone(&consumed),
727 sample_rate: 48_000,
728 fallback: None,
729 };
730 assert!(
731 clock.should_sync(),
732 "audio clock must sync when samples > 0"
733 );
734 assert_eq!(
735 clock.current_pts(),
736 Duration::from_secs(1),
737 "48000 samples at 48000 Hz must equal 1 second"
738 );
739 }
740
741 #[test]
742 fn master_clock_audio_should_sync_after_fallback_activated() {
743 let mut clock = MasterClock::Audio {
744 samples_consumed: Arc::new(AtomicU64::new(0)),
745 sample_rate: 48_000,
746 fallback: None,
747 };
748 assert!(
749 !clock.should_sync(),
750 "must not sync before fallback is armed"
751 );
752 clock.activate_fallback_if_no_audio(Duration::from_secs(1));
753 assert!(
754 clock.should_sync(),
755 "must sync after fallback is activated even when samples_consumed == 0"
756 );
757 }
758
759 #[test]
760 fn master_clock_audio_fallback_current_pts_should_advance_from_base_pts() {
761 let mut clock = MasterClock::Audio {
762 samples_consumed: Arc::new(AtomicU64::new(0)),
763 sample_rate: 48_000,
764 fallback: None,
765 };
766 let base = Duration::from_secs(5);
767 clock.activate_fallback_if_no_audio(base);
768 let pts = clock.current_pts();
769 assert!(
770 pts >= base,
771 "fallback current_pts must be >= base_pts; got {pts:?}"
772 );
773 assert!(
774 pts < base + Duration::from_secs(1),
775 "fallback must not advance 1 s in a unit test; got {pts:?}"
776 );
777 }
778
779 #[test]
780 fn master_clock_audio_max_of_sample_and_fallback_should_prefer_further_ahead() {
781 let consumed = Arc::new(AtomicU64::new(0));
786 let mut clock = MasterClock::Audio {
787 samples_consumed: Arc::clone(&consumed),
788 sample_rate: 48_000,
789 fallback: None,
790 };
791 clock.activate_fallback_if_no_audio(Duration::from_secs(2));
792 assert!(clock.should_sync(), "fallback must enable sync");
793 consumed.store(48_000, Ordering::Relaxed);
795 let pts = clock.current_pts();
797 assert!(
798 pts >= Duration::from_secs(2),
799 "max() must return fallback when fallback is further ahead; got {pts:?}"
800 );
801 assert!(
802 pts < Duration::from_secs(3),
803 "fallback must not be wildly ahead of 2 s; got {pts:?}"
804 );
805 }
806
807 #[test]
808 fn master_clock_audio_activate_fallback_should_be_idempotent() {
809 let mut clock = MasterClock::Audio {
810 samples_consumed: Arc::new(AtomicU64::new(0)),
811 sample_rate: 48_000,
812 fallback: None,
813 };
814 clock.activate_fallback_if_no_audio(Duration::from_secs(1));
815 let pts1 = clock.current_pts();
816 thread::sleep(Duration::from_millis(5));
817 clock.activate_fallback_if_no_audio(Duration::from_secs(100));
819 let pts2 = clock.current_pts();
820 assert!(
821 pts2 > pts1,
822 "clock must keep advancing from the first base after second activate; \
823 pts1={pts1:?} pts2={pts2:?}"
824 );
825 assert!(
826 pts2 < Duration::from_secs(5),
827 "second activate must not reset clock to base=100 s; pts2={pts2:?}"
828 );
829 }
830
831 #[test]
832 fn master_clock_audio_reset_should_update_fallback_base_pts() {
833 let mut clock = MasterClock::Audio {
834 samples_consumed: Arc::new(AtomicU64::new(0)),
835 sample_rate: 48_000,
836 fallback: None,
837 };
838 clock.activate_fallback_if_no_audio(Duration::from_secs(5));
839 clock.reset(Duration::from_secs(10));
841 let pts = clock.current_pts();
842 assert!(
843 pts >= Duration::from_secs(10),
844 "after reset, fallback must advance from the new base_pts; got {pts:?}"
845 );
846 assert!(
847 pts < Duration::from_secs(11),
848 "fallback must not advance 1 s in a unit test after reset; got {pts:?}"
849 );
850 }
851
852 #[test]
853 fn master_clock_audio_reset_should_not_arm_fallback_if_not_yet_active() {
854 let mut clock = MasterClock::Audio {
855 samples_consumed: Arc::new(AtomicU64::new(0)),
856 sample_rate: 48_000,
857 fallback: None,
858 };
859 clock.reset(Duration::ZERO);
861 assert!(
862 !clock.should_sync(),
863 "reset() before activate_fallback_if_no_audio must not arm the fallback"
864 );
865 assert_eq!(
866 clock.current_pts(),
867 Duration::ZERO,
868 "PTS must remain ZERO when fallback is not yet armed"
869 );
870 }
871
872 #[test]
873 fn master_clock_audio_rearm_should_advance_past_frozen_sample_pts() {
874 let frozen_frames: u64 = (45_222 * 48_000) / 1_000; let consumed = Arc::new(AtomicU64::new(frozen_frames));
880 let mut clock = MasterClock::Audio {
881 samples_consumed: Arc::clone(&consumed),
882 sample_rate: 48_000,
883 fallback: None,
884 };
885 let frozen_pts = Duration::from_secs_f64(frozen_frames as f64 / 48_000.0);
886 assert_eq!(
888 clock.current_pts(),
889 frozen_pts,
890 "clock must be frozen at audio EOF position before rearm"
891 );
892 clock.rearm_fallback_at(frozen_pts);
894 thread::sleep(Duration::from_millis(10));
895 let pts_after = clock.current_pts();
897 assert!(
898 pts_after > frozen_pts,
899 "clock must advance past frozen sample_pts after rearm; \
900 frozen={frozen_pts:?} after={pts_after:?}"
901 );
902 assert!(
903 pts_after < frozen_pts + Duration::from_secs(1),
904 "clock must not advance 1 s in a unit test after rearm; got {pts_after:?}"
905 );
906 }
907
908 #[test]
909 fn master_clock_audio_rearm_should_be_noop_for_system_clock() {
910 let mut clock = MasterClock::System {
911 started_at: Instant::now(),
912 base_pts: Duration::ZERO,
913 };
914 clock.rearm_fallback_at(Duration::from_secs(99));
916 assert!(
917 clock.should_sync(),
918 "System clock must always sync after rearm_fallback_at"
919 );
920 }
921
922 #[test]
923 fn audio_samples_snapshot_should_return_current_counter_for_audio_clock() {
924 let consumed = Arc::new(AtomicU64::new(12_345));
925 let clock = MasterClock::Audio {
926 samples_consumed: Arc::clone(&consumed),
927 sample_rate: 48_000,
928 fallback: None,
929 };
930 assert_eq!(
931 clock.audio_samples_snapshot(),
932 12_345,
933 "audio_samples_snapshot must reflect the current AtomicU64 value"
934 );
935 }
936
937 #[test]
938 fn audio_samples_snapshot_should_return_zero_for_system_clock() {
939 let clock = MasterClock::System {
940 started_at: Instant::now(),
941 base_pts: Duration::ZERO,
942 };
943 assert_eq!(
944 clock.audio_samples_snapshot(),
945 0,
946 "audio_samples_snapshot must return 0 for System clock"
947 );
948 }
949
950 #[test]
951 fn master_clock_audio_current_pts_should_advance_one_second_after_48k_frames() {
952 let consumed = Arc::new(AtomicU64::new(48_000));
956 let clock = MasterClock::Audio {
957 samples_consumed: Arc::clone(&consumed),
958 sample_rate: 48_000,
959 fallback: None,
960 };
961 assert_eq!(
962 clock.current_pts(),
963 Duration::from_secs(1),
964 "48 000 consumed frames / 48 000 Hz must equal exactly 1.0 s"
965 );
966 }
967
968 #[test]
969 fn master_clock_audio_native_rate_mismatch_demonstrates_bug() {
970 let consumed = Arc::new(AtomicU64::new(48_000));
975 let clock_wrong = MasterClock::Audio {
976 samples_consumed: Arc::clone(&consumed),
977 sample_rate: 44_100, fallback: None,
979 };
980 let pts_wrong = clock_wrong.current_pts();
981 assert!(
983 pts_wrong > Duration::from_secs(1),
984 "using native rate produces a clock that runs too fast; got {pts_wrong:?}"
985 );
986 assert!(
987 pts_wrong < Duration::from_millis(1_100),
988 "drift must be bounded to ~8.8 %; got {pts_wrong:?}"
989 );
990 }
991
992 #[test]
993 fn master_clock_system_activate_fallback_should_be_noop() {
994 let mut clock = MasterClock::System {
995 started_at: Instant::now(),
996 base_pts: Duration::ZERO,
997 };
998 clock.activate_fallback_if_no_audio(Duration::from_secs(99));
1000 assert!(
1001 clock.should_sync(),
1002 "System clock must always sync regardless of activate_fallback_if_no_audio"
1003 );
1004 }
1005}