Skip to main content

ace_player/
lib.rs

1//! ACE Player — High-Performance Rust Media Engine SDK
2//!
3//! Memory-safe, high-concurrency media playback engine.
4//! Designed to deliver ACE-generated content to enterprise clients.
5//!
6//! # Architecture
7//! - `decoder` — video + audio decoding (symphonia + ffmpeg-next)
8//! - `sync`    — AV synchronization (tokio-based, race-condition-free)
9//! - `output`  — native + WebAssembly rendering targets
10//! - `drm`     — DRM module (Phase 2: Widevine)
11
12pub mod decoder;
13pub mod sync;
14pub mod output;
15pub mod drm;
16
17use anyhow::Result;
18use serde::{Deserialize, Serialize};
19
20/// Playback configuration for a media session.
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct PlayerConfig {
23    /// Source URL or local file path
24    pub source: String,
25    /// Target width (default: 1920)
26    pub width: u32,
27    /// Target height (default: 1080)
28    pub height: u32,
29    /// Volume 0.0–1.0 (default: 1.0)
30    pub volume: f32,
31    /// Loop playback
32    pub looping: bool,
33    /// Hardware acceleration enabled
34    pub hw_accel: bool,
35}
36
37impl Default for PlayerConfig {
38    fn default() -> Self {
39        Self {
40            source: String::new(),
41            width: 1920,
42            height: 1080,
43            volume: 1.0,
44            looping: false,
45            hw_accel: true,
46        }
47    }
48}
49
50/// Core ACE Player — manages decode, sync, and output lifecycle.
51pub struct AcePlayer {
52    config: PlayerConfig,
53}
54
55impl AcePlayer {
56    pub fn new(config: PlayerConfig) -> Self {
57        Self { config }
58    }
59
60    /// Load and prepare a media source for playback.
61    pub async fn load(&mut self) -> Result<MediaInfo> {
62        decoder::probe(&self.config.source).await
63    }
64
65    /// Start playback (non-blocking, tokio task).
66    pub async fn play(&self) -> Result<()> {
67        let config = self.config.clone();
68        tokio::spawn(async move {
69            if let Err(e) = _run_playback(config).await {
70                tracing::error!("Playback error: {e}");
71            }
72        });
73        Ok(())
74    }
75}
76
77async fn _run_playback(config: PlayerConfig) -> Result<()> {
78    let (video_tx, video_rx) = tokio::sync::mpsc::channel(32);
79    let (audio_tx, audio_rx) = tokio::sync::mpsc::channel(32);
80
81    // Spawn decode tasks in parallel (fearless concurrency)
82    let source = config.source.clone();
83    let decode_video = tokio::spawn(decoder::video::decode_stream(source.clone(), video_tx));
84    let decode_audio = tokio::spawn(decoder::audio::decode_stream(source, audio_tx));
85
86    // AV sync + render
87    let render = tokio::spawn(sync::av_sync::run(video_rx, audio_rx));
88
89    let _ = tokio::join!(decode_video, decode_audio, render);
90    Ok(())
91}
92
93/// Media info returned after probing a source.
94#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct MediaInfo {
96    pub duration_secs: f64,
97    pub width: u32,
98    pub height: u32,
99    pub has_video: bool,
100    pub has_audio: bool,
101    pub codec_video: Option<String>,
102    pub codec_audio: Option<String>,
103    pub fps: f64,
104}
105
106// ── Python bindings (pyo3) ────────────────────────────────────────────────
107
108#[cfg(feature = "python-bindings")]
109use pyo3::prelude::*;
110
111#[cfg(feature = "python-bindings")]
112#[pymodule]
113fn ace_player_py(m: &Bound<'_, PyModule>) -> PyResult<()> {
114    m.add_class::<PyAcePlayer>()?;
115    Ok(())
116}
117
118#[cfg(feature = "python-bindings")]
119#[pyclass(name = "AcePlayer")]
120struct PyAcePlayer {
121    inner: AcePlayer,
122}
123
124#[cfg(feature = "python-bindings")]
125#[pymethods]
126impl PyAcePlayer {
127    #[new]
128    fn new(source: String) -> Self {
129        Self {
130            inner: AcePlayer::new(PlayerConfig {
131                source,
132                ..Default::default()
133            }),
134        }
135    }
136
137    fn play(&self) -> PyResult<()> {
138        let rt = tokio::runtime::Runtime::new().unwrap();
139        rt.block_on(self.inner.play()).map_err(|e| {
140            pyo3::exceptions::PyRuntimeError::new_err(e.to_string())
141        })
142    }
143}