v0.2.1: Refactor monoclient into monoclient and monolib

Signed-off-by: Ivan Bushchik <ivabus@ivabus.dev>
This commit is contained in:
Ivan Bushchik 2024-03-15 18:31:06 +03:00
parent 6e3327aa1f
commit 9165c43dae
No known key found for this signature in database
GPG key ID: 2F16FBF3262E090C
15 changed files with 428 additions and 367 deletions

11
Cargo.lock generated
View file

@ -597,7 +597,7 @@ checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "lonelyradio"
version = "0.2.0"
version = "0.2.1"
dependencies = [
"chrono",
"clap",
@ -653,18 +653,15 @@ dependencies = [
[[package]]
name = "monoclient"
version = "0.2.0"
version = "0.2.1"
dependencies = [
"byteorder",
"clap",
"rmp-serde",
"rodio",
"serde",
"monolib",
]
[[package]]
name = "monolib"
version = "0.2.0"
version = "0.2.1"
dependencies = [
"byteorder",
"rmp-serde",

View file

@ -1,10 +1,10 @@
[workspace]
members = ["monoclient", "platform/swiftui/monolib"]
members = [ "monoclient", "monolib"]
[package]
name = "lonelyradio"
description = "TCP radio for lonely ones"
version = "0.2.0"
version = "0.2.1"
edition = "2021"
license = "MIT"
authors = ["Ivan Bushchik <ivabus@ivabus.dev>"]

View file

@ -22,7 +22,7 @@ All files (recursively) will be shuffled and played back. Public log will be dis
### Clients
[monoclient](./monoclient) is a recommended client for lonelyradio
[monoclient](./monoclient) is a recommended CLI client for lonelyradio that uses [monolib](./monolib)
```shell
monoclient <SERVER>:<PORT>
@ -32,6 +32,8 @@ monoclient <SERVER>:<PORT>
SwiftUI client is availible in [platform](./platform) directory.
[monolib](./monolib) provides lonelyradio-compatible C API for creating custom clients.
## License
lonelyradio and monoclient are licensed under the terms of the [MIT license](./LICENSE).

View file

@ -1,12 +1,9 @@
[package]
name = "monoclient"
version = "0.2.0"
version = "0.2.1"
edition = "2021"
license = "MIT"
[dependencies]
clap = { version = "4.4.18", features = ["derive"] }
rodio = { version = "0.17.3", default-features = false }
byteorder = "1.5.0"
rmp-serde = "1.1.2"
serde = { version = "1.0.197", features = ["derive"] }
monolib = { path="../monolib" }
clap = { version = "4.4.18", features = ["derive"] }

View file

@ -1,25 +1,6 @@
use byteorder::ByteOrder;
use clap::Parser;
use rodio::buffer::SamplesBuffer;
use rodio::{OutputStream, Sink};
use serde::Deserialize;
use std::io::{IsTerminal, Read, Write};
use std::net::TcpStream;
// How many samples to cache before playing in samples (both channels) SHOULD BE EVEN
const BUFFER_SIZE: usize = 4800;
// How many buffers to cache
const CACHE_SIZE: usize = 10;
#[derive(Deserialize, Debug)]
struct SentMetadata {
// In bytes, we need to read next track metadata
lenght: u64,
sample_rate: u32,
title: String,
album: String,
artist: String,
}
use std::io::{IsTerminal, Write};
use std::time::{Duration, Instant};
#[derive(Parser)]
struct Args {
@ -31,84 +12,98 @@ struct Args {
no_backspace: bool,
}
fn delete_chars(n: usize) {
print!("{}{}{}", "\u{8}".repeat(n), " ".repeat(n), "\u{8}".repeat(n));
std::io::stdout().flush().expect("Failed to flush stdout")
fn delete_chars(n: usize, nb: bool) {
if !nb {
print!("{}{}{}", "\u{8}".repeat(n), " ".repeat(n), "\u{8}".repeat(n));
std::io::stdout().flush().expect("Failed to flush stdout")
} else {
println!()
}
}
fn flush() {
std::io::stdout().flush().unwrap();
}
fn main() {
let mut args = Args::parse();
args.no_backspace |= !std::io::stdout().is_terminal();
let mut stream = TcpStream::connect(&args.address)
.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 sink = Sink::try_new(&stream_handle).unwrap();
let mut buffer = [0u8; 2];
let mut samples = [0f32; BUFFER_SIZE];
let mut latest_msg_len = 0;
print!("Playing: ");
std::thread::spawn(move || monolib::run(&args.address));
while monolib::get_metadata().is_none() {}
let mut md = monolib::get_metadata().unwrap();
let seconds = md.length / md.sample_rate as u64 / 2;
let mut track_start = Instant::now();
let mut seconds_past = 0;
let mut msg_len = format!(
"Playing: {} - {} - {} ({}:{:02})",
md.artist,
md.album,
md.title,
seconds / 60,
seconds % 60
)
.len();
print!(
"Playing: {} - {} - {} ({}:{:02})",
md.artist,
md.album,
md.title,
seconds / 60,
seconds % 60
);
flush();
loop {
let mut index = 0usize;
let md: SentMetadata =
rmp_serde::from_read(&stream).expect("Failed to parse track metadata");
let seconds = md.lenght / (2 * md.sample_rate as u64);
let total_lenght = format!("{}:{:02}", seconds / 60, seconds % 60);
let message = format!("{} - {} - {} ", md.artist, md.album, md.title);
if latest_msg_len != 0 {
if args.no_backspace {
print!("\nPlaying: ");
} else {
delete_chars(latest_msg_len)
}
if monolib::get_metadata().unwrap() != md {
md = monolib::get_metadata().unwrap();
let seconds = md.length / md.sample_rate as u64 / 2;
delete_chars(msg_len, args.no_backspace);
msg_len = format!(
"Playing: {} - {} - {} ({}:{:02})",
md.artist,
md.album,
md.title,
seconds / 60,
seconds % 60
)
.len();
print!(
"Playing: {} - {} - {} (0:00 / {}:{:02})",
md.artist,
md.album,
md.title,
seconds / 60,
seconds % 60
);
flush();
track_start = Instant::now();
seconds_past = 0;
}
print!("{}", message);
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();
if (Instant::now() - track_start).as_secs() > seconds_past && !args.no_backspace {
seconds_past = (Instant::now() - track_start).as_secs();
msg_len = format!(
"Playing: {} - {} - {} ({}:{:02} / {}:{:02})",
md.artist,
md.album,
md.title,
seconds_past / 60,
seconds_past % 60,
seconds / 60,
seconds % 60
)
.len();
delete_chars(msg_len, args.no_backspace);
print!(
"Playing: {} - {} - {} ({}:{:02} / {}:{:02})",
md.artist,
md.album,
md.title,
seconds_past / 60,
seconds_past % 60,
seconds / 60,
seconds % 60
);
flush();
}
std::io::stdout().flush().expect("Failed to flush stdout");
latest_msg_len = message.chars().count();
let mut second = 0;
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() {
return;
};
samples[index] = byteorder::LittleEndian::read_i16(&buffer[..2]) as f32 / 32768.0;
index += 1;
if index == BUFFER_SIZE {
// 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 {
// Sleeping exactly one buffer
std::thread::sleep(std::time::Duration::from_secs_f32(
BUFFER_SIZE as f32 / md.sample_rate as f32 / 2.0,
))
}
sink.append(SamplesBuffer::new(2, md.sample_rate, samples.as_slice()));
index = 0;
}
}
sink.sleep_until_end()
std::thread::sleep(Duration::from_secs_f32(0.05))
}
}

View file

@ -1,16 +1,18 @@
[package]
name = "monolib"
version = "0.2.0"
version = "0.2.1"
edition = "2021"
license = "MIT"
description = "A library implementing the lonely radio audio streaming protocol"
repository = "https://github.com/ivabus/lonelyradio"
authors = [ "Ivan Bushchik <ivabus@ivabus.dev>"]
[lib]
name = "monolib"
crate-type = ["staticlib", "cdylib", "rlib"]
[dependencies]
rodio = { version = "0.17.3", default-features = false }
byteorder = "1.5.0"
rmp-serde = "1.1.2"
serde = { version = "1.0.197", features = ["derive"] }
[lib]
name = "monolib"
crate-type = ["staticlib", "cdylib"]

75
monolib/src/c.rs Normal file
View file

@ -0,0 +1,75 @@
use crate::*;
use std::ffi::{c_char, c_float, c_ushort};
use std::ffi::{CStr, CString};
#[no_mangle]
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub extern "C" fn c_start(server: *const c_char) {
let serv = unsafe { CStr::from_ptr(server) };
run(match serv.to_str() {
Ok(s) => s,
_ => "",
})
}
#[no_mangle]
pub extern "C" fn c_toggle() {
toggle()
}
#[no_mangle]
pub extern "C" fn c_stop() {
stop()
}
#[no_mangle]
pub extern "C" fn c_get_state() -> c_ushort {
let state = STATE.read().unwrap();
*state as c_ushort
}
#[no_mangle]
pub extern "C" fn c_get_metadata_artist() -> *mut c_char {
let md = MD.read().unwrap();
let md = md.clone();
CString::new(match md {
Some(md) => md.artist,
None => "".to_string(),
})
.unwrap()
.into_raw()
}
#[no_mangle]
pub extern "C" fn c_get_metadata_album() -> *mut c_char {
let md = MD.read().unwrap();
let md = md.clone();
CString::new(match md {
Some(md) => md.album,
None => "".to_string(),
})
.unwrap()
.into_raw()
}
#[no_mangle]
pub extern "C" fn c_get_metadata_title() -> *mut c_char {
let md = MD.read().unwrap();
let md = md.clone();
CString::new(match md {
Some(md) => md.title,
None => "".to_string(),
})
.unwrap()
.into_raw()
}
#[no_mangle]
pub extern "C" fn c_get_metadata_length() -> *mut c_float {
let md = MD.read().unwrap();
match md.as_ref() {
Some(md) => &mut (md.length as c_float / md.sample_rate as c_float),
None => &mut 0.0,
}
}

186
monolib/src/lib.rs Normal file
View file

@ -0,0 +1,186 @@
/// Functions, providing C-like API
pub mod c;
use byteorder::ByteOrder;
use rodio::buffer::SamplesBuffer;
use rodio::{OutputStream, Sink};
use serde::Deserialize;
use std::io::Read;
use std::net::TcpStream;
use std::sync::RwLock;
use std::time::Instant;
// How many samples to cache before playing in samples (both channels) SHOULD BE EVEN
const BUFFER_SIZE: usize = 4800;
// How many buffers to cache
const CACHE_SIZE: usize = 160;
static SINK: RwLock<Option<Sink>> = RwLock::new(None);
static MD: RwLock<Option<Metadata>> = RwLock::new(None);
static STATE: RwLock<State> = RwLock::new(State::NotStarted);
/// Player state
#[derive(Clone, Copy, PartialEq)]
#[repr(u8)]
pub enum State {
NotStarted = 0,
Resetting = 1,
Playing = 2,
Paused = 3,
}
/// Track metadata
#[derive(Deserialize, Clone, Debug, PartialEq)]
pub struct Metadata {
/// In samples, length / (sample_rate * 2 (channels)) = length in seconds
pub length: u64,
pub sample_rate: u32,
pub title: String,
pub album: String,
pub artist: String,
}
/// Play/pauses playback
pub fn toggle() {
let mut state = crate::STATE.write().unwrap();
if *state == State::Playing {
*state = State::Paused;
let sink = SINK.read().unwrap();
if let Some(sink) = sink.as_ref() {
sink.pause()
}
} else if *state == State::Paused {
*state = State::Playing;
let sink = SINK.read().unwrap();
if let Some(sink) = sink.as_ref() {
sink.play()
}
}
}
/// Stops playback
pub fn stop() {
let mut state = STATE.write().unwrap();
*state = State::Resetting;
let sink = SINK.read().unwrap();
if let Some(sink) = sink.as_ref() {
sink.pause()
}
drop(sink);
drop(state);
// Blocking main thread
while *STATE.read().unwrap() == State::Resetting {
std::thread::sleep(std::time::Duration::from_secs_f32(0.01))
}
}
pub fn get_state() -> State {
*STATE.read().unwrap()
}
pub fn get_metadata() -> Option<Metadata> {
MD.read().unwrap().clone()
}
fn _stop() {
let sink = SINK.read().unwrap();
if let Some(sink) = sink.as_ref() {
sink.pause();
sink.clear();
}
let mut md = MD.write().unwrap();
if md.is_some() {
*md = None;
}
*STATE.write().unwrap() = State::NotStarted;
}
// Reset - true, not - false
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.read().unwrap() == State::Resetting {
return true;
}
}
false
}
/// Starts playing at "server:port"
pub fn run(server: &str) {
let mut state = STATE.write().unwrap();
if *state == State::Playing || *state == State::Paused {
return;
}
*state = State::Playing;
drop(state);
let mut stream = TcpStream::connect(server).unwrap();
let mut sink = SINK.write().unwrap();
let (_stream, stream_handle) = OutputStream::try_default().unwrap();
// Can't reuse old sink for some reason
let audio_sink = Sink::try_new(&stream_handle).unwrap();
*sink = Some(audio_sink);
drop(sink);
let mut buffer = [0u8; 2];
let mut samples = [0f32; BUFFER_SIZE];
loop {
let mut index = 0usize;
let recv_md: Metadata =
rmp_serde::from_read(&stream).expect("Failed to parse track metadata");
let mut md = MD.write().unwrap();
*md = Some(recv_md.clone());
drop(md);
for _ in 0..recv_md.length {
while *STATE.read().unwrap() == State::Paused {
std::thread::sleep(std::time::Duration::from_secs_f32(0.25))
}
if *STATE.read().unwrap() == State::Resetting {
_stop();
return;
}
if stream.read_exact(&mut buffer).is_err() {
return;
};
samples[index] = byteorder::LittleEndian::read_i16(&buffer[..2]) as f32 / 32768.0;
index += 1;
if index == BUFFER_SIZE {
// 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
let sink = SINK.read().unwrap();
if let Some(sink) = sink.as_ref() {
while sink.len() >= CACHE_SIZE {
// Sleeping exactly one buffer and watching for reset signal
if watching_sleep(
if sink.len() > 2 {
sink.len() as f32 - 2.0
} else {
0.5
} * BUFFER_SIZE as f32 / recv_md.sample_rate as f32
/ 2.0,
) {
_stop();
return;
}
}
sink.append(SamplesBuffer::new(2, recv_md.sample_rate, samples.as_slice()));
index = 0;
}
}
}
}
}

20
monolib/src/monolib.h Normal file
View file

@ -0,0 +1,20 @@
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
void c_start(const char *server);
void c_toggle(void);
void c_stop(void);
unsigned short c_get_state(void);
char *c_get_metadata_artist(void);
char *c_get_metadata_album(void);
char *c_get_metadata_title(void);
float *c_get_metadata_length(void);

View file

@ -4,16 +4,14 @@
### Build `monolib`
Run in `monolib` directory
```
cargo lipo --release --targets aarch64-apple-ios
cargo lipo --release --targets aarch64-apple-ios -p monolib
```
For running in simulator
```
cargo lipo --release --targets aarch64-apple-ios-sim,x86_64-apple-ios
cargo lipo --release --targets aarch64-apple-ios-sim,x86_64-apple-ios -p monolib
```
### Build and run app

View file

@ -20,14 +20,14 @@ class MonoLib {
} catch {
print("Failed to set the audio session configuration")
}
start(server)
c_start(server)
}
}
struct ContentView: View {
let timer = Timer.publish(every: 0.25, on: .main, in: .common).autoconnect()
@State private var server: String = ""
@State private var port: String = ""
@State private var server: String = "ivabus.dev"
@State private var port: String = "5894"
@State private var playing: Bool = true
@State private var running: Bool = false
@ -62,7 +62,7 @@ struct ContentView: View {
Button(action: {
if running {
playing = !playing
toggle()
c_toggle()
}
running = true
let a = MonoLib()
@ -76,23 +76,35 @@ struct ContentView: View {
).font(.largeTitle)
}.buttonStyle(
.borderedProminent)
Button(action: {
reset()
running = false
playing = true
}) { Image(systemName: "stop").font(.title3) }.buttonStyle(
.bordered
).disabled(!running)
HStack{
Button(action: {
c_stop()
running = false
playing = true
}) { Image(systemName: "stop").font(.title3) }.buttonStyle(
.bordered
).disabled(!running)
Button(action: {
c_stop()
playing = true
let a = MonoLib()
Task.init {
await a.run(server: server + ":" + port)
}
}) {Image(systemName: "forward").font(.title3)}.buttonStyle(.bordered).disabled(!running)
}
}.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()!)
}
VStack(spacing: 10) {
Text(now_playing_artist).onReceive(timer) { _ in
now_playing_artist = String(cString: c_get_metadata_artist()!)
}
Text(now_playing_album).onReceive(timer) { _ in
now_playing_album = String(cString: c_get_metadata_album()!)
}
Text(now_playing_title).onReceive(timer) { _ in
now_playing_title = String(cString: c_get_metadata_title()!)
}.bold()
}.frame(minHeight: 100)
}.padding()

View file

@ -1,212 +0,0 @@
use byteorder::ByteOrder;
use rodio::buffer::SamplesBuffer;
use rodio::{OutputStream, Sink};
use serde::Deserialize;
use std::ffi::{CStr, CString};
use std::io::Read;
use std::net::TcpStream;
use std::os::raw::c_char;
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 = 40;
static mut SINK: Option<Box<Sink>> = None;
static mut MD: Option<SentMetadata> = None;
static mut STATE: State = State::NotStarted;
#[derive(PartialEq)]
enum State {
NotStarted,
Resetting,
Playing,
Paused,
}
#[no_mangle]
pub extern "C" fn start(server: *const c_char) {
let serv = unsafe { CStr::from_ptr(server) };
unsafe {
run(match serv.to_str() {
Ok(s) => s,
_ => "",
})
}
}
#[no_mangle]
pub extern "C" fn toggle() {
unsafe {
if STATE == State::Playing {
STATE = State::Paused;
if let Some(sink) = &SINK {
sink.pause();
}
} else if STATE == State::Paused {
STATE = State::Playing;
if let Some(sink) = &SINK {
sink.play();
}
}
}
}
#[no_mangle]
pub extern "C" fn reset() {
unsafe {
STATE = State::Resetting;
if let Some(sink) = &SINK {
sink.pause();
}
// Blocking main thread
while STATE == State::Resetting {
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) {
if STATE == State::Playing || STATE == State::Paused {
return;
}
STATE = State::Playing;
let mut stream = TcpStream::connect(server).unwrap();
println!("Connected to {} from {}", stream.peer_addr().unwrap(), stream.local_addr().unwrap());
let (_stream, stream_handle) = OutputStream::try_default().unwrap();
match &SINK {
None => {
let sink = Sink::try_new(&stream_handle).unwrap();
SINK = Some(Box::new(sink));
}
Some(s) => {
if s.is_paused() {
s.play()
}
}
}
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];
loop {
let mut index = 0usize;
let md: SentMetadata =
rmp_serde::from_read(&stream).expect("Failed to parse track metadata");
MD = Some(md.clone());
for _ in 0..md.lenght {
while STATE == State::Paused {
std::thread::sleep(std::time::Duration::from_secs_f32(0.25))
}
if STATE == State::Resetting {
_reset();
return;
}
if stream.read_exact(&mut buffer).is_err() {
return;
};
samples[index] = byteorder::LittleEndian::read_i16(&buffer[..2]) as f32 / 32768.0;
index += 1;
if index == BUFFER_SIZE {
// 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
if let Some(sink) = &SINK {
while sink.len() >= CACHE_SIZE {
// Sleeping exactly one buffer and watching for reset signal
if watching_sleep(
if sink.len() > 2 {
sink.len() as f32 - 2.0
} else {
0.5
} * BUFFER_SIZE as f32 / md.sample_rate as f32
/ 2.0,
) {
_reset();
return;
}
}
sink.append(SamplesBuffer::new(2, md.sample_rate, samples.as_slice()));
index = 0;
}
}
}
}
}

View file

@ -1,11 +0,0 @@
#include <stdint.h>
void start(const char *server);
void toggle();
void reset();
const char *get_metadata_artist();
const char *get_metadata_album();
const char *get_metadata_title();

View file

@ -32,7 +32,7 @@ struct Args {
#[derive(Serialize)]
struct SentMetadata {
// In bytes, we need to read next track metadata
lenght: u64,
length: u64,
// Yep, no more interpolation
sample_rate: u32,
title: String,
@ -41,7 +41,7 @@ struct SentMetadata {
}
async fn stream_samples(
track_samples: Vec<f32>,
track_samples: Vec<i16>,
war: bool,
md: SentMetadata,
s: &mut TcpStream,
@ -53,9 +53,9 @@ async fn stream_samples(
for sample in track_samples {
if s.write_all(
&(if war {
sample.signum() as i16 * 32767
sample.signum() * 32767
} else {
(sample * 32768_f32) as i16
sample
}
.to_le_bytes()),
)
@ -191,7 +191,7 @@ async fn stream(mut s: TcpStream, tracklist: Arc<Vec<PathBuf>>) {
Ok(decoded) => {
sample_rate = decoded.spec().rate;
let mut byte_buf =
SampleBuffer::<f32>::new(decoded.capacity() as u64, *decoded.spec());
SampleBuffer::<i16>::new(decoded.capacity() as u64, *decoded.spec());
byte_buf.copy_interleaved_ref(decoded);
samples.append(&mut byte_buf.samples_mut().to_vec());
continue;
@ -203,7 +203,7 @@ async fn stream(mut s: TcpStream, tracklist: Arc<Vec<PathBuf>>) {
}
}
let md = SentMetadata {
lenght: samples.len() as u64,
length: samples.len() as u64,
sample_rate,
title,
album,