@@ -3,66 +3,79 @@ use std::process::Stdio;
33use anyhow:: { bail, Context , Result } ;
44use tokio:: {
55 io:: { AsyncRead , AsyncWrite } ,
6- process:: Command ,
6+ process:: { Child , Command } ,
77} ;
88use tracing:: { info, warn} ;
99
10- // pub async fn ensure_ffmpeg_installed() -> anyhow::Result<()> {
11- // use ffmpeg_sidecar::{command::ffmpeg_is_installed, paths::ffmpeg_path, version::ffmpeg_version};
12- // if !ffmpeg_is_installed() {
13- // tracing::info!("FFmpeg not found, downloading...");
14- // tokio::task::spawn_blocking(|| {
15- // ffmpeg_sidecar::download::auto_download().map_err(|e| anyhow!(format!("{e}")))
16- // })
17- // .await??;
18- // }
19- // let version = ffmpeg_version().map_err(|e| anyhow!(format!("{e}")))?;
20- // println!("FFmpeg version: {}", version);
21- // Ok(())
22- // }
10+ pub fn capture_stdin ( ) -> Result < impl AsyncRead + Send + Unpin + ' static > {
11+ let input = [ "-i" , "pipe:" ] ;
12+ capture_ffmpeg ( input. to_vec ( ) )
13+ }
2314
24- pub fn publish ( ) -> Result < impl AsyncRead + Send + Unpin + ' static > {
25- // let bin = ffmpeg_path();
26- let bin = "ffmpeg" ;
15+ pub fn capture_camera ( ) -> Result < impl AsyncRead + Send + Unpin + ' static > {
2716 let input = match std:: env:: consts:: OS {
17+ // TODO: this is same as desktop, find out if we can find the correct device
2818 "macos" => vec ! [ "-f" , "avfoundation" , "-i" , "default:default" , "-r" , "30" ] ,
2919 "linux" => vec ! [
20+ "-f" ,
21+ "pulse" ,
22+ "-ac" ,
23+ "2" ,
24+ "-i" ,
25+ "default" ,
3026 "-f" ,
3127 "v4l2" ,
3228 "-i" ,
3329 "/dev/video0" ,
3430 "-r" ,
3531 "30" ,
32+ ] ,
33+ "windows" => {
34+ // TODO: find out how windows dshow args work
35+ // likely have to get device name from `ffmpeg -list_devices`
36+ bail ! ( "windows is not yet supported" ) ;
37+ }
38+ _ => bail ! ( "Unsupported OS" . to_string( ) ) ,
39+ } ;
40+ capture_ffmpeg ( input)
41+ }
42+
43+ pub fn capture_desktop ( ) -> Result < impl AsyncRead + Send + Unpin + ' static > {
44+ let input = match std:: env:: consts:: OS {
45+ // TODO: this is same as camera, find out if we can find the correct device
46+ "macos" => vec ! [ "-f" , "avfoundation" , "-i" , "default:default" , "-r" , "30" ] ,
47+ "linux" => vec ! [
3648 "-f" ,
3749 "pulse" ,
3850 "-ac" ,
3951 "2" ,
4052 "-i" ,
4153 "default" ,
54+ "-framerate" ,
55+ "30" ,
56+ "-f" ,
57+ "x11grab" ,
58+ "-i" ,
59+ ":0.0" ,
4260 ] ,
4361 "windows" => {
44- // TODO: find out how windows dshow args work
45- // likely have to get device name from `ffmpeg -list_devices`
46- bail ! ( "windows is not yet supported" ) ;
62+ vec ! [ "-f" , "dshow" , "-i" , "video='screen-capture-recorder'" ]
4763 }
4864 _ => bail ! ( "Unsupported OS" . to_string( ) ) ,
4965 } ;
50- // TODO: Find out if this actually helps, found it on the internets..
51- let reduce_latency = [
52- "-max_delay" ,
53- "0" ,
54- "-analyzeduration" ,
55- "0" ,
56- "-flags" ,
57- "+low_delay" ,
58- "-fflags" ,
59- "+nobuffer" ,
60- ] ;
61- let encode = [
66+ capture_ffmpeg ( input)
67+ }
68+
69+ pub fn capture_ffmpeg ( input : Vec < & ' static str > ) -> Result < impl AsyncRead + Send + Unpin + ' static > {
70+ let bin = match std:: env:: consts:: OS {
71+ "windows" => "ffmpeg.exe" ,
72+ _ => "ffmpeg" ,
73+ } ;
74+ let encode_video = [
6275 "-vcodec" ,
6376 "libx264" ,
6477 "-preset" ,
65- "ultrafast " ,
78+ "fast " ,
6679 "-tune" ,
6780 "zerolatency" ,
6881 ] ;
@@ -77,16 +90,11 @@ pub fn publish() -> Result<impl AsyncRead + Send + Unpin + 'static> {
7790 "-" ,
7891 ] ;
7992 let mut args = vec ! [ "-hide_banner" , "-v" , "quiet" ] ;
80- args. extend_from_slice ( & reduce_latency) ;
8193 args. extend_from_slice ( & input) ;
82- args. extend_from_slice ( & encode ) ;
94+ args. extend_from_slice ( & encode_video ) ;
8395 args. extend_from_slice ( & output) ;
8496
85- info ! (
86- "spawning ffmpeg: {} {}" ,
87- bin,
88- args. join( " " )
89- ) ;
97+ info ! ( "spawn: {} {}" , bin, args. join( " " ) ) ;
9098 let mut cmd = Command :: new ( bin) ;
9199 cmd. args ( args) ;
92100 cmd. stdout ( Stdio :: piped ( ) ) ;
@@ -95,38 +103,76 @@ pub fn publish() -> Result<impl AsyncRead + Send + Unpin + 'static> {
95103 . stdout
96104 . take ( )
97105 . context ( "failed to capture FFmpeg stdout" ) ?;
98- // Ensure the child process is spawned in the runtime so it can
99- // make progress on its own while we await for any output.
100- tokio:: spawn ( async move {
101- let status = child. wait ( ) . await ;
102- match status {
103- Ok ( status) => info ! ( "FFmpeg exited with status {status}" ) ,
104- Err ( err) => warn ! ( "FFmpeg exited with error {err}" ) ,
105- }
106- } ) ;
106+ tokio:: spawn ( wait_and_log ( bin, child) ) ;
107107 Ok ( stdout)
108108}
109109
110- pub fn subscribe ( ) -> Result < impl AsyncWrite + Send + Unpin + ' static > {
111- let bin = "ffplay" ;
112- let args = [ "-" ] ;
110+ pub fn out_ffplay ( ) -> Result < impl AsyncWrite + Send + Unpin + ' static > {
111+ let bin = match std:: env:: consts:: OS {
112+ "windows" => "ffplay.exe" ,
113+ _ => "ffplay" ,
114+ } ;
115+ // TODO: Find out if this actually helps, found it on the internets..
116+ let args = [
117+ "-nostats" ,
118+ "-sync" ,
119+ "ext" ,
120+ "-max_delay" ,
121+ "0" ,
122+ "-analyzeduration" ,
123+ "0" ,
124+ "-flags" ,
125+ "+low_delay" ,
126+ "-fflags" ,
127+ "+nobuffer+fastseek+flush_packets" ,
128+ "-" ,
129+ ] ;
113130
131+ info ! ( "spawn: {} {}" , bin, args. join( " " ) ) ;
114132 let mut cmd = Command :: new ( bin) ;
115133 cmd. args ( args) ;
116134 cmd. stdin ( Stdio :: piped ( ) ) ;
117135 let mut child = cmd. spawn ( ) ?;
118- let stdin = child
119- . stdin
120- . take ( )
121- . context ( "failed to capture FFmpeg stdout" ) ?;
122- // Ensure the child process is spawned in the runtime so it can
123- // make progress on its own while we await for any output.
124- tokio:: spawn ( async move {
125- let status = child. wait ( ) . await ;
126- match status {
127- Ok ( status) => info ! ( "ffplay exited with status {status}" ) ,
128- Err ( err) => warn ! ( "ffplay exited with error {err}" ) ,
129- }
130- } ) ;
136+ let stdin = child. stdin . take ( ) . context ( "failed to capture stdin" ) ?;
137+ tokio:: spawn ( wait_and_log ( bin, child) ) ;
131138 Ok ( stdin)
132139}
140+
141+ pub fn out_mpv ( ) -> Result < impl AsyncWrite + Send + Unpin + ' static > {
142+ let bin = match std:: env:: consts:: OS {
143+ "windows" => "mpv.exe" ,
144+ _ => "mpv" ,
145+ } ;
146+ let args = [ "--profile=low-latency" , "--no-cache" , "--untimed" , "-" ] ;
147+
148+ info ! ( "spawn: {} {}" , bin, args. join( " " ) ) ;
149+ let mut cmd = Command :: new ( bin) ;
150+ cmd. args ( args) ;
151+ cmd. stdin ( Stdio :: piped ( ) ) ;
152+ let mut child = cmd. spawn ( ) ?;
153+ let stdin = child. stdin . take ( ) . context ( "failed to capture stdin" ) ?;
154+ tokio:: spawn ( wait_and_log ( bin, child) ) ;
155+ Ok ( stdin)
156+ }
157+
158+ async fn wait_and_log ( name : & str , mut child : Child ) {
159+ let status = child. wait ( ) . await ;
160+ match status {
161+ Ok ( status) => info ! ( "{name} exited with status {status}" ) ,
162+ Err ( err) => warn ! ( "{name} exited with error {err}" ) ,
163+ }
164+ }
165+
166+ // pub async fn ensure_ffmpeg_installed() -> anyhow::Result<()> {
167+ // use ffmpeg_sidecar::{command::ffmpeg_is_installed, paths::ffmpeg_path, version::ffmpeg_version};
168+ // if !ffmpeg_is_installed() {
169+ // tracing::info!("FFmpeg not found, downloading...");
170+ // tokio::task::spawn_blocking(|| {
171+ // ffmpeg_sidecar::download::auto_download().map_err(|e| anyhow!(format!("{e}")))
172+ // })
173+ // .await??;
174+ // }
175+ // let version = ffmpeg_version().map_err(|e| anyhow!(format!("{e}")))?;
176+ // println!("FFmpeg version: {}", version);
177+ // Ok(())
178+ // }
0 commit comments