use serde::{Deserialize, Serialize};
use crate::types::VideoCodec;
use super::profile::QualityScores;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum DeviceType {
Desktop,
Laptop,
Mobile,
TV,
Embedded,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum NetworkQuality {
Unlimited,
Broadband,
Limited,
Offline,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClientContext {
pub device_type: DeviceType,
pub network: NetworkQuality,
pub hw_decode_codecs: Vec<VideoCodec>,
}
impl Default for ClientContext {
fn default() -> Self {
Self {
device_type: DeviceType::Desktop,
network: NetworkQuality::Unlimited,
hw_decode_codecs: vec![VideoCodec::H264, VideoCodec::HEVC],
}
}
}
impl ClientContext {
#[must_use]
pub fn adjust_score(
&self,
mut scores: QualityScores,
file_video_codec: Option<VideoCodec>,
) -> QualityScores {
if let Some(ref mut res_score) = scores.resolution {
let multiplier = self.resolution_multiplier(*res_score);
*res_score *= multiplier;
}
let network_mult = self.network_multiplier();
if let Some(ref mut res) = scores.resolution {
*res *= network_mult;
}
if let Some(ref mut vc) = scores.video_codec {
*vc *= network_mult;
}
if let Some(codec) = file_video_codec
&& !self.hw_decode_codecs.contains(&codec)
{
scores.video_codec = scores.video_codec.map(|s| s * 0.1);
}
scores
}
fn resolution_multiplier(&self, res_score: f32) -> f32 {
match self.device_type {
DeviceType::Desktop | DeviceType::TV => 1.0,
DeviceType::Laptop => {
if res_score > 0.9 {
0.85
} else {
1.0
}
}
DeviceType::Mobile => {
if res_score > 0.6 {
0.6
} else {
1.0
}
}
DeviceType::Embedded => {
if res_score > 0.5 {
0.5
} else {
1.0
}
}
}
}
fn network_multiplier(&self) -> f32 {
match self.network {
NetworkQuality::Unlimited => 1.0,
NetworkQuality::Broadband => 0.9,
NetworkQuality::Limited => 0.3,
NetworkQuality::Offline => 1.0, }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{AudioCodec, MediaSource, Resolution};
fn make_scores(
resolution: Option<Resolution>,
video_codec: Option<VideoCodec>,
) -> QualityScores {
QualityScores::from_metadata(
resolution,
video_codec,
Some(AudioCodec::AAC),
Some(MediaSource::WebDL),
0.7,
)
}
#[test]
fn desktop_unlimited_no_penalty() {
let ctx = ClientContext::default();
let scores = make_scores(Some(Resolution::UHD2160), Some(VideoCodec::H264));
let adjusted = ctx.adjust_score(scores.clone(), Some(VideoCodec::H264));
assert_eq!(adjusted.resolution, scores.resolution);
}
#[test]
fn mobile_penalizes_high_resolution() {
let ctx = ClientContext {
device_type: DeviceType::Mobile,
network: NetworkQuality::Unlimited,
hw_decode_codecs: vec![VideoCodec::H264, VideoCodec::HEVC],
};
let scores = make_scores(Some(Resolution::FHD1080), Some(VideoCodec::H264));
let adjusted = ctx.adjust_score(scores, Some(VideoCodec::H264));
let expected = 0.85 * 0.6;
assert!(
(adjusted.resolution.unwrap() - expected).abs() < 0.001,
"got {}, expected {}",
adjusted.resolution.unwrap(),
expected
);
}
#[test]
fn limited_network_penalizes_all() {
let ctx = ClientContext {
device_type: DeviceType::Desktop,
network: NetworkQuality::Limited,
hw_decode_codecs: vec![VideoCodec::H264],
};
let scores = make_scores(Some(Resolution::FHD1080), Some(VideoCodec::H264));
let adjusted = ctx.adjust_score(scores, Some(VideoCodec::H264));
let expected_res = 0.85 * 0.3;
assert!((adjusted.resolution.unwrap() - expected_res).abs() < 0.001);
}
#[test]
fn unsupported_codec_massive_penalty() {
let ctx = ClientContext {
device_type: DeviceType::Desktop,
network: NetworkQuality::Unlimited,
hw_decode_codecs: vec![VideoCodec::H264], };
let scores = make_scores(Some(Resolution::FHD1080), Some(VideoCodec::AV1));
let adjusted = ctx.adjust_score(scores, Some(VideoCodec::AV1));
assert!((adjusted.video_codec.unwrap() - 0.1).abs() < 0.001);
}
#[test]
fn default_context_is_desktop_unlimited() {
let ctx = ClientContext::default();
assert_eq!(ctx.device_type, DeviceType::Desktop);
assert_eq!(ctx.network, NetworkQuality::Unlimited);
assert!(ctx.hw_decode_codecs.contains(&VideoCodec::H264));
assert!(ctx.hw_decode_codecs.contains(&VideoCodec::HEVC));
}
}