mirror of
https://github.com/v0l/zap-stream-core.git
synced 2025-06-16 08:59:35 +00:00
191 lines
5.4 KiB
Rust
191 lines
5.4 KiB
Rust
use anyhow::{bail, Result};
|
|
use clap::Parser;
|
|
use config::Config;
|
|
use ffmpeg_rs_raw::ffmpeg_sys_the_third::{av_log_set_callback, av_version_info};
|
|
use ffmpeg_rs_raw::{av_log_redirect, rstr};
|
|
use hyper::server::conn::http1;
|
|
use hyper_util::rt::TokioIo;
|
|
use log::{error, info};
|
|
use std::net::SocketAddr;
|
|
use std::path::PathBuf;
|
|
use std::str::FromStr;
|
|
use std::sync::Arc;
|
|
use std::time::Duration;
|
|
use tokio::net::TcpListener;
|
|
use tokio::sync::RwLock;
|
|
use tokio::task::JoinHandle;
|
|
use tokio::time::sleep;
|
|
use url::Url;
|
|
#[cfg(feature = "rtmp")]
|
|
use zap_stream_core::ingress::rtmp;
|
|
#[cfg(feature = "srt")]
|
|
use zap_stream_core::ingress::srt;
|
|
#[cfg(feature = "test-pattern")]
|
|
use zap_stream_core::ingress::test;
|
|
|
|
use crate::api::Api;
|
|
use crate::http::{HttpServer, StreamCache};
|
|
use crate::monitor::BackgroundMonitor;
|
|
use crate::overseer::ZapStreamOverseer;
|
|
use crate::settings::Settings;
|
|
use zap_stream_core::ingress::{file, tcp};
|
|
|
|
mod api;
|
|
mod blossom;
|
|
mod http;
|
|
mod monitor;
|
|
mod overseer;
|
|
mod settings;
|
|
|
|
#[derive(Parser, Debug)]
|
|
struct Args {}
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<()> {
|
|
pretty_env_logger::init();
|
|
|
|
let _args = Args::parse();
|
|
|
|
unsafe {
|
|
av_log_set_callback(Some(av_log_redirect));
|
|
info!("FFMPEG version={}", rstr!(av_version_info()));
|
|
}
|
|
|
|
let builder = Config::builder()
|
|
.add_source(config::File::with_name("config.yaml"))
|
|
.add_source(config::Environment::with_prefix("APP"))
|
|
.build()?;
|
|
|
|
let settings: Settings = builder.try_deserialize()?;
|
|
let overseer = settings.get_overseer().await?;
|
|
|
|
// Create ingress listeners
|
|
let mut tasks = vec![];
|
|
for e in &settings.endpoints {
|
|
match try_create_listener(e, &settings.output_dir, &overseer) {
|
|
Ok(l) => tasks.push(l),
|
|
Err(e) => error!("{}", e),
|
|
}
|
|
}
|
|
|
|
let http_addr: SocketAddr = settings.listen_http.parse()?;
|
|
let index_template = include_str!("../index.html");
|
|
|
|
let api = Api::new(overseer.clone(), settings.clone());
|
|
// Create shared stream cache
|
|
let stream_cache: StreamCache = Arc::new(RwLock::new(None));
|
|
// HTTP server
|
|
let server = HttpServer::new(
|
|
index_template.to_string(),
|
|
PathBuf::from(settings.output_dir),
|
|
api,
|
|
stream_cache,
|
|
);
|
|
tasks.push(tokio::spawn(async move {
|
|
let listener = TcpListener::bind(&http_addr).await?;
|
|
|
|
loop {
|
|
let (socket, _) = listener.accept().await?;
|
|
let io = TokioIo::new(socket);
|
|
let server = server.clone();
|
|
tokio::spawn(async move {
|
|
if let Err(e) = http1::Builder::new().serve_connection(io, server).await {
|
|
error!("Failed to handle request: {}", e);
|
|
}
|
|
});
|
|
}
|
|
}));
|
|
|
|
// Background worker
|
|
let mut bg = BackgroundMonitor::new(overseer.clone());
|
|
tasks.push(tokio::spawn(async move {
|
|
loop {
|
|
if let Err(e) = bg.check().await {
|
|
error!("{}", e);
|
|
}
|
|
sleep(Duration::from_secs(10)).await;
|
|
}
|
|
}));
|
|
|
|
// Join tasks and get errors
|
|
for handle in tasks {
|
|
if let Err(e) = handle.await? {
|
|
error!("{e}");
|
|
}
|
|
}
|
|
info!("Server closed");
|
|
Ok(())
|
|
}
|
|
|
|
pub enum ListenerEndpoint {
|
|
SRT { endpoint: String },
|
|
RTMP { endpoint: String },
|
|
TCP { endpoint: String },
|
|
File { path: PathBuf },
|
|
TestPattern,
|
|
}
|
|
|
|
impl FromStr for ListenerEndpoint {
|
|
type Err = anyhow::Error;
|
|
|
|
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
|
let url: Url = s.parse()?;
|
|
match url.scheme() {
|
|
"srt" => Ok(Self::SRT {
|
|
endpoint: format!("{}:{}", url.host().unwrap(), url.port().unwrap()),
|
|
}),
|
|
"rtmp" => Ok(Self::RTMP {
|
|
endpoint: format!("{}:{}", url.host().unwrap(), url.port().unwrap()),
|
|
}),
|
|
"tcp" => Ok(Self::TCP {
|
|
endpoint: format!("{}:{}", url.host().unwrap(), url.port().unwrap()),
|
|
}),
|
|
"file" => Ok(Self::File {
|
|
path: PathBuf::from(url.path()),
|
|
}),
|
|
"test-pattern" => Ok(Self::TestPattern),
|
|
_ => bail!("Unsupported endpoint scheme: {}", url.scheme()),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn try_create_listener(
|
|
u: &str,
|
|
out_dir: &str,
|
|
overseer: &Arc<ZapStreamOverseer>,
|
|
) -> Result<JoinHandle<Result<()>>> {
|
|
let ep = ListenerEndpoint::from_str(u)?;
|
|
match ep {
|
|
#[cfg(feature = "srt")]
|
|
ListenerEndpoint::SRT { endpoint } => Ok(tokio::spawn(srt::listen(
|
|
out_dir.to_string(),
|
|
endpoint,
|
|
overseer.clone(),
|
|
))),
|
|
#[cfg(feature = "rtmp")]
|
|
ListenerEndpoint::RTMP { endpoint } => Ok(tokio::spawn(rtmp::listen(
|
|
out_dir.to_string(),
|
|
endpoint,
|
|
overseer.clone(),
|
|
))),
|
|
ListenerEndpoint::TCP { endpoint } => Ok(tokio::spawn(tcp::listen(
|
|
out_dir.to_string(),
|
|
endpoint,
|
|
overseer.clone(),
|
|
))),
|
|
ListenerEndpoint::File { path } => Ok(tokio::spawn(file::listen(
|
|
out_dir.to_string(),
|
|
path,
|
|
overseer.clone(),
|
|
))),
|
|
#[cfg(feature = "test-pattern")]
|
|
ListenerEndpoint::TestPattern => Ok(tokio::spawn(test::listen(
|
|
out_dir.to_string(),
|
|
overseer.clone(),
|
|
))),
|
|
_ => {
|
|
bail!("Unknown endpoint config: {u}");
|
|
}
|
|
}
|
|
}
|