mirror of
https://github.com/ivabus/lonelyradio
synced 2025-04-23 14:07:16 +03:00
116 lines
3 KiB
Rust
116 lines
3 KiB
Rust
use byteorder::ByteOrder;
|
|
use clap::Parser;
|
|
use rodio::buffer::SamplesBuffer;
|
|
use rodio::{OutputStream, Sink};
|
|
use std::io::Read;
|
|
use std::net::TcpStream;
|
|
use std::time::Instant;
|
|
|
|
// How many samples to cache before playing in samples (both channels) SHOULD BE EVEN
|
|
const BUFFER_SIZE: usize = 2400;
|
|
// How many buffers to cache
|
|
const CACHE_SIZE: usize = 100;
|
|
|
|
enum Channel {
|
|
Right,
|
|
Left,
|
|
Stereo,
|
|
}
|
|
|
|
#[derive(Parser)]
|
|
struct Args {
|
|
/// Remote address
|
|
address: String,
|
|
#[arg(short, long, default_value = "s")]
|
|
/// L, R or S for Left, Right or Stereo
|
|
channel: String,
|
|
#[arg(short)]
|
|
/// Play only on specified channel, with it if channel = Right => L=0 and R=R, without L=R and R=R. No effect on Stereo
|
|
single: bool,
|
|
|
|
/// More verbose
|
|
#[arg(short)]
|
|
verbose: bool,
|
|
}
|
|
|
|
fn main() {
|
|
let start = Instant::now();
|
|
let args = Args::parse();
|
|
let mut stream = TcpStream::connect(args.address).unwrap();
|
|
if args.verbose {
|
|
eprintln!(
|
|
"Connected to {} from {}",
|
|
stream.peer_addr().unwrap(),
|
|
stream.local_addr().unwrap()
|
|
)
|
|
}
|
|
|
|
let channel = match args.channel.to_ascii_lowercase().as_str() {
|
|
"l" => Channel::Left,
|
|
"r" => Channel::Right,
|
|
"s" => Channel::Stereo,
|
|
_ => panic!("Wrong channel specified"),
|
|
};
|
|
let (_stream, stream_handle) = OutputStream::try_default().unwrap();
|
|
let sink = Sink::try_new(&stream_handle).unwrap();
|
|
let mut buffer = [0u8; 4];
|
|
let mut samples = [0f32; BUFFER_SIZE];
|
|
let mut index = 0usize;
|
|
let mut first = true;
|
|
while stream.read_exact(&mut buffer).is_ok() {
|
|
let sample_l = byteorder::LittleEndian::read_i16(&buffer[..2]) as f32 / 32768.0;
|
|
let sample_r = byteorder::LittleEndian::read_i16(&buffer[2..]) as f32 / 32768.0;
|
|
// Left channel
|
|
samples[index] = match channel {
|
|
Channel::Left | Channel::Stereo => sample_l,
|
|
Channel::Right => {
|
|
if args.single {
|
|
0f32
|
|
} else {
|
|
sample_r
|
|
}
|
|
}
|
|
};
|
|
index += 1;
|
|
// Right channel
|
|
samples[index] = match channel {
|
|
Channel::Right | Channel::Stereo => sample_r,
|
|
Channel::Left => {
|
|
if args.single {
|
|
0f32
|
|
} else {
|
|
sample_l
|
|
}
|
|
}
|
|
};
|
|
index += 1;
|
|
if index == BUFFER_SIZE {
|
|
let mut first_wait_iteration = true;
|
|
// Sink's thread is detached from main thread, so we need to synchronize with it
|
|
// Why we should synchronize with it?
|
|
// Let's say, that if we don't synchronize with it, we would have
|
|
// a lot (no upper limit, actualy) of buffered sound, waiting for playing in sink
|
|
while sink.len() >= CACHE_SIZE {
|
|
if args.verbose && first_wait_iteration {
|
|
eprint!(".");
|
|
first_wait_iteration = false;
|
|
}
|
|
// Sleeping exactly one buffer
|
|
std::thread::sleep(std::time::Duration::from_secs_f32(
|
|
(if sink.len() >= 2 {
|
|
sink.len() - 2
|
|
} else {
|
|
1
|
|
} as f32) * BUFFER_SIZE as f32
|
|
/ 44100.0 / 2.0,
|
|
))
|
|
}
|
|
if first && args.verbose {
|
|
eprintln!("Started playing in {} ms", (Instant::now() - start).as_millis());
|
|
first = false;
|
|
}
|
|
sink.append(SamplesBuffer::new(2, 44100, samples.as_slice()));
|
|
index = 0;
|
|
}
|
|
}
|
|
}
|