using Cysharp.Threading.Tasks; using System; using UVC.UI.Loading; namespace UVC.Factory.Playback.UI { /// /// UIPlaybackController는 UIPlayback(View)에서 발생하는 이벤트를 받아 /// 실제 재생, 일시정지, 데이터 준비 등 비즈니스 로직을 처리하는 컨트롤러입니다. /// /// 예시: UIPlayback과의 연결 /// /// // UIPlayback에서 컨트롤러를 생성할 때 View를 넘겨줍니다. /// var controller = new UIPlaybackController(this); /// /// public class UIPlaybackController : IDisposable { /// /// View 역할을 하는 UIPlayback 참조입니다. /// private readonly UIPlayback view; // 재생 중 여부 private bool isPlaying = false; // 데이터 준비 중 여부 private bool preparingData = false; // 재생에 필요한 정보 private string date; private string time; private string fileName; // 타이머 동작 여부 private bool isTick = false; /// /// 타이머 동작 여부를 외부에서 제어할 수 있습니다. /// true로 설정하면 내부적으로 OnTimer()가 실행됩니다. /// /// /// /// // 타이머를 시작하려면 /// controller.IsTick = true; /// // 타이머를 멈추려면 /// controller.IsTick = false; /// /// public bool IsTick { get => isTick; set { if (isTick != value) { isTick = value; if (isTick) OnTimer().Forget(); } } } /// /// 생성자에서 View와 이벤트를 연결합니다. /// /// UIPlayback 인스턴스 public UIPlaybackController(UIPlayback view) { this.view = view; SubscribeToViewEvents(); } /// /// View에서 발생하는 이벤트를 컨트롤러의 메서드와 연결합니다. /// private void SubscribeToViewEvents() { view.OnClickExitButton += OnClickExit; view.OnClickPlayButton += OnClickPlay; view.OnChangeProgressValue += OnChangeProgress; view.OnChangeSpeedValue += OnChangeSpeed; view.OnChangeOpacityValue += OnValueChangedOpacity; } /// /// 컨트롤러가 더 이상 필요 없을 때 이벤트 연결을 해제합니다. /// public void Dispose() { if (isPlaying) IsTick = false; view.OnClickExitButton -= OnClickExit; view.OnClickPlayButton -= OnClickPlay; view.OnChangeProgressValue -= OnChangeProgress; view.OnChangeSpeedValue -= OnChangeSpeed; view.OnChangeOpacityValue -= OnValueChangedOpacity; } /// /// 재생에 필요한 데이터를 설정하고 UI를 초기화합니다. /// /// 날짜(예: "2024-07-29") /// 시간(초 단위 문자열, 예: "3600") /// 파일명 /// 비동기 작업(UniTask) /// /// /// // 재생 데이터를 설정하는 예시 /// await controller.SetData("2024-07-29", "3600", "sample.sqlite"); /// /// public async UniTask SetData(string date, string time, string fileName) { this.date = date; this.time = time; this.fileName = fileName; int timeInt = int.Parse(time); view.UpdateDateTime(date.Substring(2).Replace("-", ".")); view.InitProgressBar(timeInt); view.InitSpeedSlider(); view.SetOpacity(1); UpdateTimeScale(1); preparingData = true; view.SetUIInteractable(!preparingData); isPlaying = false; UpdatePlayState(); // 실제 데이터 로딩 (비동기) await PlaybackService.Instance.DispatchBaseInfoData(date, time, fileName); preparingData = false; view.SetUIInteractable(!preparingData); } /// /// 종료 버튼 클릭 시 호출됩니다. /// /// /// UI를 숨기고, 재생 상태를 초기화하며, PlaybackService에 종료를 알립니다. /// private void OnClickExit() { UILoading.Show(); isPlaying = false; UpdatePlayState(); view.Hide(); PlaybackService.Instance.Exit(); } /// /// 재생/일시정지 버튼 클릭 시 호출됩니다. /// /// /// 데이터 준비 중에는 동작하지 않습니다. /// private void OnClickPlay() { if (preparingData) return; isPlaying = !isPlaying; UpdatePlayState(); } // /// 재생 위치(프로그레스바) 변경 시 호출됩니다. /// /// 변경된 위치(초) private void OnChangeProgress(int newValue) { ChangePlayTime().Forget(); } /// /// 재생 속도 변경 시 호출됩니다. /// /// 변경된 속도 값 private void OnChangeSpeed(int newValue) { UpdateTimeScale(isPlaying ? view.GetSpeedValue() : 1); } /// /// 투명도 슬라이더 변경 시 호출됩니다. /// /// 0~1 사이의 투명도 값 private void OnValueChangedOpacity(float newValue) { view.SetOpacity(newValue); } /// /// 재생/일시정지 상태에 따라 UI와 타이머를 갱신합니다. /// private void UpdatePlayState() { view.UpdatePlayButtonState(isPlaying); IsTick = isPlaying; OnChangeSpeed(view.GetSpeedValue()); } /// /// 재생 위치를 변경할 때 호출됩니다. /// 데이터 준비, UI 비활성화, 로딩 표시 등 처리 후 재생 위치를 이동합니다. /// private async UniTaskVoid ChangePlayTime() { bool tempIsPlaying = isPlaying; isPlaying = false; UpdatePlayState(); int newSecond = view.GetProgressValue(); if (newSecond == view.GetProgressMaxValue()) { newSecond -= 60; view.SetProgressValue(newSecond); } preparingData = true; view.SetUIInteractable(!preparingData); IsTick = false; UILoading.Show(); int minute = newSecond / 60; int seconds = newSecond % 60; // 새로운 위치로 데이터 요청 await PlaybackService.Instance.DispatchBaseInfoData(date, time, fileName, minute.ToString("00"), seconds.ToString("00")); preparingData = false; view.SetUIInteractable(!preparingData); if (tempIsPlaying) { isPlaying = true; UpdatePlayState(); } UILoading.Hide(); } /// /// 재생 중일 때 일정 간격으로 호출되어 진행 바를 업데이트합니다. /// /// /// IsTick이 true일 때만 동작합니다. /// /// /// /// // 타이머를 시작하려면 /// controller.IsTick = true; /// /// private async UniTaskVoid OnTimer() { if (view.GetProgressValue() >= view.GetProgressMaxValue()) { if (isPlaying) OnClickPlay(); return; } view.SetProgressValue(view.GetProgressValue() + 1); // 실시간 데이터 요청 PlaybackService.Instance.DispatchRealTimeData(view.GetProgressValue(), view.GetSpeedValue()).Forget(); if (IsTick) { // 재생 속도에 따라 대기 시간 조절 await UniTask.Delay(TimeSpan.FromMilliseconds(1000 / view.GetSpeedValue())); if (IsTick) OnTimer().Forget(); } } /// /// 재생 속도를 PlaybackService에 반영합니다. /// /// 적용할 재생 속도 /// /// /// // 재생 속도를 2배로 변경 /// controller.UpdateTimeScale(2f); /// /// internal void UpdateTimeScale(float timeScale) { PlaybackService.Instance.TimeScale = timeScale; } } }