v0.2: Metadata support, samplerate removal

Signed-off-by: Ivan Bushchik <ivabus@ivabus.dev>
This commit is contained in:
Ivan Bushchik 2024-03-14 20:50:01 +03:00
parent c6b9bdcf54
commit 6e3327aa1f
No known key found for this signature in database
GPG key ID: 2F16FBF3262E090C
11 changed files with 238 additions and 211 deletions

32
Cargo.lock generated
View file

@ -289,15 +289,6 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
[[package]]
name = "cmake"
version = "0.1.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "colorchoice" name = "colorchoice"
version = "1.0.0" version = "1.0.0"
@ -572,15 +563,6 @@ dependencies = [
"windows-targets 0.52.4", "windows-targets 0.52.4",
] ]
[[package]]
name = "libsamplerate-sys"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28853b399f78f8281cd88d333b54a63170c4275f6faea66726a2bea5cca72e0d"
dependencies = [
"cmake",
]
[[package]] [[package]]
name = "lofty" name = "lofty"
version = "0.18.2" version = "0.18.2"
@ -622,7 +604,6 @@ dependencies = [
"lofty", "lofty",
"rand", "rand",
"rmp-serde", "rmp-serde",
"samplerate",
"serde", "serde",
"symphonia", "symphonia",
"tokio", "tokio",
@ -683,10 +664,12 @@ dependencies = [
[[package]] [[package]]
name = "monolib" name = "monolib"
version = "0.1.0" version = "0.2.0"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"rmp-serde",
"rodio", "rodio",
"serde",
] ]
[[package]] [[package]]
@ -1030,15 +1013,6 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "samplerate"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e032b2b24715c4f982f483ea3abdb3c9ba444d9f63e87b2843d6f998f5ba2698"
dependencies = [
"libsamplerate-sys",
]
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.197" version = "1.0.197"

View file

@ -27,7 +27,7 @@ symphonia = { version = "0.5.4", features = [
"all-formats", "all-formats",
"opt-simd", "opt-simd",
] } ] }
samplerate = "0.2.4" #samplerate = "0.2.4"
chrono = "0.4" chrono = "0.4"
rmp-serde = "1.1.2" rmp-serde = "1.1.2"
serde = { version = "1.0.197", features = ["derive"] } serde = { version = "1.0.197", features = ["derive"] }

View file

@ -2,7 +2,7 @@
> TCP radio for singles > TCP radio for singles
Radio that uses unencrypted TCP socket for broadcasting raw PCM (16/44.1/LE) stream Radio that uses unencrypted TCP socket for broadcasting tagged audio data.
Decodes audio streams using [symphonia](https://github.com/pdeljanov/Symphonia). Decodes audio streams using [symphonia](https://github.com/pdeljanov/Symphonia).
@ -22,7 +22,7 @@ All files (recursively) will be shuffled and played back. Public log will be dis
### Clients ### Clients
[monoclient](./monoclient) with optional channel separation, hardcoded input (16/44.1/LE). [monoclient](./monoclient) is a recommended client for lonelyradio
```shell ```shell
monoclient <SERVER>:<PORT> monoclient <SERVER>:<PORT>
@ -30,7 +30,7 @@ monoclient <SERVER>:<PORT>
### Other clients ### Other clients
SwiftUI client is availible in [platform](./platform) directory (not yet adapted for lonelyradio 0.2). SwiftUI client is availible in [platform](./platform) directory.
## License ## License

View file

@ -3,24 +3,19 @@ use clap::Parser;
use rodio::buffer::SamplesBuffer; use rodio::buffer::SamplesBuffer;
use rodio::{OutputStream, Sink}; use rodio::{OutputStream, Sink};
use serde::Deserialize; use serde::Deserialize;
use std::io::{Read, Write}; use std::io::{IsTerminal, Read, Write};
use std::net::TcpStream; use std::net::TcpStream;
// How many samples to cache before playing in samples (both channels) SHOULD BE EVEN // How many samples to cache before playing in samples (both channels) SHOULD BE EVEN
const BUFFER_SIZE: usize = 2400; const BUFFER_SIZE: usize = 4800;
// How many buffers to cache // How many buffers to cache
const CACHE_SIZE: usize = 100; const CACHE_SIZE: usize = 10;
enum Channel {
Right,
Left,
Stereo,
}
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
struct SentMetadata { struct SentMetadata {
// In bytes, we need to read next track metadata // In bytes, we need to read next track metadata
lenght: u64, lenght: u64,
sample_rate: u32,
title: String, title: String,
album: String, album: String,
artist: String, artist: String,
@ -30,91 +25,75 @@ struct SentMetadata {
struct Args { struct Args {
/// Remote address /// Remote address
address: String, 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,
/// Do not erase previously played track from stdout /// Do not use backspace control char
#[arg(short)] #[arg(short)]
no_backspace: bool, no_backspace: bool,
} }
fn main() { fn delete_chars(n: usize) {
let args = Args::parse(); print!("{}{}{}", "\u{8}".repeat(n), " ".repeat(n), "\u{8}".repeat(n));
let mut stream = TcpStream::connect(args.address).unwrap(); std::io::stdout().flush().expect("Failed to flush stdout")
println!("Connected to {} from {}", stream.peer_addr().unwrap(), stream.local_addr().unwrap()); }
let channel = match args.channel.to_ascii_lowercase().as_str() { fn main() {
"l" => Channel::Left, let mut args = Args::parse();
"r" => Channel::Right, args.no_backspace |= !std::io::stdout().is_terminal();
"s" => Channel::Stereo, let mut stream = TcpStream::connect(&args.address)
_ => panic!("Wrong channel specified"), .unwrap_or_else(|err| panic!("Failed to connect to {}: {}", args.address, err.to_string()));
}; println!("Connected to {} from {}", stream.peer_addr().unwrap(), stream.local_addr().unwrap());
let (_stream, stream_handle) = OutputStream::try_default().unwrap(); let (_stream, stream_handle) = OutputStream::try_default().unwrap();
let sink = Sink::try_new(&stream_handle).unwrap(); let sink = Sink::try_new(&stream_handle).unwrap();
let mut buffer = [0u8; 4]; let mut buffer = [0u8; 2];
let mut samples = [0f32; BUFFER_SIZE]; let mut samples = [0f32; BUFFER_SIZE];
let mut latest_msg_len = 0; let mut latest_msg_len = 0;
print!("Playing: "); print!("Playing: ");
loop { loop {
let mut index = 0usize; let mut index = 0usize;
let md: SentMetadata = rmp_serde::from_read(&stream).unwrap(); let md: SentMetadata =
let seconds = md.lenght / (2 * 44100); rmp_serde::from_read(&stream).expect("Failed to parse track metadata");
let message = format!( let seconds = md.lenght / (2 * md.sample_rate as u64);
"{} - {} - {} ({}:{:02})", let total_lenght = format!("{}:{:02}", seconds / 60, seconds % 60);
md.artist, let message = format!("{} - {} - {} ", md.artist, md.album, md.title);
md.album,
md.title,
seconds / 60,
seconds % 60
);
if latest_msg_len != 0 { if latest_msg_len != 0 {
if args.no_backspace { if args.no_backspace {
print!("\nPlaying: "); print!("\nPlaying: ");
} else { } else {
print!("{}", "\u{8}".repeat(latest_msg_len)); delete_chars(latest_msg_len)
print!("{}", " ".repeat(latest_msg_len));
print!("{}", "\u{8}".repeat(latest_msg_len));
} }
} }
print!("{}", message); print!("{}", message);
std::io::stdout().flush().unwrap(); let mut prev_timestamp_len = 0;
if args.no_backspace {
print!("({})", &total_lenght)
} else {
print!("(0:00 / {})", &total_lenght);
// (0:00/ + :00 + minutes len
prev_timestamp_len = 12 + format!("{}", seconds / 60).len();
}
std::io::stdout().flush().expect("Failed to flush stdout");
latest_msg_len = message.chars().count(); latest_msg_len = message.chars().count();
let mut second = 0;
for _ in 0..md.lenght / 2 { for sample_index in 0..md.lenght {
if (sample_index / (md.sample_rate as u64 * 2)) > second {
second += 1;
if !args.no_backspace {
delete_chars(prev_timestamp_len);
let current_timestamp =
format!("({}:{:02} / {})", second / 60, second % 60, &total_lenght);
print!("{}", &current_timestamp);
std::io::stdout().flush().expect("Failed to flush stdout");
prev_timestamp_len = current_timestamp.len()
}
}
if stream.read_exact(&mut buffer).is_err() { if stream.read_exact(&mut buffer).is_err() {
return; return;
}; };
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; samples[index] = 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; index += 1;
if index == BUFFER_SIZE { if index == BUFFER_SIZE {
// Sink's thread is detached from main thread, so we need to synchronize with it // Sink's thread is detached from main thread, so we need to synchronize with it
// Why we should synchronize with it? // Why we should synchronize with it?
@ -123,20 +102,13 @@ fn main() {
while sink.len() >= CACHE_SIZE { while sink.len() >= CACHE_SIZE {
// Sleeping exactly one buffer // Sleeping exactly one buffer
std::thread::sleep(std::time::Duration::from_secs_f32( std::thread::sleep(std::time::Duration::from_secs_f32(
(if sink.len() >= 2 { BUFFER_SIZE as f32 / md.sample_rate as f32 / 2.0,
sink.len() - 2
} else {
1
} as f32) * BUFFER_SIZE as f32
/ 44100.0 / 2.0,
)) ))
} }
sink.append(SamplesBuffer::new(2, 44100, samples.as_slice())); sink.append(SamplesBuffer::new(2, md.sample_rate, samples.as_slice()));
index = 0; index = 0;
} }
} }
while sink.len() != 0 { sink.sleep_until_end()
std::thread::sleep(std::time::Duration::from_secs_f32(0.01))
}
} }
} }

View file

@ -20,4 +20,4 @@ cargo lipo --release --targets aarch64-apple-ios-sim,x86_64-apple-ios
Open Xcode and run. Open Xcode and run.
[Screenshots](./screenshots/swiftui) [Screenshots (pre v0.2)](./screenshots/swiftui)

View file

@ -8,7 +8,6 @@
#ifndef MonoLib_Bridging_Header_h #ifndef MonoLib_Bridging_Header_h
#define MonoLib_Bridging_Header_h #define MonoLib_Bridging_Header_h
#import "monolib.h" #import "monolib.h"
#endif /* MonoLib_Bridging_Header_h */ #endif /* MonoLib_Bridging_Header_h */

View file

@ -25,14 +25,19 @@ class MonoLib {
} }
struct ContentView: View { struct ContentView: View {
let timer = Timer.publish(every: 0.25, on: .main, in: .common).autoconnect()
@State private var server: String = "" @State private var server: String = ""
@State private var port: String = "" @State private var port: String = ""
@State private var playing: Bool = true @State private var playing: Bool = true
@State private var running: Bool = false @State private var running: Bool = false
@State var now_playing_artist: String = ""
@State var now_playing_album: String = ""
@State var now_playing_title: String = ""
var body: some View { var body: some View {
VStack { VStack {
Text("Monoclient").font(.largeTitle).fontWidth(.expanded).bold() //.padding(.top, 25) Text("Monoclient").font(.largeTitle).fontWidth(.expanded).bold()
VStack(alignment: .center) { VStack(alignment: .center) {
HStack { HStack {
Text("Server").frame(minWidth: 50, idealWidth: 60) Text("Server").frame(minWidth: 50, idealWidth: 60)
@ -79,6 +84,15 @@ struct ContentView: View {
.bordered .bordered
).disabled(!running) ).disabled(!running)
}.frame(width: 300) }.frame(width: 300)
Text(now_playing_artist).font(.title2).onReceive(timer) { _ in
now_playing_artist = String(cString: get_metadata_artist()!)
}
Text(now_playing_album).onReceive(timer) { _ in
now_playing_album = String(cString: get_metadata_album()!)
}
Text(now_playing_title).font(.title).bold().onReceive(timer) { _ in
now_playing_title = String(cString: get_metadata_title()!)
}
}.padding() }.padding()

View file

@ -1,12 +1,15 @@
[package] [package]
name = "monolib" name = "monolib"
version = "0.1.0" version = "0.2.0"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
[dependencies] [dependencies]
rodio = { version = "0.17.3", default-features = false } rodio = { version = "0.17.3", default-features = false }
byteorder = "1.5.0" byteorder = "1.5.0"
rmp-serde = "1.1.2"
serde = { version = "1.0.197", features = ["derive"] }
[lib] [lib]
name = "monolib" name = "monolib"

View file

@ -1,10 +1,12 @@
use byteorder::ByteOrder; use byteorder::ByteOrder;
use rodio::buffer::SamplesBuffer; use rodio::buffer::SamplesBuffer;
use rodio::{OutputStream, Sink}; use rodio::{OutputStream, Sink};
use std::ffi::CStr; use serde::Deserialize;
use std::ffi::{CStr, CString};
use std::io::Read; use std::io::Read;
use std::net::TcpStream; use std::net::TcpStream;
use std::os::raw::c_char; use std::os::raw::c_char;
use std::time::Instant;
// How many samples to cache before playing in samples (both channels) SHOULD BE EVEN // How many samples to cache before playing in samples (both channels) SHOULD BE EVEN
const BUFFER_SIZE: usize = 2400; const BUFFER_SIZE: usize = 2400;
@ -12,9 +14,16 @@ const BUFFER_SIZE: usize = 2400;
const CACHE_SIZE: usize = 40; const CACHE_SIZE: usize = 40;
static mut SINK: Option<Box<Sink>> = None; static mut SINK: Option<Box<Sink>> = None;
static mut RUNNING: bool = false; static mut MD: Option<SentMetadata> = None;
static mut STOPPED: bool = false; static mut STATE: State = State::NotStarted;
static mut RESET: bool = false;
#[derive(PartialEq)]
enum State {
NotStarted,
Resetting,
Playing,
Paused,
}
#[no_mangle] #[no_mangle]
pub extern "C" fn start(server: *const c_char) { pub extern "C" fn start(server: *const c_char) {
@ -30,13 +39,13 @@ pub extern "C" fn start(server: *const c_char) {
#[no_mangle] #[no_mangle]
pub extern "C" fn toggle() { pub extern "C" fn toggle() {
unsafe { unsafe {
if !STOPPED { if STATE == State::Playing {
STOPPED = true; STATE = State::Paused;
if let Some(sink) = &SINK { if let Some(sink) = &SINK {
sink.pause(); sink.pause();
} }
} else { } else if STATE == State::Paused {
STOPPED = false; STATE = State::Playing;
if let Some(sink) = &SINK { if let Some(sink) = &SINK {
sink.play(); sink.play();
} }
@ -47,19 +56,84 @@ pub extern "C" fn toggle() {
#[no_mangle] #[no_mangle]
pub extern "C" fn reset() { pub extern "C" fn reset() {
unsafe { unsafe {
RESET = true; STATE = State::Resetting;
if let Some(sink) = &SINK {
sink.pause();
}
// Blocking main thread // Blocking main thread
while RESET { while STATE == State::Resetting {
std::thread::sleep(std::time::Duration::from_secs_f32(0.02)) std::thread::sleep(std::time::Duration::from_secs_f32(0.01))
} }
} }
} }
#[no_mangle]
pub extern "C" fn get_metadata_artist() -> *mut c_char {
unsafe {
match &MD {
Some(md) => CString::new(md.artist.clone()).unwrap().into_raw(),
_ => CString::new("").unwrap().into_raw(),
}
}
}
#[no_mangle]
pub extern "C" fn get_metadata_album() -> *mut c_char {
unsafe {
match &MD {
Some(md) => CString::new(md.album.clone()).unwrap().into_raw(),
_ => CString::new("").unwrap().into_raw(),
}
}
}
#[no_mangle]
pub extern "C" fn get_metadata_title() -> *mut c_char {
unsafe {
match &MD {
Some(md) => CString::new(md.title.clone()).unwrap().into_raw(),
_ => CString::new("").unwrap().into_raw(),
}
}
}
unsafe fn _reset() {
if let Some(sink) = &SINK {
sink.pause();
sink.clear();
}
SINK = None;
MD = None;
STATE = State::NotStarted;
}
// Reset - true, not - false
unsafe fn watching_sleep(dur: f32) -> bool {
let start = Instant::now();
while Instant::now() < start + std::time::Duration::from_secs_f32(dur) {
std::thread::sleep(std::time::Duration::from_secs_f32(0.0001));
if STATE == State::Resetting {
return true;
}
}
false
}
#[derive(Deserialize, Clone, Debug)]
struct SentMetadata {
// In bytes, we need to read next track metadata
lenght: u64,
sample_rate: u32,
title: String,
album: String,
artist: String,
}
unsafe fn run(server: &str) { unsafe fn run(server: &str) {
if RUNNING { if STATE == State::Playing || STATE == State::Paused {
return; return;
} }
RUNNING = true; STATE = State::Playing;
let mut stream = TcpStream::connect(server).unwrap(); let mut stream = TcpStream::connect(server).unwrap();
println!("Connected to {} from {}", stream.peer_addr().unwrap(), stream.local_addr().unwrap()); println!("Connected to {} from {}", stream.peer_addr().unwrap(), stream.local_addr().unwrap());
let (_stream, stream_handle) = OutputStream::try_default().unwrap(); let (_stream, stream_handle) = OutputStream::try_default().unwrap();
@ -74,33 +148,41 @@ unsafe fn run(server: &str) {
} }
} }
} }
let mut buffer = [0u8; 4]; match &SINK {
None => {
let sink = Sink::try_new(&stream_handle).unwrap();
SINK = Some(Box::new(sink));
}
Some(s) => {
if s.is_paused() {
s.play()
}
}
}
let mut buffer = [0u8; 2];
let mut samples = [0f32; BUFFER_SIZE]; let mut samples = [0f32; BUFFER_SIZE];
loop {
let mut index = 0usize; let mut index = 0usize;
while stream.read_exact(&mut buffer).is_ok() {
while STOPPED {
std::thread::sleep(std::time::Duration::from_secs_f32(0.5))
}
if RESET {
RUNNING = false;
STOPPED = false;
if let Some(sink) = &SINK { let md: SentMetadata =
sink.pause(); rmp_serde::from_read(&stream).expect("Failed to parse track metadata");
sink.clear(); MD = Some(md.clone());
for _ in 0..md.lenght {
while STATE == State::Paused {
std::thread::sleep(std::time::Duration::from_secs_f32(0.25))
} }
SINK = None; if STATE == State::Resetting {
RESET = false; _reset();
return; return;
} }
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; if stream.read_exact(&mut buffer).is_err() {
// Left channel return;
samples[index] = sample_l; };
index += 1;
// Right channel samples[index] = byteorder::LittleEndian::read_i16(&buffer[..2]) as f32 / 32768.0;
samples[index] = sample_r;
index += 1; index += 1;
if index == BUFFER_SIZE { if index == BUFFER_SIZE {
// Sink's thread is detached from main thread, so we need to synchronize with it // Sink's thread is detached from main thread, so we need to synchronize with it
// Why we should synchronize with it? // Why we should synchronize with it?
@ -108,19 +190,23 @@ unsafe fn run(server: &str) {
// a lot (no upper limit, actualy) of buffered sound, waiting for playing in sink // a lot (no upper limit, actualy) of buffered sound, waiting for playing in sink
if let Some(sink) = &SINK { if let Some(sink) = &SINK {
while sink.len() >= CACHE_SIZE { while sink.len() >= CACHE_SIZE {
// Sleeping exactly one buffer // Sleeping exactly one buffer and watching for reset signal
std::thread::sleep(std::time::Duration::from_secs_f32( if watching_sleep(
(if sink.len() >= 2 { if sink.len() > 2 {
sink.len() - 2 sink.len() as f32 - 2.0
} else { } else {
1 0.5
} as f32) * BUFFER_SIZE as f32 } * BUFFER_SIZE as f32 / md.sample_rate as f32
/ 44100.0 / 2.0, / 2.0,
)) ) {
_reset();
return;
} }
sink.append(SamplesBuffer::new(2, 44100, samples.as_slice())); }
sink.append(SamplesBuffer::new(2, md.sample_rate, samples.as_slice()));
index = 0; index = 0;
} }
} }
} }
} }
}

View file

@ -5,3 +5,7 @@ void start(const char *server);
void toggle(); void toggle();
void reset(); void reset();
const char *get_metadata_artist();
const char *get_metadata_album();
const char *get_metadata_title();

View file

@ -7,12 +7,10 @@ use clap::Parser;
use lofty::Accessor; use lofty::Accessor;
use lofty::TaggedFileExt; use lofty::TaggedFileExt;
use rand::prelude::*; use rand::prelude::*;
use samplerate::ConverterType;
use serde::Serialize; use serde::Serialize;
use symphonia::core::audio::SampleBuffer; use symphonia::core::audio::SampleBuffer;
use symphonia::core::codecs::CODEC_TYPE_NULL; use symphonia::core::codecs::CODEC_TYPE_NULL;
use symphonia::core::io::MediaSourceStream; use symphonia::core::io::MediaSourceStream;
use symphonia::core::meta::StandardTagKey;
use symphonia::core::probe::Hint; use symphonia::core::probe::Hint;
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
use tokio::net::{TcpListener, TcpStream}; use tokio::net::{TcpListener, TcpStream};
@ -35,6 +33,8 @@ struct Args {
struct SentMetadata { struct SentMetadata {
// In bytes, we need to read next track metadata // In bytes, we need to read next track metadata
lenght: u64, lenght: u64,
// Yep, no more interpolation
sample_rate: u32,
title: String, title: String,
album: String, album: String,
artist: String, artist: String,
@ -42,23 +42,15 @@ struct SentMetadata {
async fn stream_samples( async fn stream_samples(
track_samples: Vec<f32>, track_samples: Vec<f32>,
rate: u32,
war: bool, war: bool,
md: SentMetadata, md: SentMetadata,
s: &mut TcpStream, s: &mut TcpStream,
) -> bool { ) -> bool {
let resampled = if rate != 44100 {
samplerate::convert(rate, 44100, 2, ConverterType::Linear, track_samples.as_slice())
.unwrap()
} else {
track_samples
};
let mut md = md;
md.lenght = resampled.len() as u64;
if s.write_all(rmp_serde::to_vec(&md).unwrap().as_slice()).await.is_err() { if s.write_all(rmp_serde::to_vec(&md).unwrap().as_slice()).await.is_err() {
return true; return true;
} }
for sample in resampled {
for sample in track_samples {
if s.write_all( if s.write_all(
&(if war { &(if war {
sample.signum() as i16 * 32767 sample.signum() as i16 * 32767
@ -121,7 +113,8 @@ async fn stream(mut s: TcpStream, tracklist: Arc<Vec<PathBuf>>) {
let mut file = std::fs::File::open(track).unwrap(); let mut file = std::fs::File::open(track).unwrap();
let tagged = lofty::read_from(&mut file).unwrap(); let tagged = lofty::read_from(&mut file).unwrap();
if let Some(id3v2) = tagged.primary_tag() { if let Some(id3v2) = tagged.primary_tag() {
title = id3v2.title().unwrap_or("[No tag]".into()).to_string(); title =
id3v2.title().unwrap_or(track.file_stem().unwrap().to_string_lossy()).to_string();
album = id3v2.album().unwrap_or("[No tag]".into()).to_string(); album = id3v2.album().unwrap_or("[No tag]".into()).to_string();
artist = id3v2.artist().unwrap_or("[No tag]".into()).to_string() artist = id3v2.artist().unwrap_or("[No tag]".into()).to_string()
}; };
@ -179,7 +172,7 @@ async fn stream(mut s: TcpStream, tracklist: Arc<Vec<PathBuf>>) {
.expect("unsupported codec"); .expect("unsupported codec");
let track_id = track.id; let track_id = track.id;
let mut track_rate = 0; let mut sample_rate = 0;
let mut samples = vec![]; let mut samples = vec![];
loop { loop {
let packet = match format.next_packet() { let packet = match format.next_packet() {
@ -188,18 +181,6 @@ async fn stream(mut s: TcpStream, tracklist: Arc<Vec<PathBuf>>) {
}; };
while !format.metadata().is_latest() { while !format.metadata().is_latest() {
format.metadata().pop(); format.metadata().pop();
if let Some(rev) = format.metadata().current() {
for tag in rev.tags() {
println!("Looped");
match tag.std_key {
Some(StandardTagKey::Album) => album = tag.value.to_string(),
Some(StandardTagKey::Artist) => artist = tag.value.to_string(),
Some(StandardTagKey::TrackTitle) => title = tag.value.to_string(),
_ => {}
}
eprintln!("DBG: {} {} {}", &album, &artist, &title)
}
}
} }
if packet.track_id() != track_id { if packet.track_id() != track_id {
@ -208,7 +189,7 @@ async fn stream(mut s: TcpStream, tracklist: Arc<Vec<PathBuf>>) {
match decoder.decode(&packet) { match decoder.decode(&packet) {
Ok(decoded) => { Ok(decoded) => {
track_rate = decoded.spec().rate; sample_rate = decoded.spec().rate;
let mut byte_buf = let mut byte_buf =
SampleBuffer::<f32>::new(decoded.capacity() as u64, *decoded.spec()); SampleBuffer::<f32>::new(decoded.capacity() as u64, *decoded.spec());
byte_buf.copy_interleaved_ref(decoded); byte_buf.copy_interleaved_ref(decoded);
@ -221,21 +202,15 @@ async fn stream(mut s: TcpStream, tracklist: Arc<Vec<PathBuf>>) {
} }
} }
} }
if !samples.is_empty() { let md = SentMetadata {
if stream_samples( lenght: samples.len() as u64,
samples, sample_rate,
track_rate,
args.war,
SentMetadata {
lenght: 0,
title, title,
album, album,
artist, artist,
}, };
&mut s, if !samples.is_empty() {
) if stream_samples(samples, args.war, md, &mut s).await {
.await
{
break; break;
} }
} }