use url::Url;
use super::descriptors::SCHEME_URLPARAM;
use super::model::{AdaptationSet, BaseUrl, Descriptor, Mpd, Period, Representation, UrlQueryInfo};
use crate::limits::MAX_BASE_CANDIDATES;
pub const DEFAULT_BASE_PRIORITY: u32 = 1;
const QUERY_SEPARATOR: char = '&';
const QUERY_PART_TEMPLATE: &str = "$querypart$";
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct BaseCandidate {
pub chain: Vec<String>,
pub service_location: Option<String>,
pub priority: u32,
pub availability_time_offset_s: f64,
}
impl BaseCandidate {
fn root() -> Self {
Self {
chain: Vec::new(),
service_location: None,
priority: DEFAULT_BASE_PRIORITY,
availability_time_offset_s: 0.0,
}
}
#[must_use]
pub fn resolve(&self, document_url: Option<&Url>, reference: &str) -> Option<String> {
transmux::resolve_base_url_chain(document_url, &self.chain, reference)
}
}
#[must_use]
pub fn base_candidates(levels: &[&[BaseUrl]]) -> Vec<BaseCandidate> {
let mut candidates = vec![BaseCandidate::root()];
for level in levels.iter().filter(|l| !l.is_empty()) {
let mut ordered: Vec<&BaseUrl> = level.iter().collect();
ordered.sort_by_key(|b| b.priority.unwrap_or(DEFAULT_BASE_PRIORITY));
let mut next = Vec::with_capacity(ordered.len());
for b in ordered {
let parent = candidates
.iter()
.find(|c| c.service_location.is_some() && c.service_location == b.service_location)
.unwrap_or(&candidates[0]);
let mut chain = parent.chain.clone();
chain.push(b.url.clone());
next.push(BaseCandidate {
chain,
service_location: b.service_location.clone(),
priority: b.priority.unwrap_or(DEFAULT_BASE_PRIORITY),
availability_time_offset_s: parent.availability_time_offset_s
+ b.availability_time_offset.unwrap_or(0.0),
});
if next.len() >= MAX_BASE_CANDIDATES {
break;
}
}
candidates = next;
}
candidates
}
#[must_use]
pub fn representation_candidates(
mpd: &Mpd,
period: &Period,
set: &AdaptationSet,
rep: &Representation,
) -> Vec<BaseCandidate> {
base_candidates(&[
&mpd.base_urls,
&period.base_urls,
&set.base_urls,
&rep.base_urls,
])
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[error("urlparam descriptor uses a queryTemplate this core does not understand")]
#[non_exhaustive]
pub struct UrlQueryNotUnderstood;
fn level_query(
descriptors: &[&[Descriptor]],
mpd_query: Option<&str>,
out: &mut Vec<String>,
) -> Result<(), UrlQueryNotUnderstood> {
for list in descriptors {
for d in list.iter().filter(|d| d.scheme_id_uri == SCHEME_URLPARAM) {
let Some(info) = d.url_query.as_ref() else {
continue;
};
if let Some(t) = &info.query_template
&& t != QUERY_PART_TEMPLATE
{
return Err(UrlQueryNotUnderstood);
}
push_query(info, mpd_query, out);
}
}
Ok(())
}
fn push_query(info: &UrlQueryInfo, mpd_query: Option<&str>, out: &mut Vec<String>) {
let mut parts: Vec<&str> = Vec::new();
if info.use_mpd_url_query
&& let Some(q) = mpd_query.filter(|q| !q.is_empty())
{
parts.push(q);
}
if let Some(s) = info.query_string.as_deref().filter(|s| !s.is_empty()) {
parts.push(s);
}
if !parts.is_empty() {
out.push(parts.join(&QUERY_SEPARATOR.to_string()));
}
}
pub fn segment_url_query(
mpd: &Mpd,
period: &Period,
set: &AdaptationSet,
rep: &Representation,
mpd_query: Option<&str>,
) -> Result<Option<String>, UrlQueryNotUnderstood> {
let mut parts = Vec::new();
level_query(
&[&rep.essential_properties, &rep.supplemental_properties],
mpd_query,
&mut parts,
)?;
level_query(
&[&set.essential_properties, &set.supplemental_properties],
mpd_query,
&mut parts,
)?;
level_query(
&[
&period.essential_properties,
&period.supplemental_properties,
],
mpd_query,
&mut parts,
)?;
level_query(
&[&mpd.essential_properties, &mpd.supplemental_properties],
mpd_query,
&mut parts,
)?;
if parts.is_empty() {
Ok(None)
} else {
Ok(Some(parts.join(&QUERY_SEPARATOR.to_string())))
}
}
#[must_use]
pub fn append_query(url: &str, query: &str) -> String {
let (base, fragment) = match url.split_once('#') {
Some((b, f)) => (b, Some(f)),
None => (url, None),
};
let sep = if base.contains('?') {
QUERY_SEPARATOR
} else {
'?'
};
let mut out = format!("{base}{sep}{query}");
if let Some(f) = fragment {
out.push('#');
out.push_str(f);
}
out
}