|
|
|
@ -3,18 +3,18 @@ use std::path::{Path, PathBuf}; |
|
|
|
use anyhow::anyhow;
|
|
|
|
use bytes::Bytes;
|
|
|
|
use futures_util::future::join_all;
|
|
|
|
use http::{uri::{Authority, Scheme}, Uri};
|
|
|
|
use log::debug;
|
|
|
|
use http::{uri::{Authority, Scheme}, StatusCode, Uri};
|
|
|
|
use log::{debug, info};
|
|
|
|
use m3u8_rs::{MediaPlaylist, MediaSegment, Playlist};
|
|
|
|
use tokio::{io::AsyncWriteExt as _, fs::File};
|
|
|
|
use tokio::{fs::File, io::AsyncWriteExt as _, time::{sleep, Duration, Instant}};
|
|
|
|
|
|
|
|
use crate::{client_actor::ClientActorHandle, client::DownloadState};
|
|
|
|
use crate::{client::DownloadState, client_actor::ClientActorHandle};
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub(super) enum TsState {
|
|
|
|
Created, // Nothing is done.
|
|
|
|
Failed, // The download has failed.
|
|
|
|
Failed(Option<Instant>), // The download has failed.
|
|
|
|
Ready, // All .ts downloads are done.
|
|
|
|
}
|
|
|
|
|
|
|
|
@ -30,6 +30,7 @@ struct TsPart { |
|
|
|
pub(super) struct M3u8Download {
|
|
|
|
index_uri: Uri,
|
|
|
|
ts_parts: Vec<TsPart>,
|
|
|
|
time_wait: Duration
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -53,7 +54,18 @@ impl TsPart { |
|
|
|
self.content_type = content_type;
|
|
|
|
debug!("DONE TsPart: {:?}", self);
|
|
|
|
},
|
|
|
|
_ => self.state = TsState::Failed,
|
|
|
|
|
|
|
|
Err(error) if error.status_code() == Some(StatusCode::SERVICE_UNAVAILABLE) => {
|
|
|
|
self.state = TsState::Failed(Some(Instant::now()));
|
|
|
|
self.content_type =
|
|
|
|
error.action.state_ref().content_type().cloned();
|
|
|
|
debug!("FAILED WAIT on 503 TsPart: {:?}", self);
|
|
|
|
},
|
|
|
|
|
|
|
|
_ => {
|
|
|
|
self.state = TsState::Failed(None);
|
|
|
|
debug!("FAILED no wait TsPart: {:?}", self);
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
self
|
|
|
|
@ -75,6 +87,7 @@ impl M3u8Download { |
|
|
|
. to_string();
|
|
|
|
|
|
|
|
let mut ts_parts = vec![];
|
|
|
|
let time_wait = Duration::from_secs(301);
|
|
|
|
|
|
|
|
match m3u8_rs::parse_playlist(&m3u8_data) {
|
|
|
|
Result::Err(e) => Err(anyhow!("m3u8 parse error: {}", e))?,
|
|
|
|
@ -91,7 +104,7 @@ impl M3u8Download { |
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(Self {index_uri, ts_parts})
|
|
|
|
Ok(Self {index_uri, ts_parts, time_wait})
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(super) fn index_uri(&self) -> &Uri {
|
|
|
|
@ -100,22 +113,47 @@ impl M3u8Download { |
|
|
|
|
|
|
|
pub(super) async fn download(&mut self, client: &ClientActorHandle) {
|
|
|
|
loop {
|
|
|
|
let unfinished: Vec<_> = self.ts_parts.iter_mut()
|
|
|
|
. filter_map(|ts_part| match ts_part.state {
|
|
|
|
TsState::Ready => if ts_part.content_type != Some("video/MP2T".to_string()) {
|
|
|
|
Some(ts_part.download(client))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
_ => Some(ts_part.download(client))
|
|
|
|
}).collect();
|
|
|
|
|
|
|
|
debug!("UNFINISHED NOW: {}", unfinished.len());
|
|
|
|
|
|
|
|
if unfinished.is_empty() { break; }
|
|
|
|
let mut non_waits = vec![];
|
|
|
|
let mut waits = vec![];
|
|
|
|
|
|
|
|
debug!("SHOW ALL remaining TsParts:");
|
|
|
|
for ts_part in self.ts_parts.iter_mut() {
|
|
|
|
debug!("TsParts: {:?}", ts_part);
|
|
|
|
match ts_part.state {
|
|
|
|
TsState::Ready if ts_part.content_type != Some("video/MP2T".into()) => {
|
|
|
|
non_waits.push(ts_part.download(client));
|
|
|
|
},
|
|
|
|
|
|
|
|
TsState::Ready => (),
|
|
|
|
|
|
|
|
TsState::Failed(Some(wait_from)) => {
|
|
|
|
if Instant::now() >= wait_from + self.time_wait {
|
|
|
|
non_waits.push(ts_part.download(client));
|
|
|
|
} else {
|
|
|
|
waits.push((wait_from + self.time_wait) - Instant::now());
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_ => non_waits.push(ts_part.download(client)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
join_all(unfinished).await;
|
|
|
|
debug!("UNFINISHED NOW: {}", non_waits.len() + waits.len());
|
|
|
|
|
|
|
|
if non_waits.is_empty() {
|
|
|
|
if waits.is_empty() {
|
|
|
|
break
|
|
|
|
} else {
|
|
|
|
info!("All {} tasks wait for unavailable service", waits.len());
|
|
|
|
let pause_time = waits
|
|
|
|
. into_iter()
|
|
|
|
. fold(self.time_wait, |a, w| w.min(a));
|
|
|
|
info!("Sleep for {:?}", pause_time);
|
|
|
|
sleep(pause_time).await;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
join_all(non_waits).await;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|