1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
use clap::Parser;
use std::{
    collections::{HashMap, HashSet},
    path::PathBuf,
    sync::{
        atomic::{AtomicUsize, Ordering},
        Arc, Mutex,
    },
};
use tracing::log::info;
use lazy_static::lazy_static;

lazy_static! {
    pub static ref VIDEO_EXTENSIONS: Vec<String> = vec![
        "mp4".into(),
        "avi".into(),
        "flv".into(),
        "heic".into(),
        "mkv".into(),
        "mov".into(),
        "mpg".into(),
        "mpeg".into(),
        "m4v".into(),
        "webm".into(),
        "wmv".into(),
        "3gp".into()
    ];
}



/// The configuration for the video server.
#[derive(Parser, Debug, Clone)]
pub struct VideoPlayerConfig {
    #[clap(short, long, default_value = "assets")]
    pub assets_root: String,

    #[clap(short, long, default_value = "9092")]
    pub port: u16,

    #[clap(short, long, default_value = "0.0.0.0")]
    pub host: String,
}

/// The video index state that is shared between all requests.
/// Store a list of videos and their paths.
#[derive(Default)]
pub struct VideoPlayerState {
    pub videos: HashMap<String, String>,
    video_extensions: HashSet<String>,
    next_index: AtomicUsize,
    root: Option<String>,
}

pub type SharedState = Arc<Mutex<VideoPlayerState>>;

impl VideoPlayerState {
    /// Create a new video index state.
    /// This will configure the video extensions that are interpreted as videos.
    pub fn new() -> Self {
        Self {
            video_extensions: HashSet::from_iter(
                VIDEO_EXTENSIONS.iter().map(|s| s.to_string()),
            ),
            ..Default::default()
        }
    }


    fn advance_index(&mut self) {
        self.next_index
            .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
    }

    /// Check if a path is a supported video file.
    pub fn is_video_file<P: AsRef<std::path::Path>>(&self, path: P) -> bool {
        if let Some(extension) = path.as_ref().extension() {
            if self.video_extensions.contains(extension.to_str().unwrap()) {
                return true;
            }
        }
        false
    }

    pub fn load_videos<P: AsRef<std::path::Path>>(&mut self, root: P) -> std::io::Result<()> {
        self.visit_dirs(root)
    }

    /// Load a video from a path.
    pub fn load_video(&mut self, path: PathBuf) {
        let stored_file_name = path.to_str().unwrap().to_string();
        let extension = path.extension().unwrap();
        let server_path = format!(
            "{}.{}",
            self.next_index.load(Ordering::SeqCst),
            extension.to_str().unwrap()
        );
        info!("Loading video: {} as {}", stored_file_name, server_path);
        self.advance_index();
        self.videos.insert(server_path, stored_file_name);
    }

    /// Recursively visit all directories and load videos from them.
    pub fn visit_dirs<P: AsRef<std::path::Path>>(&mut self, root: P) -> std::io::Result<()> {
        if root.as_ref().is_dir() {
            if let Ok(dir) = std::fs::read_dir(root.as_ref()) {
                for entry in dir {
                    let entry = entry?;
                    let path = entry.path();
                    if path.is_dir() {
                        self.visit_dirs(path)?;
                    } else if self.is_video_file(path.as_path()) {
                        self.load_video(path);
                    }
                }
            }
        }
        Ok(())
    }

    /// Build a new video index state from a config.
    pub fn build(config: &VideoPlayerConfig) -> Self {
        let mut state = Self::new();
        state.root = Some(config.assets_root.clone());
        state.load_videos(state.root.clone().unwrap()).unwrap();
        state
    }

    /// Reload the video index state.
    pub fn reload(&mut self) {
        self.next_index = AtomicUsize::new(0);
        self.videos.clear();
        self.load_videos(self.root.clone().unwrap()).unwrap();
    }
}