chore: cleanup

This commit is contained in:
kieran 2025-01-30 11:45:05 +00:00
parent 9045bb93e4
commit e11d7dc787
No known key found for this signature in database
GPG Key ID: DE71CEB3925BE941
16 changed files with 322 additions and 111 deletions

2
Cargo.lock generated
View File

@ -1044,7 +1044,7 @@ dependencies = [
[[package]] [[package]]
name = "ffmpeg-rs-raw" name = "ffmpeg-rs-raw"
version = "0.1.0" version = "0.1.0"
source = "git+https://git.v0l.io/Kieran/ffmpeg-rs-raw.git?rev=df69b2f05da4279e36ad55086d77b45b2caf5174#df69b2f05da4279e36ad55086d77b45b2caf5174" source = "git+https://git.v0l.io/Kieran/ffmpeg-rs-raw.git?rev=a63b88ef3c8f58c7c0ac57d361d06ff0bb3ed385#a63b88ef3c8f58c7c0ac57d361d06ff0bb3ed385"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"ffmpeg-sys-the-third", "ffmpeg-sys-the-third",

View File

@ -7,7 +7,7 @@ members = [
] ]
[workspace.dependencies] [workspace.dependencies]
ffmpeg-rs-raw = { git = "https://git.v0l.io/Kieran/ffmpeg-rs-raw.git", rev = "df69b2f05da4279e36ad55086d77b45b2caf5174" } ffmpeg-rs-raw = { git = "https://git.v0l.io/Kieran/ffmpeg-rs-raw.git", rev = "a63b88ef3c8f58c7c0ac57d361d06ff0bb3ed385" }
tokio = { version = "1.36.0", features = ["rt", "rt-multi-thread", "macros"] } tokio = { version = "1.36.0", features = ["rt", "rt-multi-thread", "macros"] }
anyhow = { version = "^1.0.91", features = ["backtrace"] } anyhow = { version = "^1.0.91", features = ["backtrace"] }
async-trait = "0.1.77" async-trait = "0.1.77"

View File

@ -1,5 +1,4 @@
- RTMP?
- Setup multi-variant output - Setup multi-variant output
- API parity https://git.v0l.io/Kieran/zap.stream/issues/7 - API parity https://git.v0l.io/Kieran/zap.stream/issues/7
- HLS-LL - HLS-LL
- Delete old segments (HLS+N94) - Delete old segments (N94)

View File

@ -144,7 +144,7 @@ impl PipelineRunner {
}; };
// run transcoder pipeline // run transcoder pipeline
let (mut pkt, stream) = self.demuxer.get_packet()?; let (mut pkt, _stream) = self.demuxer.get_packet()?;
if pkt.is_null() { if pkt.is_null() {
return Ok(false); return Ok(false);
} }
@ -159,7 +159,7 @@ impl PipelineRunner {
}; };
let mut egress_results = vec![]; let mut egress_results = vec![];
for frame in frames { for (frame, stream) in frames {
// Copy frame from GPU if using hwaccel decoding // Copy frame from GPU if using hwaccel decoding
let mut frame = get_frame_from_hw(frame)?; let mut frame = get_frame_from_hw(frame)?;
(*frame).time_base = (*stream).time_base; (*frame).time_base = (*stream).time_base;

View File

@ -3,6 +3,7 @@ use anyhow::Result;
use sqlx::{Executor, MySqlPool, Row}; use sqlx::{Executor, MySqlPool, Row};
use uuid::Uuid; use uuid::Uuid;
#[derive(Clone)]
pub struct ZapStreamDb { pub struct ZapStreamDb {
db: MySqlPool, db: MySqlPool,
} }

View File

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[features] [features]
default = ["srt", "rtmp"] default = ["srt", "rtmp", "test-pattern"]
srt = ["zap-stream-core/srt"] srt = ["zap-stream-core/srt"]
rtmp = ["zap-stream-core/rtmp"] rtmp = ["zap-stream-core/rtmp"]
test-pattern = ["zap-stream-core/test-pattern", "zap-stream-db/test-pattern"] test-pattern = ["zap-stream-core/test-pattern", "zap-stream-db/test-pattern"]

View File

@ -5,6 +5,10 @@ endpoints:
- "rtmp://127.0.0.1:3336" - "rtmp://127.0.0.1:3336"
- "srt://127.0.0.1:3335" - "srt://127.0.0.1:3335"
- "tcp://127.0.0.1:3334" - "tcp://127.0.0.1:3334"
- "test-pattern://"
# Public hostname which points to the IP address used to listen for all [endpoints]
endpoints_public_hostname: "localhost"
# Output directory for recording / hls # Output directory for recording / hls
output_dir: "./out" output_dir: "./out"
@ -40,8 +44,8 @@ overseer:
zap-stream: zap-stream:
cost: 16 cost: 16
nsec: "nsec1wya428srvpu96n4h78gualaj7wqw4ecgatgja8d5ytdqrxw56r2se440y4" nsec: "nsec1wya428srvpu96n4h78gualaj7wqw4ecgatgja8d5ytdqrxw56r2se440y4"
blossom: #blossom:
- "http://localhost:8881" # - "http://localhost:8881"
relays: relays:
- "ws://localhost:7766" - "ws://localhost:7766"
database: "mysql://root:root@localhost:3368/zap_stream?max_connections=2" database: "mysql://root:root@localhost:3368/zap_stream?max_connections=2"

View File

@ -0,0 +1,2 @@
create database route96;
create database zap_stream;

View File

@ -0,0 +1,5 @@
listen: "0.0.0.0:8000"
database: "mysql://root:root@db:3306/route96"
storage_dir: "./data"
max_upload_bytes: 5e+9
public_url: "http://localhost:8881"

View File

@ -47,7 +47,7 @@ relay {
port = 7777 port = 7777
# Set OS-limit on maximum number of open files/sockets (if 0, don't attempt to set) (restart required) # Set OS-limit on maximum number of open files/sockets (if 0, don't attempt to set) (restart required)
nofiles = 1000000 nofiles = 0
# HTTP header that contains the client's real IP, before reverse proxying (ie x-real-ip) (MUST be all lower-case) # HTTP header that contains the client's real IP, before reverse proxying (ie x-real-ip) (MUST be all lower-case)
realIpHeader = "" realIpHeader = ""
@ -64,6 +64,12 @@ relay {
# NIP-11: Alternative administrative contact (email, website, etc) # NIP-11: Alternative administrative contact (email, website, etc)
contact = "" contact = ""
# NIP-11: URL pointing to an image to be used as an icon for the relay
icon = ""
# List of supported lists as JSON array, or empty string to use default. Example: "[1,2]"
nips = ""
} }
# Maximum accepted incoming websocket frame size (should be larger than max event) (restart required) # Maximum accepted incoming websocket frame size (should be larger than max event) (restart required)
@ -86,7 +92,7 @@ relay {
writePolicy { writePolicy {
# If non-empty, path to an executable script that implements the writePolicy plugin logic # If non-empty, path to an executable script that implements the writePolicy plugin logic
plugin = "/app/write-policy.py" plugin = ""
} }
compression { compression {
@ -135,4 +141,4 @@ relay {
# Maximum records that sync will process before returning an error # Maximum records that sync will process before returning an error
maxSyncEvents = 1000000 maxSyncEvents = 1000000
} }
} }

View File

@ -18,14 +18,14 @@ services:
blossom: blossom:
depends_on: depends_on:
- db - db
image: voidic/route96 image: voidic/route96:latest
environment: environment:
- "RUST_LOG=info" - "RUST_LOG=info"
ports: ports:
- "8881:8000" - "8881:8000"
volumes: volumes:
- "blossom:/app/data" - "blossom:/app/data"
- "./dev-setup/route96.toml:/app/config.toml" - "./dev-setup/route96.yaml:/app/config.yaml"
volumes: volumes:
db: db:
blossom: blossom:

View File

@ -0,0 +1,192 @@
use crate::http::check_nip98_auth;
use crate::settings::Settings;
use crate::ListenerEndpoint;
use anyhow::{anyhow, bail, Result};
use bytes::Bytes;
use fedimint_tonic_lnd::tonic::codegen::Body;
use http_body_util::combinators::BoxBody;
use http_body_util::{BodyExt, Full};
use hyper::body::Incoming;
use hyper::{Method, Request, Response};
use nostr_sdk::{serde_json, Event, PublicKey};
use serde::{Deserialize, Serialize};
use std::net::SocketAddr;
use std::str::FromStr;
use url::Url;
use zap_stream_db::ZapStreamDb;
#[derive(Clone)]
pub struct Api {
db: ZapStreamDb,
settings: Settings,
}
impl Api {
pub fn new(db: ZapStreamDb, settings: Settings) -> Self {
Self { db, settings }
}
pub async fn handler(
self,
req: Request<Incoming>,
) -> Result<Response<BoxBody<Bytes, anyhow::Error>>, anyhow::Error> {
let base = Response::builder()
.header("server", "zap-stream")
.header("access-control-allow-origin", "*")
.header("access-control-allow-headers", "*")
.header("access-control-allow-methods", "HEAD, GET");
Ok(match (req.method(), req.uri().path()) {
(&Method::GET, "/api/v1/account") => {
let auth = check_nip98_auth(&req)?;
let rsp = self.get_account(&auth.pubkey).await?;
return Ok(base.body(Self::body_json(&rsp)?)?);
}
(&Method::PATCH, "/api/v1/account") => {
let auth = check_nip98_auth(&req)?;
let body = req.collect().await?.to_bytes();
let r_body: PatchAccount = serde_json::from_slice(&body)?;
let rsp = self.update_account(&auth.pubkey, r_body).await?;
return Ok(base.body(Self::body_json(&rsp)?)?);
}
(&Method::GET, "/api/v1/topup") => {
let auth = check_nip98_auth(&req)?;
let url: Url = req.uri().to_string().parse()?;
let amount: usize = url
.query_pairs()
.find_map(|(k, v)| if k == "amount" { Some(v) } else { None })
.and_then(|v| v.parse().ok())
.ok_or(anyhow!("Missing amount"))?;
let rsp = self.topup(&auth.pubkey, amount).await?;
return Ok(base.body(Self::body_json(&rsp)?)?);
}
(&Method::PATCH, "/api/v1/event") => {
bail!("Not implemented")
}
(&Method::POST, "/api/v1/withdraw") => {
bail!("Not implemented")
}
(&Method::POST, "/api/v1/account/forward") => {
bail!("Not implemented")
}
(&Method::DELETE, "/api/v1/account/forward/<id>") => {
bail!("Not implemented")
}
(&Method::GET, "/api/v1/account/history") => {
bail!("Not implemented")
}
(&Method::GET, "/api/v1/account/keys") => {
bail!("Not implemented")
}
_ => {
if req.method() == Method::OPTIONS {
base.body(Default::default())?
} else {
base.status(404).body(Default::default())?
}
}
})
}
fn body_json<T: Serialize>(obj: &T) -> Result<BoxBody<Bytes, anyhow::Error>> {
Ok(Full::from(serde_json::to_string(obj)?)
.map_err(|e| match e {})
.boxed())
}
async fn get_account(&self, pubkey: &PublicKey) -> Result<AccountInfo> {
let uid = self.db.upsert_user(&pubkey.to_bytes()).await?;
let user = self.db.get_user(uid).await?;
Ok(AccountInfo {
endpoints: self
.settings
.endpoints
.iter()
.filter_map(|e| match ListenerEndpoint::from_str(&e).ok()? {
ListenerEndpoint::SRT { endpoint } => {
let addr: SocketAddr = endpoint.parse().ok()?;
Some(Endpoint {
name: "SRT".to_string(),
url: format!("srt://{}:{}", self.settings.endpoints_public_hostname, addr.port()),
key: user.stream_key.clone(),
capabilities: vec![],
})
}
ListenerEndpoint::RTMP { endpoint } => {
let addr: SocketAddr = endpoint.parse().ok()?;
Some(Endpoint {
name: "RTMP".to_string(),
url: format!("rtmp://{}:{}", self.settings.endpoints_public_hostname, addr.port()),
key: user.stream_key.clone(),
capabilities: vec![],
})
}
ListenerEndpoint::TCP { endpoint } => {
let addr: SocketAddr = endpoint.parse().ok()?;
Some(Endpoint {
name: "TCP".to_string(),
url: format!("tcp://{}:{}", self.settings.endpoints_public_hostname, addr.port()),
key: user.stream_key.clone(),
capabilities: vec![],
})
}
ListenerEndpoint::File { .. } => None,
ListenerEndpoint::TestPattern => None,
})
.collect(),
event: None,
balance: user.balance as u64,
tos: AccountTos {
accepted: user.tos_accepted.is_some(),
link: "https://zap.stream/tos".to_string(),
},
})
}
async fn update_account(&self, pubkey: &PublicKey, account: PatchAccount) -> Result<()> {
bail!("Not implemented")
}
async fn topup(&self, pubkey: &PublicKey, amount: usize) -> Result<TopupResponse> {
bail!("Not implemented")
}
}
#[derive(Deserialize, Serialize)]
struct AccountInfo {
pub endpoints: Vec<Endpoint>,
pub event: Option<Event>,
pub balance: u64,
pub tos: AccountTos,
}
#[derive(Deserialize, Serialize)]
struct Endpoint {
pub name: String,
pub url: String,
pub key: String,
pub capabilities: Vec<String>,
}
#[derive(Deserialize, Serialize)]
struct EndpointCost {
pub unit: String,
pub rate: u16,
}
#[derive(Deserialize, Serialize)]
struct AccountTos {
pub accepted: bool,
pub link: String,
}
#[derive(Deserialize, Serialize)]
struct PatchAccount {
pub accept_tos: Option<bool>,
}
#[derive(Deserialize, Serialize)]
struct TopupResponse {
pub pr: String,
}

View File

@ -1,3 +1,7 @@
use crate::api::Api;
use crate::overseer::ZapStreamOverseer;
use anyhow::{bail, Result};
use base64::Engine;
use bytes::Bytes; use bytes::Bytes;
use futures_util::TryStreamExt; use futures_util::TryStreamExt;
use http_body_util::combinators::BoxBody; use http_body_util::combinators::BoxBody;
@ -5,7 +9,8 @@ use http_body_util::{BodyExt, Full, StreamBody};
use hyper::body::{Frame, Incoming}; use hyper::body::{Frame, Incoming};
use hyper::service::Service; use hyper::service::Service;
use hyper::{Method, Request, Response}; use hyper::{Method, Request, Response};
use log::error; use log::{error, info};
use nostr_sdk::{serde_json, Event};
use std::future::Future; use std::future::Future;
use std::path::PathBuf; use std::path::PathBuf;
use std::pin::Pin; use std::pin::Pin;
@ -13,21 +18,20 @@ use std::sync::Arc;
use tokio::fs::File; use tokio::fs::File;
use tokio_util::io::ReaderStream; use tokio_util::io::ReaderStream;
use zap_stream_core::overseer::Overseer; use zap_stream_core::overseer::Overseer;
use crate::overseer::ZapStreamOverseer;
#[derive(Clone)] #[derive(Clone)]
pub struct HttpServer { pub struct HttpServer {
index: String, index: String,
files_dir: PathBuf, files_dir: PathBuf,
overseer: Arc<ZapStreamOverseer>, api: Api,
} }
impl HttpServer { impl HttpServer {
pub fn new(index: String, files_dir: PathBuf, overseer: Arc<ZapStreamOverseer>) -> Self { pub fn new(index: String, files_dir: PathBuf, api: Api) -> Self {
Self { Self {
index, index,
files_dir, files_dir,
overseer, api,
} }
} }
} }
@ -81,9 +85,9 @@ impl Service<Request<Incoming>> for HttpServer {
} }
// otherwise handle in overseer // otherwise handle in overseer
let overseer = self.overseer.clone(); let mut api = self.api.clone();
Box::pin(async move { Box::pin(async move {
match overseer.api(req).await { match api.handler(req).await {
Ok(res) => Ok(res), Ok(res) => Ok(res),
Err(e) => { Err(e) => {
error!("{}", e); error!("{}", e);
@ -93,3 +97,22 @@ impl Service<Request<Incoming>> for HttpServer {
}) })
} }
} }
pub fn check_nip98_auth(req: &Request<Incoming>) -> Result<Event> {
let auth = if let Some(a) = req.headers().get("authorization") {
a.to_str()?
} else {
bail!("Authorization header missing");
};
if !auth.starts_with("Nostr ") {
bail!("Invalid authorization scheme");
}
let json =
String::from_utf8(base64::engine::general_purpose::STANDARD.decode(auth[6..].as_bytes())?)?;
info!("{}", json);
// TODO: check tags
Ok(serde_json::from_str::<Event>(&json)?)
}

View File

@ -8,6 +8,7 @@ use hyper_util::rt::TokioIo;
use log::{error, info}; use log::{error, info};
use std::net::SocketAddr; use std::net::SocketAddr;
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use tokio::net::TcpListener; use tokio::net::TcpListener;
@ -21,13 +22,15 @@ use zap_stream_core::ingress::srt;
#[cfg(feature = "test-pattern")] #[cfg(feature = "test-pattern")]
use zap_stream_core::ingress::test; use zap_stream_core::ingress::test;
use zap_stream_core::ingress::{file, tcp}; use crate::api::Api;
use zap_stream_core::overseer::Overseer;
use crate::http::HttpServer; use crate::http::HttpServer;
use crate::monitor::BackgroundMonitor; use crate::monitor::BackgroundMonitor;
use crate::overseer::ZapStreamOverseer; use crate::overseer::ZapStreamOverseer;
use crate::settings::Settings; use crate::settings::Settings;
use zap_stream_core::ingress::{file, tcp};
use zap_stream_core::overseer::Overseer;
mod api;
mod blossom; mod blossom;
mod http; mod http;
mod monitor; mod monitor;
@ -56,6 +59,7 @@ async fn main() -> Result<()> {
let settings: Settings = builder.try_deserialize()?; let settings: Settings = builder.try_deserialize()?;
let overseer = settings.get_overseer().await?; let overseer = settings.get_overseer().await?;
// Create ingress listeners
let mut tasks = vec![]; let mut tasks = vec![];
for e in &settings.endpoints { for e in &settings.endpoints {
match try_create_listener(e, &settings.output_dir, &overseer) { match try_create_listener(e, &settings.output_dir, &overseer) {
@ -67,10 +71,12 @@ async fn main() -> Result<()> {
let http_addr: SocketAddr = settings.listen_http.parse()?; let http_addr: SocketAddr = settings.listen_http.parse()?;
let index_html = include_str!("../index.html").replace("%%PUBLIC_URL%%", &settings.public_url); let index_html = include_str!("../index.html").replace("%%PUBLIC_URL%%", &settings.public_url);
let api = Api::new(overseer.database(), settings.clone());
// HTTP server
let server = HttpServer::new( let server = HttpServer::new(
index_html, index_html,
PathBuf::from(settings.output_dir), PathBuf::from(settings.output_dir),
overseer.clone(), api,
); );
tasks.push(tokio::spawn(async move { tasks.push(tokio::spawn(async move {
let listener = TcpListener::bind(&http_addr).await?; let listener = TcpListener::bind(&http_addr).await?;
@ -87,7 +93,7 @@ async fn main() -> Result<()> {
} }
})); }));
// spawn background job // Background worker
let mut bg = BackgroundMonitor::new(overseer.clone()); let mut bg = BackgroundMonitor::new(overseer.clone());
tasks.push(tokio::spawn(async move { tasks.push(tokio::spawn(async move {
loop { loop {
@ -98,6 +104,7 @@ async fn main() -> Result<()> {
} }
})); }));
// Join tasks and get errors
for handle in tasks { for handle in tasks {
if let Err(e) = handle.await? { if let Err(e) = handle.await? {
error!("{e}"); error!("{e}");
@ -107,37 +114,69 @@ async fn main() -> Result<()> {
Ok(()) 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( fn try_create_listener(
u: &str, u: &str,
out_dir: &str, out_dir: &str,
overseer: &Arc<ZapStreamOverseer>, overseer: &Arc<ZapStreamOverseer>,
) -> Result<JoinHandle<Result<()>>> { ) -> Result<JoinHandle<Result<()>>> {
let url: Url = u.parse()?; let ep = ListenerEndpoint::from_str(u)?;
match url.scheme() { match ep {
#[cfg(feature = "srt")] #[cfg(feature = "srt")]
"srt" => Ok(tokio::spawn(srt::listen( ListenerEndpoint::SRT { endpoint } => Ok(tokio::spawn(srt::listen(
out_dir.to_string(), out_dir.to_string(),
format!("{}:{}", url.host().unwrap(), url.port().unwrap()), endpoint,
overseer.clone(), overseer.clone(),
))), ))),
#[cfg(feature = "rtmp")] #[cfg(feature = "rtmp")]
"rtmp" => Ok(tokio::spawn(rtmp::listen( ListenerEndpoint::RTMP { endpoint } => Ok(tokio::spawn(rtmp::listen(
out_dir.to_string(), out_dir.to_string(),
format!("{}:{}", url.host().unwrap(), url.port().unwrap()), endpoint,
overseer.clone(), overseer.clone(),
))), ))),
"tcp" => Ok(tokio::spawn(tcp::listen( ListenerEndpoint::TCP { endpoint } => Ok(tokio::spawn(tcp::listen(
out_dir.to_string(), out_dir.to_string(),
format!("{}:{}", url.host().unwrap(), url.port().unwrap()), endpoint,
overseer.clone(), overseer.clone(),
))), ))),
"file" => Ok(tokio::spawn(file::listen( ListenerEndpoint::File { path } => Ok(tokio::spawn(file::listen(
out_dir.to_string(), out_dir.to_string(),
PathBuf::from(url.path()), path,
overseer.clone(), overseer.clone(),
))), ))),
#[cfg(feature = "test-pattern")] #[cfg(feature = "test-pattern")]
"test-pattern" => Ok(tokio::spawn(test::listen( ListenerEndpoint::TestPattern => Ok(tokio::spawn(test::listen(
out_dir.to_string(), out_dir.to_string(),
overseer.clone(), overseer.clone(),
))), ))),

View File

@ -1,10 +1,5 @@
use crate::blossom::{BlobDescriptor, Blossom}; use crate::blossom::{BlobDescriptor, Blossom};
use zap_stream_core::egress::hls::HlsEgress; use crate::settings::LndSettings;
use zap_stream_core::egress::EgressConfig;
use zap_stream_core::ingress::ConnectionInfo;
use zap_stream_core::overseer::{IngressInfo, IngressStreamType, Overseer};
use zap_stream_core::pipeline::{EgressType, PipelineConfig};
use zap_stream_core::variant::{StreamMapping, VariantStream};
use anyhow::{anyhow, bail, Result}; use anyhow::{anyhow, bail, Result};
use async_trait::async_trait; use async_trait::async_trait;
use base64::alphabet::STANDARD; use base64::alphabet::STANDARD;
@ -14,6 +9,7 @@ use chrono::Utc;
use fedimint_tonic_lnd::verrpc::VersionRequest; use fedimint_tonic_lnd::verrpc::VersionRequest;
use ffmpeg_rs_raw::ffmpeg_sys_the_third::AVCodecID::AV_CODEC_ID_MJPEG; use ffmpeg_rs_raw::ffmpeg_sys_the_third::AVCodecID::AV_CODEC_ID_MJPEG;
use ffmpeg_rs_raw::ffmpeg_sys_the_third::AVFrame; use ffmpeg_rs_raw::ffmpeg_sys_the_third::AVFrame;
use ffmpeg_rs_raw::ffmpeg_sys_the_third::AVPixelFormat::AV_PIX_FMT_YUV420P;
use ffmpeg_rs_raw::Encoder; use ffmpeg_rs_raw::Encoder;
use futures_util::FutureExt; use futures_util::FutureExt;
use http_body_util::combinators::BoxBody; use http_body_util::combinators::BoxBody;
@ -31,16 +27,20 @@ use std::fs::create_dir_all;
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
use ffmpeg_rs_raw::ffmpeg_sys_the_third::AVPixelFormat::AV_PIX_FMT_YUV420P;
use tokio::sync::RwLock; use tokio::sync::RwLock;
use url::Url; use url::Url;
use uuid::Uuid; use uuid::Uuid;
use zap_stream_core::egress::hls::HlsEgress;
use zap_stream_core::egress::EgressConfig;
use zap_stream_core::ingress::ConnectionInfo;
use zap_stream_core::overseer::{IngressInfo, IngressStreamType, Overseer};
use zap_stream_core::pipeline::{EgressType, PipelineConfig};
use zap_stream_core::variant::audio::AudioVariant; use zap_stream_core::variant::audio::AudioVariant;
use zap_stream_core::variant::mapping::VariantMapping; use zap_stream_core::variant::mapping::VariantMapping;
use zap_stream_core::variant::video::VideoVariant; use zap_stream_core::variant::video::VideoVariant;
use zap_stream_core::variant::{StreamMapping, VariantStream};
use zap_stream_db::sqlx::Encode; use zap_stream_db::sqlx::Encode;
use zap_stream_db::{UserStream, UserStreamState, ZapStreamDb}; use zap_stream_db::{UserStream, UserStreamState, ZapStreamDb};
use crate::settings::LndSettings;
const STREAM_EVENT_KIND: u16 = 30_313; const STREAM_EVENT_KIND: u16 = 30_313;
@ -100,7 +100,7 @@ impl ZapStreamOverseer {
PathBuf::from(&lnd.cert), PathBuf::from(&lnd.cert),
PathBuf::from(&lnd.macaroon), PathBuf::from(&lnd.macaroon),
) )
.await?; .await?;
let version = lnd let version = lnd
.versioner() .versioner()
@ -133,50 +133,8 @@ impl ZapStreamOverseer {
}) })
} }
pub(crate) async fn api(&self, req: Request<Incoming>) -> Result<Response<BoxBody<Bytes, anyhow::Error>>> { pub(crate) fn database(&self) -> ZapStreamDb {
let base = Response::builder() self.db.clone()
.header("server", "zap-stream-core")
.header("access-control-allow-origin", "*")
.header("access-control-allow-headers", "*")
.header("access-control-allow-methods", "HEAD, GET");
Ok(match (req.method(), req.uri().path()) {
(&Method::GET, "/api/v1/account") => {
self.check_nip98_auth(req)?;
base.body(Default::default())?
}
(&Method::PATCH, "/api/v1/account") => {
bail!("Not implemented")
}
(&Method::GET, "/api/v1/topup") => {
bail!("Not implemented")
}
(&Method::PATCH, "/api/v1/event") => {
bail!("Not implemented")
}
(&Method::POST, "/api/v1/withdraw") => {
bail!("Not implemented")
}
(&Method::POST, "/api/v1/account/forward") => {
bail!("Not implemented")
}
(&Method::DELETE, "/api/v1/account/forward/<id>") => {
bail!("Not implemented")
}
(&Method::GET, "/api/v1/account/history") => {
bail!("Not implemented")
}
(&Method::GET, "/api/v1/account/keys") => {
bail!("Not implemented")
}
_ => {
if req.method() == Method::OPTIONS {
base.body(Default::default())?
} else {
base.status(404).body(Default::default())?
}
}
})
} }
fn stream_to_event_builder(&self, stream: &UserStream) -> Result<EventBuilder> { fn stream_to_event_builder(&self, stream: &UserStream) -> Result<EventBuilder> {
@ -280,25 +238,6 @@ impl ZapStreamOverseer {
let u: Url = self.public_url.parse()?; let u: Url = self.public_url.parse()?;
Ok(u.join(path)?.to_string()) Ok(u.join(path)?.to_string())
} }
fn check_nip98_auth(&self, req: Request<Incoming>) -> Result<()> {
let auth = if let Some(a) = req.headers().get("authorization") {
a.to_str()?
} else {
bail!("Authorization header missing");
};
if !auth.starts_with("Nostr ") {
bail!("Invalid authorization scheme");
}
let json = String::from_utf8(
base64::engine::general_purpose::STANDARD.decode(auth[6..].as_bytes())?,
)?;
info!("{}", json);
Ok(())
}
} }
#[derive(Serialize)] #[derive(Serialize)]
@ -459,7 +398,6 @@ impl Overseer for ZapStreamOverseer {
} }
} }
fn get_default_variants(info: &IngressInfo) -> Result<Vec<VariantStream>> { fn get_default_variants(info: &IngressInfo) -> Result<Vec<VariantStream>> {
let mut vars: Vec<VariantStream> = vec![]; let mut vars: Vec<VariantStream> = vec![];
if let Some(video_src) = info if let Some(video_src) = info

View File

@ -1,7 +1,6 @@
use crate::overseer::ZapStreamOverseer; use crate::overseer::ZapStreamOverseer;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::sync::Arc; use std::sync::Arc;
use zap_stream_core::overseer::Overseer;
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Settings { pub struct Settings {
@ -12,6 +11,9 @@ pub struct Settings {
/// - rtmp://localhost:1935 /// - rtmp://localhost:1935
pub endpoints: Vec<String>, pub endpoints: Vec<String>,
/// Public facing hostname that maps to [endpoints]
pub endpoints_public_hostname: String,
/// Where to store output (static files) /// Where to store output (static files)
pub output_dir: String, pub output_dir: String,
@ -21,7 +23,7 @@ pub struct Settings {
/// Binding address for http server serving files from [output_dir] /// Binding address for http server serving files from [output_dir]
pub listen_http: String, pub listen_http: String,
/// Overseer service see [crate::overseer::Overseer] for more info /// Overseer service see [Overseer] for more info
pub overseer: OverseerConfig, pub overseer: OverseerConfig,
} }