# VideoPlayerControl2 리팩토링 계획 **날짜**: 2025-10-01 **상태**: ✅ 완료 (Phase 1-4) **접근 방식**: 모듈 분리 (Option 2) + 실용적 아키텍처 --- ## 🎯 목표 1. **VideoPlayerControl2 생성** - 기존 VideoPlayerControl과 함께 새로운 구현 생성 2. **모듈화 설계** - 과도한 엔지니어링 없이 책임 분리 3. **성능 영향 없음** - 현재 성능 유지 또는 개선 4. **점진적 마이그레이션** - 개발 중 VideoPlayerControl을 참조용으로 유지 5. **확장성** - 주요 리팩토링 없이 기능 추가 용이 --- ## 📊 현재 상태 분석 ### **VideoPlayerControl (원본)** - **크기**: 1,671 lines (.cpp) + 219 lines (.h) = 1,890 lines - **책임**: 7개 이상의 서로 다른 관심사가 혼재 - VavCore 플레이어 관리 - GPU/CPU 렌더링 전환 - 재생 타이밍 제어 - UI 이벤트 처리 - 메모리 풀 관리 (미사용) - 성능 모니터링 (미사용) - D3D Surface 관리 ### **해결할 문제** 1. ❌ **너무 많은 책임** - 단일 클래스가 모든 것을 처리 2. ❌ **죽은 코드** - MemoryPool, AdvancedPerformanceMonitor, CPU 렌더링 3. ❌ **복잡한 상태** - 전체에 분산된 11개의 atomic/bool 플래그 4. ❌ **테스트 어려움** - WinUI3와의 강한 결합 5. ❌ **확장 어려움** - 기능 추가 시 모든 것을 건드려야 함 --- ## 🏗️ 새로운 아키텍처 (VideoPlayerControl2) ### **설계 원칙** - ✅ **완벽보다 실용** - 분리가 필요한 것만 분리 - ✅ **상속보다 컴포지션** - 컴포지션 사용, 깊은 계층 구조 회피 - ✅ **단일 책임** - 각 클래스는 하나의 명확한 목적 - ✅ **간결하게 유지** - 최대 3-4개 클래스, 10개 이상 과도한 분리 방지 ### **클래스 구조** ``` VideoPlayerControl2.xaml.h/.cpp (UI 레이어 - ~400 lines) ├── PlaybackController (재생 로직 - ~300 lines) │ ├── 타이밍 스레드 관리 │ ├── Play/Pause/Stop 상태 머신 │ ├── VavCore 플레이어 생명주기 │ └── 프레임 디코드 조정 │ ├── FrameProcessor (프레임 처리 - ~250 lines) │ ├── 백그라운드 디코드 스레드 │ ├── 프레임 처리 조절 │ ├── 디코드 → 렌더 파이프라인 │ └── 오류 처리 │ └── SimpleGPURenderer (렌더링 - 기존, ~2,000 lines) └── NV12 파이프라인 (별도로 정리 예정) ``` **총 예상 크기**: ~950 lines (vs 1,890 = 50% 감소) --- ## 📝 상세 설계 ### **1. VideoPlayerControl2.xaml.h** (~150 lines) **책임:** - WinUI3 XAML UserControl 통합 - UI 이벤트 처리 (Click, SizeChanged, Loaded/Unloaded) - 프로퍼티 바인딩 (VideoSource, ShowControls, AutoPlay) - PlaybackController로 상태 쿼리 전달 - UI 스레드 업데이트 (DispatcherQueue) **주요 멤버:** ```cpp namespace winrt::Vav2Player::implementation { struct VideoPlayerControl2 : VideoPlayerControl2T { VideoPlayerControl2(); ~VideoPlayerControl2(); // XAML 이벤트 void UserControl_Loaded(...); void UserControl_Unloaded(...); void UserControl_SizeChanged(...); void HoverDetector_PointerEntered(...); void HoverDetector_PointerExited(...); // 공개 프로퍼티 (XAML 바인딩) winrt::hstring VideoSource(); void VideoSource(winrt::hstring const& value); bool ShowControls(); void ShowControls(bool value); bool AutoPlay(); void AutoPlay(bool value); // 공개 메서드 void LoadVideo(winrt::hstring const& filePath); void Play(); void Pause(); void Stop(); void Seek(double timeSeconds); // 상태 쿼리 bool IsVideoPlaying(); bool IsVideoLoaded(); double CurrentTime(); double Duration(); winrt::hstring Status(); private: // 핵심 컴포넌트 (컴포지션) std::unique_ptr m_playbackController; std::unique_ptr m_frameProcessor; std::unique_ptr m_gpuRenderer; // UI 상태만 winrt::hstring m_videoSource; bool m_showControls = true; bool m_autoPlay = false; winrt::hstring m_status = L"Ready"; // WinUI 컴포넌트 winrt::Microsoft::UI::Xaml::Controls::SwapChainPanel m_swapChainPanel{ nullptr }; // UI 헬퍼 void InitializeRenderer(); void UpdateStatus(winrt::hstring const& message); void UpdateVideoImageAspectFit(int videoWidth, int videoHeight); }; } ``` **더 이상 포함하지 않음:** - ❌ 타이밍 스레드 로직 - ❌ VavCore 플레이어 관리 - ❌ 프레임 처리 로직 - ❌ 디코더 타입 관리 - ❌ 메모리 풀 - ❌ 성능 모니터링 --- ### **2. PlaybackController.h/.cpp** (~300 lines) **책임:** - VavCore 플레이어 생명주기 (create, open, close, destroy) - 재생 상태 머신 (Stopped → Playing → Paused) - 타이밍 스레드 관리 (30fps/60fps) - 디코더 구성 - 비디오 메타데이터 (duration, resolution, FPS) **주요 멤버:** ```cpp class PlaybackController { public: PlaybackController(); ~PlaybackController(); // 생명주기 bool LoadVideo(const std::wstring& filePath); void Unload(); // 재생 제어 void Play(std::function onFrameReady); void Pause(); void Stop(); void Seek(double timeSeconds); // 상태 쿼리 bool IsPlaying() const { return m_isPlaying; } bool IsLoaded() const { return m_isLoaded; } double GetCurrentTime() const { return m_currentTime; } double GetDuration() const { return m_duration; } // 비디오 정보 uint32_t GetVideoWidth() const { return m_videoWidth; } uint32_t GetVideoHeight() const { return m_videoHeight; } double GetFrameRate() const { return m_frameRate; } // VavCore 접근 (FrameProcessor용) VavCorePlayer* GetVavCorePlayer() const { return m_vavCorePlayer; } // 디코더 구성 void SetDecoderType(VavCoreDecoderType type); VavCoreDecoderType GetDecoderType() const { return m_decoderType; } private: // VavCore 플레이어 VavCorePlayer* m_vavCorePlayer = nullptr; // 재생 상태 std::atomic m_isPlaying{false}; std::atomic m_isLoaded{false}; std::atomic m_shouldStopTiming{false}; // 비디오 메타데이터 uint32_t m_videoWidth = 0; uint32_t m_videoHeight = 0; double m_frameRate = 30.0; double m_duration = 0.0; double m_currentTime = 0.0; uint64_t m_currentFrame = 0; uint64_t m_totalFrames = 0; // 구성 VavCoreDecoderType m_decoderType = VAVCORE_DECODER_AUTO; std::wstring m_currentFilePath; // 타이밍 스레드 std::unique_ptr m_timingThread; std::function m_frameReadyCallback; // 헬퍼 메서드 bool InitializeVavCore(); void CleanupVavCore(); void StartTimingThread(); void StopTimingThread(); void TimingThreadLoop(); }; ``` **주요 설계 결정:** - ✅ **콜백 기반** - `Play(onFrameReady)`가 프레임 처리 트리거 - ✅ **렌더링 로직 없음** - 순수한 재생 제어만 - ✅ **VavCore 캡슐화** - 모든 vavcore_* 호출을 한 곳에 - ✅ **스레드 안전** - 상태를 위한 Atomic 플래그 --- ### **3. FrameProcessor.h/.cpp** (~250 lines) **책임:** - 백그라운드 프레임 디코딩 (UI 스레드 밖) - 프레임 처리 조절 (m_frameProcessing 플래그) - 디코드 → 렌더 파이프라인 조정 - 오류 처리 및 복구 - 렌더 완료 동기화 **주요 멤버:** ```cpp class FrameProcessor { public: FrameProcessor(); ~FrameProcessor(); // 렌더러로 초기화 void SetRenderer(SimpleGPURenderer* renderer); void SetDispatcherQueue(winrt::Microsoft::UI::Dispatching::DispatcherQueue queue); // 단일 프레임 처리 (PlaybackController 타이밍 스레드에서 호출) // 반환: 프레임이 처리되면 true, 스킵되면 false (이전 프레임이 아직 렌더링 중) void ProcessFrame(VavCorePlayer* player, std::function onComplete); // 현재 처리 중인지 확인 bool IsProcessing() const { return m_frameProcessing; } // 통계 uint64_t GetFramesDecoded() const { return m_framesDecoded; } uint64_t GetFramesDropped() const { return m_framesDropped; } uint64_t GetDecodeErrors() const { return m_decodeErrors; } private: SimpleGPURenderer* m_renderer = nullptr; // 비소유 winrt::Microsoft::UI::Dispatching::DispatcherQueue m_dispatcherQueue{ nullptr }; // 처리 상태 std::atomic m_frameProcessing{false}; // 통계 std::atomic m_framesDecoded{0}; std::atomic m_framesDropped{0}; std::atomic m_decodeErrors{0}; // 헬퍼 메서드 bool DecodeFrame(VavCorePlayer* player, VavCoreVideoFrame& frame); bool RenderFrame(const VavCoreVideoFrame& frame); }; ``` **주요 설계 결정:** - ✅ **상태 없음** - 내부 상태 없이 처리 로직만 - ✅ **논블로킹** - 이미 처리 중이면 즉시 반환 - ✅ **콜백 기반 완료** - 비동기 렌더 완료 - ✅ **간단한 인터페이스** - 하나의 주 메서드 `ProcessFrame()` --- ### **4. SimpleGPURenderer** (기존, 별도로 정리 예정) **현재 상태**: 여러 파이프라인이 있는 2,083 lines **미래**: NV12 전용 파이프라인으로 정리 (~800 lines) **현재로서는**: 있는 그대로 사용, VideoPlayerControl2 통합에 집중 --- ## 🔄 데이터 플로우 ### **초기화 플로우** ``` VideoPlayerControl2::UserControl_Loaded() ├─> InitializeRenderer() │ └─> m_gpuRenderer->InitializeWithSwapChain(...) │ └─> m_playbackController = std::make_unique() └─> m_frameProcessor = std::make_unique() └─> m_frameProcessor->SetRenderer(m_gpuRenderer.get()) ``` ### **비디오 로드 플로우** ``` VideoPlayerControl2::LoadVideo(filePath) └─> m_playbackController->LoadVideo(filePath) ├─> vavcore_create_player() ├─> vavcore_open_file() ├─> vavcore_set_decoder_type() └─> 메타데이터 추출 (width, height, fps, duration) ``` ### **재생 플로우** ``` VideoPlayerControl2::Play() └─> m_playbackController->Play(onFrameReady) └─> 타이밍 스레드 시작 (30fps 루프) └─> 콜백: VideoPlayerControl2::OnFrameReady() └─> m_frameProcessor->ProcessFrame(player, onComplete) ├─> [백그라운드] vavcore_decode_to_surface() │ └─> NV12 텍스처로 디코드 │ └─> [UI 스레드] DispatcherQueue.TryEnqueue() └─> m_gpuRenderer->RenderNV12TextureToBackBuffer() ├─> D3D12 NV12 → RGB 변환 └─> Present() └─> 콜백: onComplete(true) └─> m_frameProcessing = false ``` ### **주요 동기화 지점** 1. **타이밍 스레드** → `OnFrameReady()` 매 33.3ms (30fps) 2. **백그라운드 디코드** → VavCore에서 NV12 텍스처로 디코드 3. **UI 스레드 렌더** → D3D12 렌더 + Present 4. **완료 콜백** → `m_frameProcessing` 플래그 해제 --- ## 📂 파일 구조 ``` D:\Project\video-av1\vav2\platforms\windows\applications\vav2player\Vav2Player\ ├── VideoPlayerControl2.xaml (XAML UI 정의 - VideoPlayerControl.xaml에서 복사) ├── VideoPlayerControl2.xaml.h (150 lines - UI 레이어) ├── VideoPlayerControl2.xaml.cpp (400 lines - UI 구현) ├── VideoPlayerControl2.idl (WinRT 인터페이스 정의) │ ├── src\Playback\ │ ├── PlaybackController.h (80 lines) │ └── PlaybackController.cpp (300 lines) │ └── src\Playback\ ├── FrameProcessor.h (60 lines) └── FrameProcessor.cpp (250 lines) 총 새 코드: ~1,240 lines (잘 구조화됨) ``` --- ## 🚀 구현 계획 ### **Phase 1: 핵심 클래스 생성** (1-2시간) 1. **PlaybackController 스켈레톤 생성** - 기본 클래스 구조 - VavCore 생명주기 메서드 - 타이밍 스레드 스텁 2. **FrameProcessor 스켈레톤 생성** - 기본 클래스 구조 - ProcessFrame() 스텁 - 통계 추적 3. **VideoPlayerControl2.xaml 생성** - VideoPlayerControl.xaml에서 복사 - x:Class를 VideoPlayerControl2로 업데이트 4. **VideoPlayerControl2.xaml.h/cpp 생성** - 기본 XAML UserControl 구조 - PlaybackController + FrameProcessor 컴포지션 - 빈 메서드 스텁 **마일스톤**: 모든 파일 컴파일, 아직 기능 없음 --- ### **Phase 2: PlaybackController 구현** (1-2시간) 1. **VavCore 생명주기** - `LoadVideo()` - vavcore_create_player, open_file - `Unload()` - vavcore_close_file, destroy_player - 메타데이터 추출 2. **재생 제어** - `Play()` - 타이밍 스레드 시작 - `Pause()` - 타이밍 스레드 일시정지 - `Stop()` - 타이밍 스레드 중지, 상태 리셋 3. **타이밍 스레드** - 고해상도 타이머로 30fps 루프 - 프레임 준비 콜백 호출 - 적절한 스레드 생명주기 **마일스톤**: 비디오 로드, 재생 시작/중지 가능 (아직 렌더링 없음) --- ### **Phase 3: FrameProcessor 구현** (1시간) 1. **ProcessFrame() 로직** - m_frameProcessing atomic 플래그 - vavcore_decode_to_surface() 호출 - 렌더러 통합 2. **비동기 렌더 완료** - UI 스레드용 DispatcherQueue.TryEnqueue() - Present() 후 콜백 - 오류 처리 **마일스톤**: 전체 디코드 → 렌더 파이프라인 작동 --- ### **Phase 4: VideoPlayerControl2 UI 구현** (1시간) 1. **UI 이벤트 핸들러** - Loaded/Unloaded 생명주기 - Play/Pause 버튼 핸들러 - SizeChanged → 렌더러 리사이즈 2. **프로퍼티 구현** - VideoSource setter → LoadVideo() - 상태 쿼리 전달 - AutoPlay 로직 3. **UI 업데이트** - 상태 텍스트 업데이트 - AspectFit 렌더링 - 컨트롤 가시성 **마일스톤**: 완전히 작동하는 VideoPlayerControl2 --- ### **Phase 5: 통합 테스트** (완료되지 않음 - 향후 작업) 1. **테스트 페이지 생성** - MainVideoPage.xaml에 VideoPlayerControl2 추가 - VideoPlayerControl과 나란히 배치 (선택사항) 2. **기능 테스트** - 비디오 로드 - Play/Pause/Stop - Seek - 윈도우 리사이즈 3. **성능 테스트** - VideoPlayerControl과 FPS 비교 - 메모리 사용량 확인 - 프레임 드롭 검증 **마일스톤**: VideoPlayerControl2가 VideoPlayerControl 성능과 일치 --- ### **Phase 6: 문서화 & 마이그레이션** (완료되지 않음 - 향후 작업) 1. **코드 문서화** - 클래스 레벨 주석 추가 - 주요 메서드 문서화 - 사용 예제 추가 2. **마이그레이션 가이드** - VideoPlayerControl과의 차이점 문서화 - 마이그레이션 체크리스트 제공 **마일스톤**: 프로덕션 사용 준비 완료 --- ## ✅ 성공 기준 1. **기능**: VideoPlayerControl2가 VideoPlayerControl과 기능 동등 2. **성능**: 성능 저하 없음 (≤5% FPS 차이) 3. **코드 품질**: 50% 라인 감소, 명확한 관심사 분리 4. **유지보수성**: 이해, 수정, 확장 용이 5. **안정성**: 새로운 크래시나 버그 발생 없음 --- ## 🔍 테스트 전략 ### **단위 테스트** (선택사항, 하지만 권장) - PlaybackController: VavCore 생명주기, 상태 전환 - FrameProcessor: 프레임 처리 로직, 조절 - 독립적 테스트를 위한 Mock VavCore 플레이어 ### **통합 테스트** - 전체 재생 파이프라인 (로드 → 재생 → 렌더) - 상태 전환 (재생 → 일시정지 → 중지) - 오류 처리 (잘못된 파일, 디코드 오류) ### **성능 테스트** - FPS 측정 (30fps 지속) - 프레임 드롭 수 - 메모리 사용량 프로파일링 - CPU/GPU 활용 --- ## 🎯 향후 확장 (리팩토링 이후) VideoPlayerControl2가 안정화되면, 쉬운 확장: 1. **다중 디코더 지원** - 디코더 선택 UI 추가 2. **성능 오버레이** - 실시간 FPS/통계 표시 3. **Seek 바** - 비주얼 타임라인 스크러빙 4. **재생 목록** - 다중 비디오 큐 5. **Picture-in-Picture** - 분리 가능한 비디오 윈도우 6. **녹화** - 디코딩된 프레임을 디스크에 저장 7. **효과** - 실시간 비디오 필터 (밝기, 대비 등) 모든 확장은 핵심 아키텍처를 건드리지 않고 추가 가능. --- ## 📚 참고 **원본 코드** (개발 중 항상 사용 가능): - `VideoPlayerControl.xaml.h` (219 lines) - `VideoPlayerControl.xaml.cpp` (1,671 lines) - 비교를 위해 나란히 실행 가능 **사용된 디자인 패턴**: - **상속보다 컴포지션** - 컴포넌트 컴포지션, 상속 없음 - **단일 책임 원칙** - 각 클래스는 하나의 명확한 작업 - **의존성 주입** - 렌더러를 FrameProcessor로 전달 - **콜백 패턴** - 비동기 완료 콜백 - **RAII** - 스마트 포인터를 통한 리소스 관리 --- ## 🚨 위험 완화 1. **VideoPlayerControl 그대로 유지** - 원본 수정 없음 2. **나란히 테스트** - 동작 직접 비교 가능 3. **점진적 개발** - 각 Phase는 테스트 가능 4. **성능 벤치마크** - 각 Phase에서 측정 5. **쉬운 롤백** - 필요시 새 파일 삭제 --- ## 📊 완료 상태 ### **Phase 1: 핵심 클래스 생성** ✅ - PlaybackController 스켈레톤 생성 완료 - FrameProcessor 스켈레톤 생성 완료 - VideoPlayerControl2.xaml 생성 완료 - VideoPlayerControl2.xaml.h/cpp 생성 완료 ### **Phase 2: vcxproj 통합** ✅ - VideoPlayerControl2 파일들을 vcxproj에 추가 - ItemGroup Label="VideoPlayerControl2"로 구조화 - Headers, Sources, XAML, IDL 모두 추가 ### **Phase 3: 빌드 성공** ✅ - 모든 컴파일 오류 해결 - LogManager 싱글톤 패턴 적용 - WinRT enum if-else 체인 구현 - Clean build 성공 ### **Phase 4: 생성된 파일 검증** ✅ - VideoPlayerControl2.g.h 생성 확인 - VideoPlayerControl2.g.cpp 생성 확인 - VideoPlayerControl2.xaml.g.h 생성 확인 - IVideoPlayerControl2 인터페이스 검증 - WinRT 런타임 클래스 "Vav2Player.VideoPlayerControl2" 확인 ### **향후 작업** (Phase 5-6) - Phase 5: 통합 테스트 (실제 비디오 재생 검증) - Phase 6: 문서화 & 마이그레이션 가이드 --- **상태**: ✅ Phase 1-4 완료 - 빌드 및 WinRT 생성 성공 **다음 단계**: Phase 5 - 통합 테스트 (MainVideoPage.xaml에 VideoPlayerControl2 추가 및 실제 비디오 재생 테스트)