mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-12 16:55:37 +02:00
✨ feat: Add streaming support (#125)
This adds: - [x] Keyboard and mouse handling on the frontend - [x] Video and audio streaming from the backend to the frontend - [x] Input server that works with Websockets Update - 17/11 - [ ] Master docker container to run this - [ ] Steam runtime - [ ] Entrypoint.sh --------- Co-authored-by: Kristian Ollikainen <14197772+DatCaptainHorse@users.noreply.github.com> Co-authored-by: Kristian Ollikainen <DatCaptainHorse@users.noreply.github.com>
This commit is contained in:
59
packages/server/src/args/app_args.rs
Normal file
59
packages/server/src/args/app_args.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
pub struct AppArgs {
|
||||
/// Verbose output mode
|
||||
pub verbose: bool,
|
||||
/// Debug the pipeline by showing a window on host
|
||||
pub debug_feed: bool,
|
||||
/// Debug the latency by showing time in stream
|
||||
pub debug_latency: bool,
|
||||
|
||||
/// Virtual display resolution
|
||||
pub resolution: (u32, u32),
|
||||
/// Virtual display framerate
|
||||
pub framerate: u32,
|
||||
|
||||
/// Nestri relay url
|
||||
pub relay_url: String,
|
||||
/// Nestri room name/identifier
|
||||
pub room: String,
|
||||
}
|
||||
impl AppArgs {
|
||||
pub fn from_matches(matches: &clap::ArgMatches) -> Self {
|
||||
Self {
|
||||
verbose: matches.get_one::<String>("verbose").unwrap() == "true"
|
||||
|| matches.get_one::<String>("verbose").unwrap() == "1",
|
||||
debug_feed: matches.get_one::<String>("debug-feed").unwrap() == "true"
|
||||
|| matches.get_one::<String>("debug-feed").unwrap() == "1",
|
||||
debug_latency: matches.get_one::<String>("debug-latency").unwrap() == "true"
|
||||
|| matches.get_one::<String>("debug-latency").unwrap() == "1",
|
||||
resolution: {
|
||||
let res = matches.get_one::<String>("resolution").unwrap().clone();
|
||||
let parts: Vec<&str> = res.split('x').collect();
|
||||
(
|
||||
parts[0].parse::<u32>().unwrap(),
|
||||
parts[1].parse::<u32>().unwrap(),
|
||||
)
|
||||
},
|
||||
framerate: matches
|
||||
.get_one::<String>("framerate")
|
||||
.unwrap()
|
||||
.parse::<u32>()
|
||||
.unwrap(),
|
||||
relay_url: matches.get_one::<String>("relay-url").unwrap().clone(),
|
||||
// Generate random room name if not provided
|
||||
room: matches.get_one::<String>("room")
|
||||
.unwrap_or(&rand::random::<u32>().to_string())
|
||||
.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn debug_print(&self) {
|
||||
println!("AppArgs:");
|
||||
println!("> verbose: {}", self.verbose);
|
||||
println!("> debug_feed: {}", self.debug_feed);
|
||||
println!("> debug_latency: {}", self.debug_latency);
|
||||
println!("> resolution: {}x{}", self.resolution.0, self.resolution.1);
|
||||
println!("> framerate: {}", self.framerate);
|
||||
println!("> relay_url: {}", self.relay_url);
|
||||
println!("> room: {}", self.room);
|
||||
}
|
||||
}
|
||||
41
packages/server/src/args/device_args.rs
Normal file
41
packages/server/src/args/device_args.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
pub struct DeviceArgs {
|
||||
/// GPU vendor (e.g. "intel")
|
||||
pub gpu_vendor: String,
|
||||
/// GPU name (e.g. "a770")
|
||||
pub gpu_name: String,
|
||||
/// GPU index, if multiple same GPUs are present
|
||||
pub gpu_index: u32,
|
||||
/// GPU card/render path, sets card explicitly from such path
|
||||
pub gpu_card_path: String,
|
||||
}
|
||||
impl DeviceArgs {
|
||||
pub fn from_matches(matches: &clap::ArgMatches) -> Self {
|
||||
Self {
|
||||
gpu_vendor: matches
|
||||
.get_one::<String>("gpu-vendor")
|
||||
.unwrap_or(&"".to_string())
|
||||
.clone(),
|
||||
gpu_name: matches
|
||||
.get_one::<String>("gpu-name")
|
||||
.unwrap_or(&"".to_string())
|
||||
.clone(),
|
||||
gpu_index: matches
|
||||
.get_one::<String>("gpu-index")
|
||||
.unwrap()
|
||||
.parse::<u32>()
|
||||
.unwrap(),
|
||||
gpu_card_path: matches
|
||||
.get_one::<String>("gpu-card-path")
|
||||
.unwrap_or(&"".to_string())
|
||||
.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn debug_print(&self) {
|
||||
println!("DeviceArgs:");
|
||||
println!("> gpu_vendor: {}", self.gpu_vendor);
|
||||
println!("> gpu_name: {}", self.gpu_name);
|
||||
println!("> gpu_index: {}", self.gpu_index);
|
||||
println!("> gpu_card_path: {}", self.gpu_card_path);
|
||||
}
|
||||
}
|
||||
190
packages/server/src/args/encoding_args.rs
Normal file
190
packages/server/src/args/encoding_args.rs
Normal file
@@ -0,0 +1,190 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct RateControlCQP {
|
||||
/// Constant Quantization Parameter (CQP) quality level
|
||||
pub quality: u32,
|
||||
}
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct RateControlVBR {
|
||||
/// Target bitrate in kbps
|
||||
pub target_bitrate: i32,
|
||||
/// Maximum bitrate in kbps
|
||||
pub max_bitrate: i32,
|
||||
}
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct RateControlCBR {
|
||||
/// Target bitrate in kbps
|
||||
pub target_bitrate: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum RateControl {
|
||||
/// Constant Quantization Parameter
|
||||
CQP(RateControlCQP),
|
||||
/// Variable Bitrate
|
||||
VBR(RateControlVBR),
|
||||
/// Constant Bitrate
|
||||
CBR(RateControlCBR),
|
||||
}
|
||||
|
||||
pub struct EncodingOptionsBase {
|
||||
/// Codec (e.g. "h264", "opus" etc.)
|
||||
pub codec: String,
|
||||
/// Overridable encoder (e.g. "vah264lpenc", "opusenc" etc.)
|
||||
pub encoder: String,
|
||||
/// Rate control method (e.g. "cqp", "vbr", "cbr")
|
||||
pub rate_control: RateControl,
|
||||
}
|
||||
impl EncodingOptionsBase {
|
||||
pub fn debug_print(&self) {
|
||||
println!("> Codec: {}", self.codec);
|
||||
println!("> Encoder: {}", self.encoder);
|
||||
match &self.rate_control {
|
||||
RateControl::CQP(cqp) => {
|
||||
println!("> Rate Control: CQP");
|
||||
println!("-> Quality: {}", cqp.quality);
|
||||
}
|
||||
RateControl::VBR(vbr) => {
|
||||
println!("> Rate Control: VBR");
|
||||
println!("-> Target Bitrate: {}", vbr.target_bitrate);
|
||||
println!("-> Max Bitrate: {}", vbr.max_bitrate);
|
||||
}
|
||||
RateControl::CBR(cbr) => {
|
||||
println!("> Rate Control: CBR");
|
||||
println!("-> Target Bitrate: {}", cbr.target_bitrate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VideoEncodingOptions {
|
||||
pub base: EncodingOptionsBase,
|
||||
/// Encoder type (e.g. "hardware", "software")
|
||||
pub encoder_type: String,
|
||||
}
|
||||
impl VideoEncodingOptions {
|
||||
pub fn from_matches(matches: &clap::ArgMatches) -> Self {
|
||||
Self {
|
||||
base: EncodingOptionsBase {
|
||||
codec: matches.get_one::<String>("video-codec").unwrap().clone(),
|
||||
encoder: matches
|
||||
.get_one::<String>("video-encoder")
|
||||
.unwrap_or(&"".to_string())
|
||||
.clone(),
|
||||
rate_control: match matches.get_one::<String>("video-rate-control").unwrap().as_str() {
|
||||
"cqp" => RateControl::CQP(RateControlCQP {
|
||||
quality: matches.get_one::<String>("video-cqp").unwrap().parse::<u32>().unwrap(),
|
||||
}),
|
||||
"cbr" => RateControl::CBR(RateControlCBR {
|
||||
target_bitrate: matches.get_one::<String>("video-bitrate").unwrap().parse::<i32>().unwrap(),
|
||||
}),
|
||||
"vbr" => RateControl::VBR(RateControlVBR {
|
||||
target_bitrate: matches.get_one::<String>("video-bitrate").unwrap().parse::<i32>().unwrap(),
|
||||
max_bitrate: matches.get_one::<String>("video-bitrate-max").unwrap().parse::<i32>().unwrap(),
|
||||
}),
|
||||
_ => panic!("Invalid rate control method for video"),
|
||||
},
|
||||
},
|
||||
encoder_type: matches.get_one::<String>("video-encoder-type").unwrap_or(&"hardware".to_string()).clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn debug_print(&self) {
|
||||
println!("Video Encoding Options:");
|
||||
self.base.debug_print();
|
||||
println!("> Encoder Type: {}", self.encoder_type);
|
||||
}
|
||||
}
|
||||
impl Deref for VideoEncodingOptions {
|
||||
type Target = EncodingOptionsBase;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.base
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum AudioCaptureMethod {
|
||||
PulseAudio,
|
||||
PipeWire,
|
||||
ALSA,
|
||||
}
|
||||
impl AudioCaptureMethod {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
AudioCaptureMethod::PulseAudio => "pulseaudio",
|
||||
AudioCaptureMethod::PipeWire => "pipewire",
|
||||
AudioCaptureMethod::ALSA => "alsa",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AudioEncodingOptions {
|
||||
pub base: EncodingOptionsBase,
|
||||
pub capture_method: AudioCaptureMethod,
|
||||
}
|
||||
impl AudioEncodingOptions {
|
||||
pub fn from_matches(matches: &clap::ArgMatches) -> Self {
|
||||
Self {
|
||||
base: EncodingOptionsBase {
|
||||
codec: matches.get_one::<String>("audio-codec").unwrap().clone(),
|
||||
encoder: matches
|
||||
.get_one::<String>("audio-encoder")
|
||||
.unwrap_or(&"".to_string())
|
||||
.clone(),
|
||||
rate_control: match matches.get_one::<String>("audio-rate-control").unwrap().as_str() {
|
||||
"cbr" => RateControl::CBR(RateControlCBR {
|
||||
target_bitrate: matches.get_one::<String>("audio-bitrate").unwrap().parse::<i32>().unwrap(),
|
||||
}),
|
||||
"vbr" => RateControl::VBR(RateControlVBR {
|
||||
target_bitrate: matches.get_one::<String>("audio-bitrate").unwrap().parse::<i32>().unwrap(),
|
||||
max_bitrate: matches.get_one::<String>("audio-bitrate-max").unwrap().parse::<i32>().unwrap(),
|
||||
}),
|
||||
_ => panic!("Invalid rate control method for audio"),
|
||||
},
|
||||
},
|
||||
capture_method: match matches.get_one::<String>("audio-capture-method").unwrap().as_str() {
|
||||
"pulseaudio" => AudioCaptureMethod::PulseAudio,
|
||||
"pipewire" => AudioCaptureMethod::PipeWire,
|
||||
"alsa" => AudioCaptureMethod::ALSA,
|
||||
// Default to PulseAudio
|
||||
_ => AudioCaptureMethod::PulseAudio,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn debug_print(&self) {
|
||||
println!("Audio Encoding Options:");
|
||||
self.base.debug_print();
|
||||
println!("> Capture Method: {}", self.capture_method.as_str());
|
||||
}
|
||||
}
|
||||
impl Deref for AudioEncodingOptions {
|
||||
type Target = EncodingOptionsBase;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.base
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EncodingArgs {
|
||||
/// Video encoder options
|
||||
pub video: VideoEncodingOptions,
|
||||
/// Audio encoder options
|
||||
pub audio: AudioEncodingOptions,
|
||||
}
|
||||
impl EncodingArgs {
|
||||
pub fn from_matches(matches: &clap::ArgMatches) -> Self {
|
||||
Self {
|
||||
video: VideoEncodingOptions::from_matches(matches),
|
||||
audio: AudioEncodingOptions::from_matches(matches),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn debug_print(&self) {
|
||||
println!("Encoding Arguments:");
|
||||
self.video.debug_print();
|
||||
self.audio.debug_print();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user