Add VideoWall feature: server-side preview clip generation and mobile grid view
Backend (Rust/Actix-web): - Add video_preview_clips table and PreviewDao for tracking preview generation - Add ffmpeg preview clip generator: 10 equally-spaced 1s segments at 480p with CUDA NVENC auto-detection - Add PreviewClipGenerator actor with semaphore-limited concurrent processing - Add GET /video/preview and POST /video/preview/status endpoints - Extend file watcher to detect and queue previews for new videos - Use relative paths consistently for DB storage (matching EXIF convention) Frontend (React Native/Expo): - Add VideoWall grid view with 2-3 column layout of looping preview clips - Add VideoWallItem component with ActiveVideoPlayer sub-component for lifecycle management - Add useVideoWall hook for batch status polling with 5s refresh - Add navigation button in grid header (visible when videos exist) - Use TextureView surface type to fix Android z-ordering issues - Optimize memory: players only mount while visible via FlatList windowSize - Configure ExoPlayer buffer options and caching for short clips - Tap to toggle audio focus, long press to open in full viewer Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
35
src/state.rs
35
src/state.rs
@@ -4,7 +4,10 @@ use crate::database::{
|
||||
SqliteCalendarEventDao, SqliteDailySummaryDao, SqliteExifDao, SqliteInsightDao,
|
||||
SqliteLocationHistoryDao, SqliteSearchHistoryDao,
|
||||
};
|
||||
use crate::video::actors::{PlaylistGenerator, StreamActor, VideoPlaylistManager};
|
||||
use crate::database::{PreviewDao, SqlitePreviewDao};
|
||||
use crate::video::actors::{
|
||||
PlaylistGenerator, PreviewClipGenerator, StreamActor, VideoPlaylistManager,
|
||||
};
|
||||
use actix::{Actor, Addr};
|
||||
use std::env;
|
||||
use std::sync::{Arc, Mutex};
|
||||
@@ -12,10 +15,12 @@ use std::sync::{Arc, Mutex};
|
||||
pub struct AppState {
|
||||
pub stream_manager: Arc<Addr<StreamActor>>,
|
||||
pub playlist_manager: Arc<Addr<VideoPlaylistManager>>,
|
||||
pub preview_clip_generator: Arc<Addr<PreviewClipGenerator>>,
|
||||
pub base_path: String,
|
||||
pub thumbnail_path: String,
|
||||
pub video_path: String,
|
||||
pub gif_path: String,
|
||||
pub preview_clips_path: String,
|
||||
pub excluded_dirs: Vec<String>,
|
||||
pub ollama: OllamaClient,
|
||||
pub sms_client: SmsApiClient,
|
||||
@@ -29,22 +34,32 @@ impl AppState {
|
||||
thumbnail_path: String,
|
||||
video_path: String,
|
||||
gif_path: String,
|
||||
preview_clips_path: String,
|
||||
excluded_dirs: Vec<String>,
|
||||
ollama: OllamaClient,
|
||||
sms_client: SmsApiClient,
|
||||
insight_generator: InsightGenerator,
|
||||
preview_dao: Arc<Mutex<Box<dyn PreviewDao>>>,
|
||||
) -> Self {
|
||||
let playlist_generator = PlaylistGenerator::new();
|
||||
let video_playlist_manager =
|
||||
VideoPlaylistManager::new(video_path.clone(), playlist_generator.start());
|
||||
|
||||
let preview_clip_generator = PreviewClipGenerator::new(
|
||||
preview_clips_path.clone(),
|
||||
base_path.clone(),
|
||||
preview_dao,
|
||||
);
|
||||
|
||||
Self {
|
||||
stream_manager,
|
||||
playlist_manager: Arc::new(video_playlist_manager.start()),
|
||||
preview_clip_generator: Arc::new(preview_clip_generator.start()),
|
||||
base_path,
|
||||
thumbnail_path,
|
||||
video_path,
|
||||
gif_path,
|
||||
preview_clips_path,
|
||||
excluded_dirs,
|
||||
ollama,
|
||||
sms_client,
|
||||
@@ -94,6 +109,8 @@ impl Default for AppState {
|
||||
Arc::new(Mutex::new(Box::new(SqliteExifDao::new())));
|
||||
let daily_summary_dao: Arc<Mutex<Box<dyn DailySummaryDao>>> =
|
||||
Arc::new(Mutex::new(Box::new(SqliteDailySummaryDao::new())));
|
||||
let preview_dao: Arc<Mutex<Box<dyn PreviewDao>>> =
|
||||
Arc::new(Mutex::new(Box::new(SqlitePreviewDao::new())));
|
||||
|
||||
// Initialize Google Takeout DAOs
|
||||
let calendar_dao: Arc<Mutex<Box<dyn CalendarEventDao>>> =
|
||||
@@ -119,16 +136,23 @@ impl Default for AppState {
|
||||
base_path.clone(),
|
||||
);
|
||||
|
||||
// Ensure preview clips directory exists
|
||||
let preview_clips_path = env::var("PREVIEW_CLIPS_DIRECTORY")
|
||||
.unwrap_or_else(|_| "preview_clips".to_string());
|
||||
std::fs::create_dir_all(&preview_clips_path).expect("Failed to create PREVIEW_CLIPS_DIRECTORY");
|
||||
|
||||
Self::new(
|
||||
Arc::new(StreamActor {}.start()),
|
||||
base_path,
|
||||
env::var("THUMBNAILS").expect("THUMBNAILS was not set in the env"),
|
||||
env::var("VIDEO_PATH").expect("VIDEO_PATH was not set in the env"),
|
||||
env::var("GIFS_DIRECTORY").expect("GIFS_DIRECTORY was not set in the env"),
|
||||
preview_clips_path,
|
||||
Self::parse_excluded_dirs(),
|
||||
ollama,
|
||||
sms_client,
|
||||
insight_generator,
|
||||
preview_dao,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -142,10 +166,11 @@ impl AppState {
|
||||
let temp_dir = tempfile::tempdir().expect("Failed to create temp directory");
|
||||
let base_path = temp_dir.path().to_path_buf();
|
||||
|
||||
// Create subdirectories for thumbnails, videos, and gifs
|
||||
// Create subdirectories for thumbnails, videos, gifs, and preview clips
|
||||
let thumbnail_path = create_test_subdir(&base_path, "thumbnails");
|
||||
let video_path = create_test_subdir(&base_path, "videos");
|
||||
let gif_path = create_test_subdir(&base_path, "gifs");
|
||||
let preview_clips_path = create_test_subdir(&base_path, "preview_clips");
|
||||
|
||||
// Initialize test AI clients
|
||||
let ollama = OllamaClient::new(
|
||||
@@ -186,6 +211,10 @@ impl AppState {
|
||||
base_path_str.clone(),
|
||||
);
|
||||
|
||||
// Initialize test preview DAO
|
||||
let preview_dao: Arc<Mutex<Box<dyn PreviewDao>>> =
|
||||
Arc::new(Mutex::new(Box::new(SqlitePreviewDao::new())));
|
||||
|
||||
// Create the AppState with the temporary paths
|
||||
AppState::new(
|
||||
Arc::new(StreamActor {}.start()),
|
||||
@@ -193,10 +222,12 @@ impl AppState {
|
||||
thumbnail_path.to_string_lossy().to_string(),
|
||||
video_path.to_string_lossy().to_string(),
|
||||
gif_path.to_string_lossy().to_string(),
|
||||
preview_clips_path.to_string_lossy().to_string(),
|
||||
Vec::new(), // No excluded directories for test state
|
||||
ollama,
|
||||
sms_client,
|
||||
insight_generator,
|
||||
preview_dao,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user