mirror of
https://github.com/ivabus/lonelyradio
synced 2024-11-21 15:45:09 +03:00
WIP sample rate
Signed-off-by: Ivan Bushchik <ivabus@ivabus.dev>
This commit is contained in:
parent
fd780cf28b
commit
dd59f53d6f
6 changed files with 299 additions and 113 deletions
|
@ -7,9 +7,9 @@ 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;
|
||||
const BUFFER_SIZE: usize = 96000;
|
||||
// How many buffers to cache
|
||||
const CACHE_SIZE: usize = 100;
|
||||
const CACHE_SIZE: usize = 20;
|
||||
|
||||
enum Channel {
|
||||
Right,
|
||||
|
@ -31,6 +31,14 @@ struct Args {
|
|||
/// More verbose
|
||||
#[arg(short)]
|
||||
verbose: bool,
|
||||
|
||||
/// Stream in f32le instead of s16le
|
||||
#[arg(short, long)]
|
||||
float: bool,
|
||||
|
||||
/// Stream in custom sample rate
|
||||
#[arg(short, long, default_value = "44100")]
|
||||
sample_rate: u32,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
@ -53,13 +61,28 @@ fn main() {
|
|||
};
|
||||
let (_stream, stream_handle) = OutputStream::try_default().unwrap();
|
||||
let sink = Sink::try_new(&stream_handle).unwrap();
|
||||
let mut buffer = [0u8; 4];
|
||||
let mut buffer = vec![
|
||||
0u8;
|
||||
if args.float {
|
||||
8
|
||||
} else {
|
||||
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;
|
||||
let sample_l = if args.float {
|
||||
byteorder::LittleEndian::read_f32(&buffer[..4])
|
||||
} else {
|
||||
byteorder::LittleEndian::read_i16(&buffer[..2]) as f32 / 32768.0
|
||||
};
|
||||
let sample_r = if args.float {
|
||||
byteorder::LittleEndian::read_f32(&buffer[4..])
|
||||
} else {
|
||||
byteorder::LittleEndian::read_i16(&buffer[2..]) as f32 / 32768.0
|
||||
};
|
||||
// Left channel
|
||||
samples[index] = match channel {
|
||||
Channel::Left | Channel::Stereo => sample_l,
|
||||
|
@ -102,14 +125,15 @@ fn main() {
|
|||
} else {
|
||||
1
|
||||
} as f32) * BUFFER_SIZE as f32
|
||||
/ 44100.0 / 2.0,
|
||||
/ args.sample_rate as f32
|
||||
/ 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()));
|
||||
sink.append(SamplesBuffer::new(2, args.sample_rate, samples.as_slice()));
|
||||
index = 0;
|
||||
}
|
||||
}
|
||||
|
|
40
platform/swiftui/Launch Screen.storyboard
Normal file
40
platform/swiftui/Launch Screen.storyboard
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="22505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22504"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="obG-Y5-kRd">
|
||||
<rect key="frame" x="0.0" y="832" width="393" height="0.0"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="Bcu-3y-fUS"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="Bcu-3y-fUS" firstAttribute="centerX" secondItem="obG-Y5-kRd" secondAttribute="centerX" id="5cz-MP-9tL"/>
|
||||
<constraint firstItem="obG-Y5-kRd" firstAttribute="leading" secondItem="Bcu-3y-fUS" secondAttribute="leading" symbolic="YES" id="SfN-ll-jLj"/>
|
||||
<constraint firstAttribute="bottom" secondItem="obG-Y5-kRd" secondAttribute="bottom" constant="20" id="Y44-ml-fuU"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
|
@ -25,8 +25,8 @@ class MonoLib {
|
|||
}
|
||||
|
||||
struct ContentView: View {
|
||||
@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
|
||||
|
||||
|
@ -56,13 +56,15 @@ struct ContentView: View {
|
|||
|
||||
Button(action: {
|
||||
if running {
|
||||
playing = !playing
|
||||
toggle()
|
||||
}
|
||||
running = true
|
||||
let a = MonoLib()
|
||||
Task.init {
|
||||
await a.run(server: server + ":" + port)
|
||||
playing = !playing
|
||||
} else {
|
||||
let a = MonoLib()
|
||||
Task.init {
|
||||
await a.run(server: server + ":" + port)
|
||||
}
|
||||
running = true
|
||||
playing = true
|
||||
}
|
||||
}) {
|
||||
Image(
|
||||
|
@ -73,8 +75,7 @@ struct ContentView: View {
|
|||
.borderedProminent)
|
||||
Button(action: {
|
||||
reset()
|
||||
running = false
|
||||
playing = true
|
||||
(running, playing) = (false, true)
|
||||
}) { Image(systemName: "stop").font(.title3) }.buttonStyle(
|
||||
.bordered
|
||||
).disabled(!running)
|
||||
|
|
|
@ -5,16 +5,23 @@ use std::ffi::CStr;
|
|||
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;
|
||||
const BUFFER_SIZE: usize = 4410;
|
||||
// How many buffers to cache
|
||||
const CACHE_SIZE: usize = 40;
|
||||
const CACHE_SIZE: usize = 20;
|
||||
|
||||
static mut SINK: Option<Box<Sink>> = None;
|
||||
static mut RUNNING: bool = false;
|
||||
static mut STOPPED: bool = false;
|
||||
static mut RESET: bool = false;
|
||||
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) {
|
||||
|
@ -30,13 +37,13 @@ pub extern "C" fn start(server: *const c_char) {
|
|||
#[no_mangle]
|
||||
pub extern "C" fn toggle() {
|
||||
unsafe {
|
||||
if !STOPPED {
|
||||
STOPPED = true;
|
||||
if STATE == State::Playing {
|
||||
STATE = State::Paused;
|
||||
if let Some(sink) = &SINK {
|
||||
sink.pause();
|
||||
}
|
||||
} else {
|
||||
STOPPED = false;
|
||||
} else if STATE == State::Paused {
|
||||
STATE = State::Playing;
|
||||
if let Some(sink) = &SINK {
|
||||
sink.play();
|
||||
}
|
||||
|
@ -47,19 +54,43 @@ pub extern "C" fn toggle() {
|
|||
#[no_mangle]
|
||||
pub extern "C" fn reset() {
|
||||
unsafe {
|
||||
RESET = true;
|
||||
STATE = State::Resetting;
|
||||
if let Some(sink) = &SINK {
|
||||
sink.pause();
|
||||
}
|
||||
// Blocking main thread
|
||||
while RESET {
|
||||
std::thread::sleep(std::time::Duration::from_secs_f32(0.02))
|
||||
while STATE == State::Resetting {
|
||||
std::thread::sleep(std::time::Duration::from_secs_f32(0.01))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn _reset() {
|
||||
if let Some(sink) = &SINK {
|
||||
sink.pause();
|
||||
sink.clear();
|
||||
}
|
||||
SINK = 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
|
||||
}
|
||||
|
||||
unsafe fn run(server: &str) {
|
||||
if RUNNING {
|
||||
if STATE == State::Playing || STATE == State::Paused {
|
||||
return;
|
||||
}
|
||||
RUNNING = true;
|
||||
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();
|
||||
|
@ -78,19 +109,11 @@ unsafe fn run(server: &str) {
|
|||
let mut samples = [0f32; BUFFER_SIZE];
|
||||
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))
|
||||
while STATE == State::Paused {
|
||||
std::thread::sleep(std::time::Duration::from_secs_f32(0.25))
|
||||
}
|
||||
if RESET {
|
||||
RUNNING = false;
|
||||
STOPPED = false;
|
||||
|
||||
if let Some(sink) = &SINK {
|
||||
sink.pause();
|
||||
sink.clear();
|
||||
}
|
||||
SINK = None;
|
||||
RESET = false;
|
||||
if STATE == State::Resetting {
|
||||
_reset();
|
||||
return;
|
||||
}
|
||||
let sample_l = byteorder::LittleEndian::read_i16(&buffer[..2]) as f32 / 32768.0;
|
||||
|
@ -108,15 +131,18 @@ unsafe fn run(server: &str) {
|
|||
// 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
|
||||
std::thread::sleep(std::time::Duration::from_secs_f32(
|
||||
(if sink.len() >= 2 {
|
||||
sink.len() - 2
|
||||
// Sleeping exactly one buffer and watching for reset signal
|
||||
if watching_sleep(
|
||||
if sink.len() > 2 {
|
||||
sink.len() as f32 - 2.0
|
||||
} else {
|
||||
1
|
||||
} as f32) * BUFFER_SIZE as f32
|
||||
/ 44100.0 / 2.0,
|
||||
))
|
||||
0.5
|
||||
} * BUFFER_SIZE as f32 / 44100.0
|
||||
/ 2.0,
|
||||
) {
|
||||
_reset();
|
||||
return;
|
||||
}
|
||||
}
|
||||
sink.append(SamplesBuffer::new(2, 44100, samples.as_slice()));
|
||||
index = 0;
|
||||
|
|
|
@ -4,4 +4,4 @@ void start(const char *server);
|
|||
|
||||
void toggle();
|
||||
|
||||
void reset();
|
||||
void reset();
|
217
src/main.rs
217
src/main.rs
|
@ -1,10 +1,13 @@
|
|||
use std::path::PathBuf;
|
||||
use std::i16;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
use chrono::Local;
|
||||
use clap::Parser;
|
||||
use rand::prelude::*;
|
||||
use samplerate::ConverterType;
|
||||
use rubato::{
|
||||
Resampler, SincFixedIn, SincInterpolationParameters, SincInterpolationType, WindowFunction,
|
||||
};
|
||||
use symphonia::core::audio::SampleBuffer;
|
||||
use symphonia::core::codecs::CODEC_TYPE_NULL;
|
||||
use symphonia::core::io::MediaSourceStream;
|
||||
|
@ -24,6 +27,14 @@ struct Args {
|
|||
|
||||
#[arg(short, long)]
|
||||
war: bool,
|
||||
|
||||
/// Stream in f32le instead of s16le
|
||||
#[arg(short, long)]
|
||||
float: bool,
|
||||
|
||||
/// Stream in custom sample rate
|
||||
#[arg(short, long, default_value = "44100")]
|
||||
sample_rate: u32,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
|
@ -35,8 +46,7 @@ async fn main() {
|
|||
.filter_entry(is_not_hidden)
|
||||
.filter_map(|v| v.ok())
|
||||
.map(|x| x.into_path())
|
||||
.filter(track_valid)
|
||||
.into_iter()
|
||||
.filter(|arg0: &std::path::PathBuf| track_valid(arg0))
|
||||
.collect::<Vec<PathBuf>>(),
|
||||
);
|
||||
loop {
|
||||
|
@ -48,7 +58,7 @@ fn is_not_hidden(entry: &DirEntry) -> bool {
|
|||
entry.file_name().to_str().map(|s| entry.depth() == 0 || !s.starts_with('.')).unwrap_or(false)
|
||||
}
|
||||
|
||||
fn track_valid(track: &PathBuf) -> bool {
|
||||
fn track_valid(track: &Path) -> bool {
|
||||
if !track.metadata().unwrap().is_file() {
|
||||
return false;
|
||||
}
|
||||
|
@ -60,36 +70,139 @@ fn track_valid(track: &PathBuf) -> bool {
|
|||
true
|
||||
}
|
||||
|
||||
async fn stream_samples(
|
||||
args: &Args,
|
||||
track_samples: Vec<f32>,
|
||||
rate: u32,
|
||||
s: &mut TcpStream,
|
||||
) -> bool {
|
||||
let params = SincInterpolationParameters {
|
||||
sinc_len: 64,
|
||||
f_cutoff: 0.96,
|
||||
interpolation: SincInterpolationType::Quadratic,
|
||||
oversampling_factor: 16,
|
||||
window: WindowFunction::Blackman,
|
||||
};
|
||||
let target_rate = args.sample_rate;
|
||||
let mut resampler = SincFixedIn::<f32>::new(
|
||||
target_rate as f64 / rate as f64,
|
||||
100.0,
|
||||
params,
|
||||
track_samples.len() / 2,
|
||||
2,
|
||||
)
|
||||
.unwrap();
|
||||
let start = std::time::Instant::now();
|
||||
let (left, right): (Vec<&f32>, Vec<&f32>) =
|
||||
(track_samples.iter().step_by(2).collect(), track_samples[1..].iter().step_by(2).collect());
|
||||
eprintln!("Splitted channels in {} ms", (std::time::Instant::now() - start).as_millis());
|
||||
let samples = if rate != target_rate {
|
||||
eprintln!("Resampling {} samples from {} to {}", track_samples.len(), rate, target_rate);
|
||||
let start = std::time::Instant::now();
|
||||
if rate > target_rate {
|
||||
let resampled_l =
|
||||
samplerate::convert(rate, target_rate, 1, samplerate::ConverterType::Linear, &left)
|
||||
.unwrap();
|
||||
let resampled_r = samplerate::convert(
|
||||
rate,
|
||||
target_rate,
|
||||
1,
|
||||
samplerate::ConverterType::Linear,
|
||||
right.as_slice(),
|
||||
)
|
||||
.unwrap();
|
||||
eprintln!("Resampled in {} ms", (std::time::Instant::now() - start).as_millis());
|
||||
vec![resampled_l, resampled_r]
|
||||
} else {
|
||||
match resampler.process(&[&left, &right], None) {
|
||||
Ok(s) => {
|
||||
eprintln!(
|
||||
"Resampled in {} ms",
|
||||
(std::time::Instant::now() - start).as_millis()
|
||||
);
|
||||
s
|
||||
}
|
||||
Err(e) => panic!("{}", e),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
vec![left, right]
|
||||
};
|
||||
let (left, right) = (&samples[0], &samples[1]);
|
||||
for (sample_l, sample_r) in left.iter().zip(right) {
|
||||
if args.float {
|
||||
let result = s
|
||||
.write(
|
||||
&(if args.war {
|
||||
sample_l.signum()
|
||||
} else {
|
||||
*sample_l
|
||||
})
|
||||
.to_le_bytes(),
|
||||
)
|
||||
.await;
|
||||
match result {
|
||||
Err(_) | Ok(0) => return true,
|
||||
_ => (),
|
||||
};
|
||||
let result = s
|
||||
.write(
|
||||
&(if args.war {
|
||||
sample_r.signum()
|
||||
} else {
|
||||
*sample_r
|
||||
})
|
||||
.to_le_bytes(),
|
||||
)
|
||||
.await;
|
||||
match result {
|
||||
Err(_) | Ok(0) => return true,
|
||||
_ => (),
|
||||
};
|
||||
} else {
|
||||
let result = s
|
||||
.write(
|
||||
&(if args.war {
|
||||
sample_l.signum() as i16 * 32767
|
||||
} else {
|
||||
(sample_l * 32768_f32) as i16
|
||||
})
|
||||
.to_le_bytes(),
|
||||
)
|
||||
.await;
|
||||
match result {
|
||||
Err(_) | Ok(0) => return true,
|
||||
_ => (),
|
||||
};
|
||||
let result = s
|
||||
.write(
|
||||
&(if args.war {
|
||||
sample_r.signum() as i16 * 32767
|
||||
} else {
|
||||
(sample_r * 32768_f32) as i16
|
||||
})
|
||||
.to_le_bytes(),
|
||||
)
|
||||
.await;
|
||||
match result {
|
||||
Err(_) | Ok(0) => return true,
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
}
|
||||
eprintln!("Exiting");
|
||||
false
|
||||
}
|
||||
|
||||
async fn stream(mut s: TcpStream, tracklist: Arc<Vec<PathBuf>>) {
|
||||
let args = Args::parse();
|
||||
|
||||
'track: loop {
|
||||
loop {
|
||||
s.writable().await.unwrap();
|
||||
let track = tracklist.choose(&mut thread_rng()).unwrap();
|
||||
eprintln!(
|
||||
"[{}] {} to {}:{}{}",
|
||||
Local::now().to_rfc3339(),
|
||||
track.to_str().unwrap(),
|
||||
s.peer_addr().unwrap().ip(),
|
||||
s.peer_addr().unwrap().port(),
|
||||
if args.war {
|
||||
" with WAR.rs"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
);
|
||||
|
||||
if args.public_log {
|
||||
println!(
|
||||
"[{}] {} to {}{}",
|
||||
Local::now().to_rfc3339(),
|
||||
track.to_str().unwrap(),
|
||||
s.peer_addr().unwrap().port(),
|
||||
if args.war {
|
||||
" with WAR.rs"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
);
|
||||
println!("[{}] {}", Local::now().to_rfc3339(), track.to_str().unwrap(),);
|
||||
}
|
||||
|
||||
let file = Box::new(std::fs::File::open(track).unwrap());
|
||||
|
@ -118,11 +231,15 @@ async fn stream(mut s: TcpStream, tracklist: Arc<Vec<PathBuf>>) {
|
|||
.expect("unsupported codec");
|
||||
|
||||
let track_id = track.id;
|
||||
let mut track_samples = vec![];
|
||||
let mut track_rate = 0;
|
||||
|
||||
loop {
|
||||
let packet = match format.next_packet() {
|
||||
Ok(packet) => packet,
|
||||
_ => continue 'track,
|
||||
_ => {
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
while !format.metadata().is_latest() {
|
||||
|
@ -136,46 +253,24 @@ async fn stream(mut s: TcpStream, tracklist: Arc<Vec<PathBuf>>) {
|
|||
match decoder.decode(&packet) {
|
||||
Ok(decoded) => {
|
||||
let rate = decoded.spec().rate;
|
||||
track_rate = rate;
|
||||
let mut byte_buf =
|
||||
SampleBuffer::<f32>::new(decoded.capacity() as u64, *decoded.spec());
|
||||
byte_buf.copy_interleaved_ref(decoded);
|
||||
let samples = if rate != 44100 {
|
||||
samplerate::convert(
|
||||
rate,
|
||||
44100,
|
||||
2,
|
||||
ConverterType::Linear,
|
||||
byte_buf.samples(),
|
||||
)
|
||||
.unwrap()
|
||||
} else {
|
||||
byte_buf.samples().to_vec()
|
||||
};
|
||||
for sample in samples {
|
||||
let result = s
|
||||
.write(
|
||||
&(if args.war {
|
||||
sample.signum() as i16 * 32767
|
||||
} else {
|
||||
(sample * 32768_f32) as i16
|
||||
})
|
||||
.to_le_bytes(),
|
||||
)
|
||||
.await;
|
||||
match result {
|
||||
Err(_) | Ok(0) => {
|
||||
return;
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
track_samples.append(&mut byte_buf.samples_mut().to_vec());
|
||||
continue;
|
||||
}
|
||||
_ => {
|
||||
// Handling any error as track skip
|
||||
continue 'track;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !track_samples.is_empty() {
|
||||
if stream_samples(&args, track_samples, track_rate, &mut s).await {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue