struct SoundcloudFetcher {
duration: Duration,
segment_infos: Vec<SegmentInfo>,
segment_cache: Arc<Mutex<SegmentCache>>,
}
impl SoundcloudFetcher {
pub fn spawn(hls: &MediaPlaylist) -> Arc<Mutex<SegmentCache>> {
let segment_cache = Arc::new(Mutex::new(SegmentCache {
source_position: (0, 0),
segments: vec![None; hls.segments.iter().len()],
}));
let fetcher = SoundcloudFetcher {
duration: hls.duration(),
segment_infos: hls
.segments
.values()
.map(|segment| SegmentInfo {
url: segment.uri().to_string(),
duration: segment.duration.duration(),
})
.collect(),
segment_cache: segment_cache.clone(),
};
std::thread::spawn(move || {
fetcher.run();
});
segment_cache
}
fn segment_at(&self, time: Duration) -> Option<usize> {
let mut t = Duration::ZERO;
for (idx, segment) in self.segment_infos.iter().enumerate() {
t += segment.duration;
if t > time {
return Some(idx);
}
}
return None;
}
fn time_at_position(&self, segment_idx: usize, byte_idx: usize) -> Duration {
let mut t = Duration::ZERO;
for (idx, segment) in self.segment_infos.iter().enumerate() {
if segment_idx == idx {
t += Duration::from_secs_f64(byte_idx as f64 / 128_000.0);
break;
}
t += segment.duration;
}
return t;
}
fn run(self) {
loop {
// We look where the source is located and make sure the following 10 seconds are loaded in cache
let source_position = self.segment_cache.lock().source_position;
let curr_time = self.time_at_position(source_position.0, source_position.1);
let curr_segment = self.segment_at(curr_time).unwrap();
let target_time = (curr_time + Duration::SECOND * 10).min(self.duration);
let target_segment = self
.segment_at(target_time)
.unwrap_or(self.segment_infos.len());
for idx in curr_segment..=target_segment {
if self.segment_cache.lock().segments.get(idx) == Some(&None) {
println!("fetching next segment: {}", idx);
let info = &self.segment_infos[idx];
self.segment_cache.lock().segments[idx] = Some(
ureq::get(&info.url)
.call()
.unwrap()
.into_reader()
.bytes()
.collect::<Result<Vec<u8>, std::io::Error>>()
.unwrap(),
);
}
}
std::thread::sleep(Duration::SECOND);
}
}
}
struct SoundcloudMediaSource {
duration: Duration,
nb_segments: usize,
segment_cache: Arc<Mutex<SegmentCache>>,
curr_segment: Option<Vec<u8>>,
curr_segment_idx: usize,
curr_offset: usize,
}
impl SoundcloudMediaSource {
pub fn new(
duration: Duration,
nb_segments: usize,
segment_cache: Arc<Mutex<SegmentCache>>,
) -> Self {
Self {
duration,
nb_segments,
segment_cache,
curr_segment: None,
curr_segment_idx: 0,
curr_offset: 0,
}
}
fn next_segment(&mut self) {
println!(
"grabbing segment {} from the cache...",
self.curr_segment_idx
);
self.curr_segment = Some(
self.segment_cache.lock().segments[self.curr_segment_idx]
.clone()
.unwrap(),
);
}
fn advance(&mut self) -> bool {
self.curr_segment_idx += 1;
if self.curr_segment_idx < self.nb_segments {
self.next_segment();
self.curr_offset = 0;
true
} else {
self.curr_segment = None;
false
}
}
fn ended(&self) -> bool {
self.curr_segment_idx >= self.nb_segments
}
}
impl Read for SoundcloudMediaSource {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
if self.curr_segment.is_none() {
self.next_segment();
self.curr_offset = 0;
}
let mut nb_filled = 0;
while nb_filled < buf.len() && !self.ended() {
let segment = self.curr_segment.as_ref().unwrap();
let nb_remaining_to_take = buf.len() - nb_filled;
let nb_available_in_segment = segment.len() - self.curr_offset;
let nb_to_take = nb_remaining_to_take.min(nb_available_in_segment);
let segment_slice = &segment[self.curr_offset..][..nb_to_take];
buf[nb_filled..nb_filled + nb_to_take].copy_from_slice(segment_slice);
self.curr_offset += nb_to_take;
nb_filled += nb_to_take;
if nb_filled < buf.len() {
self.advance();
}
}
self.segment_cache.lock().source_position = (self.curr_segment_idx, self.curr_offset);
Ok(nb_filled)
}
}
impl Seek for SoundcloudMediaSource {
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
todo!()
}
}
impl MediaSource for SoundcloudMediaSource {
fn is_seekable(&self) -> bool {
false
}
fn byte_len(&self) -> Option<u64> {
Some((128_000.0 * self.duration.as_secs_f64()).ceil() as u64)
}
}