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]] [[package]]
name = "lonelyradio" name = "lonelyradio"
version = "0.2.0" version = "0.2.1"
dependencies = [ dependencies = [
"chrono", "chrono",
"clap", "clap",
@ -653,18 +653,15 @@ dependencies = [
[[package]] [[package]]
name = "monoclient" name = "monoclient"
version = "0.2.0" version = "0.2.1"
dependencies = [ dependencies = [
"byteorder",
"clap", "clap",
"rmp-serde", "monolib",
"rodio",
"serde",
] ]
[[package]] [[package]]
name = "monolib" name = "monolib"
version = "0.2.0" version = "0.2.1"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"rmp-serde", "rmp-serde",

View file

@ -1,10 +1,10 @@
[workspace] [workspace]
members = ["monoclient", "platform/swiftui/monolib"] members = [ "monoclient", "monolib"]
[package] [package]
name = "lonelyradio" name = "lonelyradio"
description = "TCP radio for lonely ones" description = "TCP radio for lonely ones"
version = "0.2.0" version = "0.2.1"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
authors = ["Ivan Bushchik <ivabus@ivabus.dev>"] 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 ### Clients
[monoclient](./monoclient) is a recommended client for lonelyradio [monoclient](./monoclient) is a recommended CLI client for lonelyradio that uses [monolib](./monolib)
```shell ```shell
monoclient <SERVER>:<PORT> monoclient <SERVER>:<PORT>
@ -32,6 +32,8 @@ monoclient <SERVER>:<PORT>
SwiftUI client is availible in [platform](./platform) directory. SwiftUI client is availible in [platform](./platform) directory.
[monolib](./monolib) provides lonelyradio-compatible C API for creating custom clients.
## License ## License
lonelyradio and monoclient are licensed under the terms of the [MIT license](./LICENSE). lonelyradio and monoclient are licensed under the terms of the [MIT license](./LICENSE).

View file

@ -1,12 +1,9 @@
[package] [package]
name = "monoclient" name = "monoclient"
version = "0.2.0" version = "0.2.1"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
[dependencies] [dependencies]
monolib = { path="../monolib" }
clap = { version = "4.4.18", features = ["derive"] } 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"] }

View file

@ -1,25 +1,6 @@
use byteorder::ByteOrder;
use clap::Parser; use clap::Parser;
use rodio::buffer::SamplesBuffer; use std::io::{IsTerminal, Write};
use rodio::{OutputStream, Sink}; use std::time::{Duration, Instant};
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,
}
#[derive(Parser)] #[derive(Parser)]
struct Args { struct Args {
@ -31,84 +12,98 @@ struct Args {
no_backspace: bool, no_backspace: bool,
} }
fn delete_chars(n: usize) { fn delete_chars(n: usize, nb: bool) {
if !nb {
print!("{}{}{}", "\u{8}".repeat(n), " ".repeat(n), "\u{8}".repeat(n)); print!("{}{}{}", "\u{8}".repeat(n), " ".repeat(n), "\u{8}".repeat(n));
std::io::stdout().flush().expect("Failed to flush stdout") std::io::stdout().flush().expect("Failed to flush stdout")
} else {
println!()
}
}
fn flush() {
std::io::stdout().flush().unwrap();
} }
fn main() { fn main() {
let mut args = Args::parse(); let mut args = Args::parse();
args.no_backspace |= !std::io::stdout().is_terminal(); args.no_backspace |= !std::io::stdout().is_terminal();
let mut stream = TcpStream::connect(&args.address) std::thread::spawn(move || monolib::run(&args.address));
.unwrap_or_else(|err| panic!("Failed to connect to {}: {}", args.address, err.to_string())); while monolib::get_metadata().is_none() {}
println!("Connected to {} from {}", stream.peer_addr().unwrap(), stream.local_addr().unwrap()); let mut md = monolib::get_metadata().unwrap();
let (_stream, stream_handle) = OutputStream::try_default().unwrap(); let seconds = md.length / md.sample_rate as u64 / 2;
let sink = Sink::try_new(&stream_handle).unwrap(); let mut track_start = Instant::now();
let mut buffer = [0u8; 2]; let mut seconds_past = 0;
let mut samples = [0f32; BUFFER_SIZE]; let mut msg_len = format!(
let mut latest_msg_len = 0; "Playing: {} - {} - {} ({}:{:02})",
print!("Playing: "); 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 { loop {
let mut index = 0usize; if monolib::get_metadata().unwrap() != md {
md = monolib::get_metadata().unwrap();
let md: SentMetadata = let seconds = md.length / md.sample_rate as u64 / 2;
rmp_serde::from_read(&stream).expect("Failed to parse track metadata"); delete_chars(msg_len, args.no_backspace);
let seconds = md.lenght / (2 * md.sample_rate as u64); msg_len = format!(
let total_lenght = format!("{}:{:02}", seconds / 60, seconds % 60); "Playing: {} - {} - {} ({}:{:02})",
let message = format!("{} - {} - {} ", md.artist, md.album, md.title); md.artist,
if latest_msg_len != 0 { md.album,
if args.no_backspace { md.title,
print!("\nPlaying: "); seconds / 60,
} else { seconds % 60
delete_chars(latest_msg_len) )
} .len();
} print!(
print!("{}", message); "Playing: {} - {} - {} (0:00 / {}:{:02})",
let mut prev_timestamp_len = 0; md.artist,
if args.no_backspace { md.album,
print!("({})", &total_lenght) md.title,
} else { seconds / 60,
print!("(0:00 / {})", &total_lenght); seconds % 60
// (0:00/ + :00 + minutes len );
prev_timestamp_len = 12 + format!("{}", seconds / 60).len(); flush();
} track_start = Instant::now();
std::io::stdout().flush().expect("Failed to flush stdout"); seconds_past = 0;
latest_msg_len = message.chars().count(); }
let mut second = 0; if (Instant::now() - track_start).as_secs() > seconds_past && !args.no_backspace {
for sample_index in 0..md.lenght { seconds_past = (Instant::now() - track_start).as_secs();
if (sample_index / (md.sample_rate as u64 * 2)) > second { msg_len = format!(
second += 1; "Playing: {} - {} - {} ({}:{:02} / {}:{:02})",
if !args.no_backspace { md.artist,
delete_chars(prev_timestamp_len); md.album,
let current_timestamp = md.title,
format!("({}:{:02} / {})", second / 60, second % 60, &total_lenght); seconds_past / 60,
print!("{}", &current_timestamp); seconds_past % 60,
std::io::stdout().flush().expect("Failed to flush stdout"); seconds / 60,
prev_timestamp_len = current_timestamp.len() seconds % 60
} )
} .len();
if stream.read_exact(&mut buffer).is_err() { delete_chars(msg_len, args.no_backspace);
return; print!(
}; "Playing: {} - {} - {} ({}:{:02} / {}:{:02})",
md.artist,
samples[index] = byteorder::LittleEndian::read_i16(&buffer[..2]) as f32 / 32768.0; md.album,
index += 1; md.title,
seconds_past / 60,
if index == BUFFER_SIZE { seconds_past % 60,
// Sink's thread is detached from main thread, so we need to synchronize with it seconds / 60,
// Why we should synchronize with it? seconds % 60
// 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 flush();
while sink.len() >= CACHE_SIZE { }
// Sleeping exactly one buffer std::thread::sleep(Duration::from_secs_f32(0.05))
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()
} }
} }

View file

@ -1,16 +1,18 @@
[package] [package]
name = "monolib" name = "monolib"
version = "0.2.0" version = "0.2.1"
edition = "2021" edition = "2021"
license = "MIT" 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] [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" rmp-serde = "1.1.2"
serde = { version = "1.0.197", features = ["derive"] } 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` ### 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 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 ### Build and run app

View file

@ -20,14 +20,14 @@ class MonoLib {
} catch { } catch {
print("Failed to set the audio session configuration") print("Failed to set the audio session configuration")
} }
start(server) c_start(server)
} }
} }
struct ContentView: View { struct ContentView: View {
let timer = Timer.publish(every: 0.25, on: .main, in: .common).autoconnect() let timer = Timer.publish(every: 0.25, on: .main, in: .common).autoconnect()
@State private var server: String = "" @State private var server: String = "ivabus.dev"
@State private var port: String = "" @State private var port: String = "5894"
@State private var playing: Bool = true @State private var playing: Bool = true
@State private var running: Bool = false @State private var running: Bool = false
@ -62,7 +62,7 @@ struct ContentView: View {
Button(action: { Button(action: {
if running { if running {
playing = !playing playing = !playing
toggle() c_toggle()
} }
running = true running = true
let a = MonoLib() let a = MonoLib()
@ -76,23 +76,35 @@ struct ContentView: View {
).font(.largeTitle) ).font(.largeTitle)
}.buttonStyle( }.buttonStyle(
.borderedProminent) .borderedProminent)
HStack{
Button(action: { Button(action: {
reset() c_stop()
running = false running = false
playing = true playing = true
}) { Image(systemName: "stop").font(.title3) }.buttonStyle( }) { Image(systemName: "stop").font(.title3) }.buttonStyle(
.bordered .bordered
).disabled(!running) ).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) }.frame(width: 300)
Text(now_playing_artist).font(.title2).onReceive(timer) { _ in VStack(spacing: 10) {
now_playing_artist = String(cString: get_metadata_artist()!) Text(now_playing_artist).onReceive(timer) { _ in
now_playing_artist = String(cString: c_get_metadata_artist()!)
} }
Text(now_playing_album).onReceive(timer) { _ in Text(now_playing_album).onReceive(timer) { _ in
now_playing_album = String(cString: get_metadata_album()!) now_playing_album = String(cString: c_get_metadata_album()!)
}
Text(now_playing_title).font(.title).bold().onReceive(timer) { _ in
now_playing_title = String(cString: get_metadata_title()!)
} }
Text(now_playing_title).onReceive(timer) { _ in
now_playing_title = String(cString: c_get_metadata_title()!)
}.bold()
}.frame(minHeight: 100)
}.padding() }.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)] #[derive(Serialize)]
struct SentMetadata { struct SentMetadata {
// In bytes, we need to read next track metadata // In bytes, we need to read next track metadata
lenght: u64, length: u64,
// Yep, no more interpolation // Yep, no more interpolation
sample_rate: u32, sample_rate: u32,
title: String, title: String,
@ -41,7 +41,7 @@ struct SentMetadata {
} }
async fn stream_samples( async fn stream_samples(
track_samples: Vec<f32>, track_samples: Vec<i16>,
war: bool, war: bool,
md: SentMetadata, md: SentMetadata,
s: &mut TcpStream, s: &mut TcpStream,
@ -53,9 +53,9 @@ async fn stream_samples(
for sample in track_samples { for sample in track_samples {
if s.write_all( if s.write_all(
&(if war { &(if war {
sample.signum() as i16 * 32767 sample.signum() * 32767
} else { } else {
(sample * 32768_f32) as i16 sample
} }
.to_le_bytes()), .to_le_bytes()),
) )
@ -191,7 +191,7 @@ async fn stream(mut s: TcpStream, tracklist: Arc<Vec<PathBuf>>) {
Ok(decoded) => { Ok(decoded) => {
sample_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::<i16>::new(decoded.capacity() as u64, *decoded.spec());
byte_buf.copy_interleaved_ref(decoded); byte_buf.copy_interleaved_ref(decoded);
samples.append(&mut byte_buf.samples_mut().to_vec()); samples.append(&mut byte_buf.samples_mut().to_vec());
continue; continue;
@ -203,7 +203,7 @@ async fn stream(mut s: TcpStream, tracklist: Arc<Vec<PathBuf>>) {
} }
} }
let md = SentMetadata { let md = SentMetadata {
lenght: samples.len() as u64, length: samples.len() as u64,
sample_rate, sample_rate,
title, title,
album, album,