Merge branch 'new-infinit'
This commit is contained in:
commit
66b4526a48
@ -11,9 +11,11 @@ use serde_json::json;
|
|||||||
use simplelog::*;
|
use simplelog::*;
|
||||||
|
|
||||||
use ffplayout_lib::utils::{
|
use ffplayout_lib::utils::{
|
||||||
controller::PlayerControl, gen_dummy, get_delta, is_close, is_remote,
|
controller::PlayerControl,
|
||||||
json_serializer::read_json, loop_filler, loop_image, modified_time, seek_and_length,
|
gen_dummy, get_delta, is_close, is_remote,
|
||||||
time_in_seconds, Media, MediaProbe, PlayoutConfig, PlayoutStatus, IMAGE_FORMAT,
|
json_serializer::{read_json, set_defaults},
|
||||||
|
loop_filler, loop_image, modified_time, seek_and_length, time_in_seconds, JsonPlaylist, Media,
|
||||||
|
MediaProbe, PlayoutConfig, PlayoutStatus, IMAGE_FORMAT,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Struct for current playlist.
|
/// Struct for current playlist.
|
||||||
@ -24,9 +26,7 @@ pub struct CurrentProgram {
|
|||||||
config: PlayoutConfig,
|
config: PlayoutConfig,
|
||||||
start_sec: f64,
|
start_sec: f64,
|
||||||
end_sec: f64,
|
end_sec: f64,
|
||||||
json_mod: Option<String>,
|
json_playlist: JsonPlaylist,
|
||||||
json_path: Option<String>,
|
|
||||||
json_date: String,
|
|
||||||
player_control: PlayerControl,
|
player_control: PlayerControl,
|
||||||
current_node: Media,
|
current_node: Media,
|
||||||
is_terminated: Arc<AtomicBool>,
|
is_terminated: Arc<AtomicBool>,
|
||||||
@ -47,9 +47,10 @@ impl CurrentProgram {
|
|||||||
config: config.clone(),
|
config: config.clone(),
|
||||||
start_sec: config.playlist.start_sec.unwrap(),
|
start_sec: config.playlist.start_sec.unwrap(),
|
||||||
end_sec: config.playlist.length_sec.unwrap(),
|
end_sec: config.playlist.length_sec.unwrap(),
|
||||||
json_mod: None,
|
json_playlist: JsonPlaylist::new(
|
||||||
json_path: None,
|
"1970-01-01".to_string(),
|
||||||
json_date: String::new(),
|
config.playlist.start_sec.unwrap(),
|
||||||
|
),
|
||||||
player_control: player_control.clone(),
|
player_control: player_control.clone(),
|
||||||
current_node: Media::new(0, "", false),
|
current_node: Media::new(0, "", false),
|
||||||
is_terminated,
|
is_terminated,
|
||||||
@ -65,9 +66,9 @@ impl CurrentProgram {
|
|||||||
let mut get_current = false;
|
let mut get_current = false;
|
||||||
let mut reload = false;
|
let mut reload = false;
|
||||||
|
|
||||||
if let Some(path) = self.json_path.clone() {
|
if let Some(path) = self.json_playlist.path.clone() {
|
||||||
if (Path::new(&path).is_file() || is_remote(&path))
|
if (Path::new(&path).is_file() || is_remote(&path))
|
||||||
&& self.json_mod != modified_time(&path)
|
&& self.json_playlist.modified != modified_time(&path)
|
||||||
{
|
{
|
||||||
info!("Reload playlist <b><magenta>{path}</></b>");
|
info!("Reload playlist <b><magenta>{path}</></b>");
|
||||||
self.playout_stat.list_init.store(true, Ordering::SeqCst);
|
self.playout_stat.list_init.store(true, Ordering::SeqCst);
|
||||||
@ -79,26 +80,24 @@ impl CurrentProgram {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if get_current {
|
if get_current {
|
||||||
let json = read_json(
|
self.json_playlist = read_json(
|
||||||
&self.config,
|
&mut self.config,
|
||||||
&self.player_control,
|
&self.player_control,
|
||||||
self.json_path.clone(),
|
self.json_playlist.path.clone(),
|
||||||
self.is_terminated.clone(),
|
self.is_terminated.clone(),
|
||||||
seek,
|
seek,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
if !reload {
|
if !reload {
|
||||||
if let Some(file) = &json.current_file {
|
if let Some(file) = &self.json_playlist.path {
|
||||||
info!("Read playlist: <b><magenta>{file}</></b>");
|
info!("Read playlist: <b><magenta>{file}</></b>");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.json_path = json.current_file;
|
*self.player_control.current_list.lock().unwrap() = self.json_playlist.program.clone();
|
||||||
self.json_mod = json.modified;
|
|
||||||
*self.player_control.current_list.lock().unwrap() = json.program;
|
|
||||||
|
|
||||||
if self.json_path.is_none() {
|
if self.json_playlist.path.is_none() {
|
||||||
trace!("missing playlist");
|
trace!("missing playlist");
|
||||||
|
|
||||||
self.current_node = Media::new(0, "", false);
|
self.current_node = Media::new(0, "", false);
|
||||||
@ -137,15 +136,16 @@ impl CurrentProgram {
|
|||||||
trace!("next_start: {next_start}, end_sec: {}", self.end_sec);
|
trace!("next_start: {next_start}, end_sec: {}", self.end_sec);
|
||||||
|
|
||||||
// Check if we over the target length or we are close to it, if so we load the next playlist.
|
// Check if we over the target length or we are close to it, if so we load the next playlist.
|
||||||
if next_start >= self.end_sec
|
if !self.config.playlist.infinit
|
||||||
|
&& (next_start >= self.end_sec
|
||||||
|| is_close(total_delta, 0.0, 2.0)
|
|| is_close(total_delta, 0.0, 2.0)
|
||||||
|| is_close(total_delta, self.end_sec, 2.0)
|
|| is_close(total_delta, self.end_sec, 2.0))
|
||||||
{
|
{
|
||||||
trace!("get next day");
|
trace!("get next day");
|
||||||
next = true;
|
next = true;
|
||||||
|
|
||||||
let json = read_json(
|
self.json_playlist = read_json(
|
||||||
&self.config,
|
&mut self.config,
|
||||||
&self.player_control,
|
&self.player_control,
|
||||||
None,
|
None,
|
||||||
self.is_terminated.clone(),
|
self.is_terminated.clone(),
|
||||||
@ -153,18 +153,14 @@ impl CurrentProgram {
|
|||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(file) = &json.current_file {
|
if let Some(file) = &self.json_playlist.path {
|
||||||
info!("Read next playlist: <b><magenta>{file}</></b>");
|
info!("Read next playlist: <b><magenta>{file}</></b>");
|
||||||
}
|
}
|
||||||
|
|
||||||
self.playout_stat.list_init.store(false, Ordering::SeqCst);
|
self.playout_stat.list_init.store(false, Ordering::SeqCst);
|
||||||
self.set_status(json.date.clone());
|
self.set_status(self.json_playlist.date.clone());
|
||||||
|
|
||||||
self.json_path = json.current_file.clone();
|
*self.player_control.current_list.lock().unwrap() = self.json_playlist.program.clone();
|
||||||
self.json_mod = json.modified;
|
|
||||||
self.json_date = json.date;
|
|
||||||
|
|
||||||
*self.player_control.current_list.lock().unwrap() = json.program;
|
|
||||||
self.player_control.current_index.store(0, Ordering::SeqCst);
|
self.player_control.current_index.store(0, Ordering::SeqCst);
|
||||||
} else {
|
} else {
|
||||||
self.load_or_update_playlist(seek)
|
self.load_or_update_playlist(seek)
|
||||||
@ -212,7 +208,7 @@ impl CurrentProgram {
|
|||||||
let mut time_sec = time_in_seconds();
|
let mut time_sec = time_in_seconds();
|
||||||
|
|
||||||
if time_sec < self.start_sec {
|
if time_sec < self.start_sec {
|
||||||
time_sec += self.config.playlist.length_sec.unwrap()
|
time_sec += 86400.0 // self.config.playlist.length_sec.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
time_sec
|
time_sec
|
||||||
@ -231,6 +227,15 @@ impl CurrentProgram {
|
|||||||
time_sec += *shift;
|
time_sec += *shift;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
drop(shift);
|
||||||
|
|
||||||
|
if self.config.playlist.infinit
|
||||||
|
&& self.json_playlist.length.unwrap() < 86400.0
|
||||||
|
&& time_sec > self.json_playlist.length.unwrap() + self.start_sec
|
||||||
|
{
|
||||||
|
self.recalculate_begin(true)
|
||||||
|
}
|
||||||
|
|
||||||
for (i, item) in self
|
for (i, item) in self
|
||||||
.player_control
|
.player_control
|
||||||
.current_list
|
.current_list
|
||||||
@ -266,14 +271,14 @@ impl CurrentProgram {
|
|||||||
// de-instance node to preserve original values in list
|
// de-instance node to preserve original values in list
|
||||||
let mut node_clone = nodes[index].clone();
|
let mut node_clone = nodes[index].clone();
|
||||||
|
|
||||||
|
// Important! When no manual drop is happen here, lock is still active in handle_list_init
|
||||||
|
drop(nodes);
|
||||||
|
|
||||||
trace!("Clip from init: {}", node_clone.source);
|
trace!("Clip from init: {}", node_clone.source);
|
||||||
|
|
||||||
node_clone.seek += time_sec
|
node_clone.seek += time_sec
|
||||||
- (node_clone.begin.unwrap() - *self.playout_stat.time_shift.lock().unwrap());
|
- (node_clone.begin.unwrap() - *self.playout_stat.time_shift.lock().unwrap());
|
||||||
|
|
||||||
// Important! When no manual drop is happen here, lock is still active in handle_list_init
|
|
||||||
drop(nodes);
|
|
||||||
|
|
||||||
self.current_node = handle_list_init(
|
self.current_node = handle_list_init(
|
||||||
&self.config,
|
&self.config,
|
||||||
node_clone,
|
node_clone,
|
||||||
@ -297,7 +302,6 @@ impl CurrentProgram {
|
|||||||
|
|
||||||
fn fill_end(&mut self, total_delta: f64) {
|
fn fill_end(&mut self, total_delta: f64) {
|
||||||
// Fill end from playlist
|
// Fill end from playlist
|
||||||
|
|
||||||
let index = self.player_control.current_index.load(Ordering::SeqCst);
|
let index = self.player_control.current_index.load(Ordering::SeqCst);
|
||||||
let mut media = Media::new(index, "", false);
|
let mut media = Media::new(index, "", false);
|
||||||
media.begin = Some(time_in_seconds());
|
media.begin = Some(time_in_seconds());
|
||||||
@ -327,6 +331,20 @@ impl CurrentProgram {
|
|||||||
.current_index
|
.current_index
|
||||||
.fetch_add(1, Ordering::SeqCst);
|
.fetch_add(1, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn recalculate_begin(&mut self, extend: bool) {
|
||||||
|
debug!("Infinit playlist reaches end, recalculate clip begins.");
|
||||||
|
|
||||||
|
let mut time_sec = time_in_seconds();
|
||||||
|
|
||||||
|
if extend {
|
||||||
|
time_sec = self.start_sec + self.json_playlist.length.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.json_playlist.start_sec = Some(time_sec);
|
||||||
|
set_defaults(&mut self.json_playlist);
|
||||||
|
*self.player_control.current_list.lock().unwrap() = self.json_playlist.program.clone();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build the playlist iterator
|
/// Build the playlist iterator
|
||||||
@ -334,7 +352,7 @@ impl Iterator for CurrentProgram {
|
|||||||
type Item = Media;
|
type Item = Media;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
self.last_json_path = self.json_path.clone();
|
self.last_json_path = self.json_playlist.path.clone();
|
||||||
self.last_node_ad = self.current_node.last_ad;
|
self.last_node_ad = self.current_node.last_ad;
|
||||||
self.check_for_playlist(self.playout_stat.list_init.load(Ordering::SeqCst));
|
self.check_for_playlist(self.playout_stat.list_init.load(Ordering::SeqCst));
|
||||||
|
|
||||||
@ -342,7 +360,7 @@ impl Iterator for CurrentProgram {
|
|||||||
trace!("Init playlist, from next iterator");
|
trace!("Init playlist, from next iterator");
|
||||||
let mut init_clip_is_filler = false;
|
let mut init_clip_is_filler = false;
|
||||||
|
|
||||||
if self.json_path.is_some() {
|
if self.json_playlist.path.is_some() {
|
||||||
init_clip_is_filler = self.init_clip();
|
init_clip_is_filler = self.init_clip();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -420,7 +438,7 @@ impl Iterator for CurrentProgram {
|
|||||||
let (_, total_delta) = get_delta(&self.config, &self.start_sec);
|
let (_, total_delta) = get_delta(&self.config, &self.start_sec);
|
||||||
|
|
||||||
if !self.config.playlist.infinit
|
if !self.config.playlist.infinit
|
||||||
&& self.last_json_path == self.json_path
|
&& self.last_json_path == self.json_playlist.path
|
||||||
&& total_delta.abs() > 1.0
|
&& total_delta.abs() > 1.0
|
||||||
{
|
{
|
||||||
// Playlist is to early finish,
|
// Playlist is to early finish,
|
||||||
@ -438,6 +456,10 @@ impl Iterator for CurrentProgram {
|
|||||||
|
|
||||||
drop(c_list);
|
drop(c_list);
|
||||||
|
|
||||||
|
if self.config.playlist.infinit {
|
||||||
|
self.recalculate_begin(false)
|
||||||
|
}
|
||||||
|
|
||||||
self.player_control.current_index.store(0, Ordering::SeqCst);
|
self.player_control.current_index.store(0, Ordering::SeqCst);
|
||||||
self.current_node = gen_source(
|
self.current_node = gen_source(
|
||||||
&self.config,
|
&self.config,
|
||||||
@ -502,6 +524,7 @@ fn timed_source(
|
|||||||
if (total_delta > node.out - node.seek && !last)
|
if (total_delta > node.out - node.seek && !last)
|
||||||
|| node.index.unwrap() < 2
|
|| node.index.unwrap() < 2
|
||||||
|| !config.playlist.length.contains(':')
|
|| !config.playlist.length.contains(':')
|
||||||
|
|| config.playlist.infinit
|
||||||
{
|
{
|
||||||
// when we are in the 24 hour range, get the clip
|
// when we are in the 24 hour range, get the clip
|
||||||
new_node.process = Some(true);
|
new_node.process = Some(true);
|
||||||
@ -742,7 +765,7 @@ fn handle_list_init(
|
|||||||
debug!("Playlist init");
|
debug!("Playlist init");
|
||||||
let (_, total_delta) = get_delta(config, &node.begin.unwrap());
|
let (_, total_delta) = get_delta(config, &node.begin.unwrap());
|
||||||
|
|
||||||
if node.out - node.seek > total_delta {
|
if !config.playlist.infinit && node.out - node.seek > total_delta {
|
||||||
node.out = total_delta + node.seek;
|
node.out = total_delta + node.seek;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,8 +107,18 @@ pub fn player(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let c_index = if cfg!(debug_assertions) {
|
||||||
|
format!(
|
||||||
|
" ({}/{})",
|
||||||
|
node.index.unwrap() + 1,
|
||||||
|
play_control.current_list.lock().unwrap().len()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
"Play for <yellow>{}</>: <b><magenta>{} {}</></b>",
|
"Play for <yellow>{}</>{c_index}: <b><magenta>{} {}</></b>",
|
||||||
sec_to_time(node.out - node.seek),
|
sec_to_time(node.out - node.seek),
|
||||||
node.source,
|
node.source,
|
||||||
node.audio
|
node.audio
|
||||||
|
@ -272,8 +272,9 @@ pub fn generate_playlist(
|
|||||||
let mut playlist = JsonPlaylist {
|
let mut playlist = JsonPlaylist {
|
||||||
channel: channel.clone(),
|
channel: channel.clone(),
|
||||||
date,
|
date,
|
||||||
current_file: None,
|
path: None,
|
||||||
start_sec: None,
|
start_sec: None,
|
||||||
|
length: None,
|
||||||
modified: None,
|
modified: None,
|
||||||
program: vec![],
|
program: vec![],
|
||||||
};
|
};
|
||||||
|
@ -19,8 +19,9 @@ pub fn import_file(
|
|||||||
let mut playlist = JsonPlaylist {
|
let mut playlist = JsonPlaylist {
|
||||||
channel: channel_name.unwrap_or_else(|| "Channel 1".to_string()),
|
channel: channel_name.unwrap_or_else(|| "Channel 1".to_string()),
|
||||||
date: date.to_string(),
|
date: date.to_string(),
|
||||||
current_file: None,
|
path: None,
|
||||||
start_sec: None,
|
start_sec: None,
|
||||||
|
length: None,
|
||||||
modified: None,
|
modified: None,
|
||||||
program: vec![],
|
program: vec![],
|
||||||
};
|
};
|
||||||
|
@ -9,8 +9,8 @@ use std::{
|
|||||||
use simplelog::*;
|
use simplelog::*;
|
||||||
|
|
||||||
use crate::utils::{
|
use crate::utils::{
|
||||||
controller::ProcessUnit::*, get_date, is_remote, modified_time, time_from_header,
|
get_date, is_remote, modified_time, time_from_header, validate_playlist, Media, PlayerControl,
|
||||||
validate_playlist, Media, PlayerControl, PlayoutConfig, DUMMY_LEN,
|
PlayoutConfig, DUMMY_LEN,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// This is our main playlist object, it holds all necessary information for the current day.
|
/// This is our main playlist object, it holds all necessary information for the current day.
|
||||||
@ -24,7 +24,10 @@ pub struct JsonPlaylist {
|
|||||||
pub start_sec: Option<f64>,
|
pub start_sec: Option<f64>,
|
||||||
|
|
||||||
#[serde(skip_serializing, skip_deserializing)]
|
#[serde(skip_serializing, skip_deserializing)]
|
||||||
pub current_file: Option<String>,
|
pub length: Option<f64>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing, skip_deserializing)]
|
||||||
|
pub path: Option<String>,
|
||||||
|
|
||||||
#[serde(skip_serializing, skip_deserializing)]
|
#[serde(skip_serializing, skip_deserializing)]
|
||||||
pub modified: Option<String>,
|
pub modified: Option<String>,
|
||||||
@ -33,7 +36,7 @@ pub struct JsonPlaylist {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl JsonPlaylist {
|
impl JsonPlaylist {
|
||||||
fn new(date: String, start: f64) -> Self {
|
pub fn new(date: String, start: f64) -> Self {
|
||||||
let mut media = Media::new(0, "", false);
|
let mut media = Media::new(0, "", false);
|
||||||
media.begin = Some(start);
|
media.begin = Some(start);
|
||||||
media.duration = DUMMY_LEN;
|
media.duration = DUMMY_LEN;
|
||||||
@ -42,7 +45,8 @@ impl JsonPlaylist {
|
|||||||
channel: "Channel 1".into(),
|
channel: "Channel 1".into(),
|
||||||
date,
|
date,
|
||||||
start_sec: Some(start),
|
start_sec: Some(start),
|
||||||
current_file: None,
|
length: Some(86400.0),
|
||||||
|
path: None,
|
||||||
modified: None,
|
modified: None,
|
||||||
program: vec![media],
|
program: vec![media],
|
||||||
}
|
}
|
||||||
@ -61,13 +65,9 @@ fn default_channel() -> String {
|
|||||||
"Channel 1".to_string()
|
"Channel 1".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_defaults(
|
pub fn set_defaults(playlist: &mut JsonPlaylist) {
|
||||||
mut playlist: JsonPlaylist,
|
let mut start_sec = playlist.start_sec.unwrap();
|
||||||
current_file: String,
|
let mut length = 0.0;
|
||||||
mut start_sec: f64,
|
|
||||||
) -> JsonPlaylist {
|
|
||||||
playlist.current_file = Some(current_file);
|
|
||||||
playlist.start_sec = Some(start_sec);
|
|
||||||
|
|
||||||
// Add extra values to every media clip
|
// Add extra values to every media clip
|
||||||
for (i, item) in playlist.program.iter_mut().enumerate() {
|
for (i, item) in playlist.program.iter_mut().enumerate() {
|
||||||
@ -78,69 +78,18 @@ fn set_defaults(
|
|||||||
item.process = Some(true);
|
item.process = Some(true);
|
||||||
item.filter = None;
|
item.filter = None;
|
||||||
|
|
||||||
start_sec += item.out - item.seek;
|
let dur = item.out - item.seek;
|
||||||
|
start_sec += dur;
|
||||||
|
length += dur;
|
||||||
}
|
}
|
||||||
|
|
||||||
playlist
|
playlist.length = Some(length)
|
||||||
}
|
|
||||||
|
|
||||||
fn loop_playlist(
|
|
||||||
config: &PlayoutConfig,
|
|
||||||
current_file: String,
|
|
||||||
mut playlist: JsonPlaylist,
|
|
||||||
) -> JsonPlaylist {
|
|
||||||
let start_sec = config.playlist.start_sec.unwrap();
|
|
||||||
let mut begin = start_sec;
|
|
||||||
let length = config.playlist.length_sec.unwrap();
|
|
||||||
let mut program_list = vec![];
|
|
||||||
let mut index = 0;
|
|
||||||
|
|
||||||
playlist.current_file = Some(current_file);
|
|
||||||
playlist.start_sec = Some(start_sec);
|
|
||||||
|
|
||||||
'program_looper: loop {
|
|
||||||
for item in playlist.program.iter() {
|
|
||||||
let media = Media {
|
|
||||||
index: Some(index),
|
|
||||||
begin: Some(begin),
|
|
||||||
seek: item.seek,
|
|
||||||
out: item.out,
|
|
||||||
duration: item.duration,
|
|
||||||
duration_audio: item.duration_audio,
|
|
||||||
category: item.category.clone(),
|
|
||||||
source: item.source.clone(),
|
|
||||||
audio: item.audio.clone(),
|
|
||||||
cmd: item.cmd.clone(),
|
|
||||||
probe: item.probe.clone(),
|
|
||||||
probe_audio: item.probe_audio.clone(),
|
|
||||||
process: Some(true),
|
|
||||||
unit: Decoder,
|
|
||||||
last_ad: false,
|
|
||||||
next_ad: false,
|
|
||||||
filter: None,
|
|
||||||
custom_filter: String::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if begin < start_sec + length {
|
|
||||||
program_list.push(media);
|
|
||||||
} else {
|
|
||||||
break 'program_looper;
|
|
||||||
}
|
|
||||||
|
|
||||||
begin += item.out - item.seek;
|
|
||||||
index += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
playlist.program = program_list;
|
|
||||||
|
|
||||||
playlist
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read json playlist file, fills JsonPlaylist struct and set some extra values,
|
/// Read json playlist file, fills JsonPlaylist struct and set some extra values,
|
||||||
/// which we need to process.
|
/// which we need to process.
|
||||||
pub fn read_json(
|
pub fn read_json(
|
||||||
config: &PlayoutConfig,
|
config: &mut PlayoutConfig,
|
||||||
player_control: &PlayerControl,
|
player_control: &PlayerControl,
|
||||||
path: Option<String>,
|
path: Option<String>,
|
||||||
is_terminated: Arc<AtomicBool>,
|
is_terminated: Arc<AtomicBool>,
|
||||||
@ -179,6 +128,8 @@ pub fn read_json(
|
|||||||
if let Ok(body) = resp.text() {
|
if let Ok(body) = resp.text() {
|
||||||
let mut playlist: JsonPlaylist =
|
let mut playlist: JsonPlaylist =
|
||||||
serde_json::from_str(&body).expect("Could't read remote json playlist.");
|
serde_json::from_str(&body).expect("Could't read remote json playlist.");
|
||||||
|
playlist.path = Some(current_file);
|
||||||
|
playlist.start_sec = Some(start_sec);
|
||||||
|
|
||||||
if let Some(time) = time_from_header(&headers) {
|
if let Some(time) = time_from_header(&headers) {
|
||||||
playlist.modified = Some(time.to_string());
|
playlist.modified = Some(time.to_string());
|
||||||
@ -197,10 +148,9 @@ pub fn read_json(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
match config.playlist.infinit {
|
set_defaults(&mut playlist);
|
||||||
true => return loop_playlist(config, current_file, playlist),
|
|
||||||
false => return set_defaults(playlist, current_file, start_sec),
|
return playlist;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -225,6 +175,8 @@ pub fn read_json(
|
|||||||
playlist = JsonPlaylist::new(date, start_sec)
|
playlist = JsonPlaylist::new(date, start_sec)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
playlist.path = Some(current_file);
|
||||||
|
playlist.start_sec = Some(start_sec);
|
||||||
playlist.modified = modified;
|
playlist.modified = modified;
|
||||||
|
|
||||||
let list_clone = playlist.clone();
|
let list_clone = playlist.clone();
|
||||||
@ -235,10 +187,9 @@ pub fn read_json(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
match config.playlist.infinit {
|
set_defaults(&mut playlist);
|
||||||
true => return loop_playlist(config, current_file, playlist),
|
|
||||||
false => return set_defaults(playlist, current_file, start_sec),
|
return playlist;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
error!("Playlist <b><magenta>{current_file}</></b> not exist!");
|
error!("Playlist <b><magenta>{current_file}</></b> not exist!");
|
||||||
|
@ -236,5 +236,16 @@ pub fn validate_playlist(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("Validation done, in {:.3?} ...", timer.elapsed(),);
|
if config.general.validate {
|
||||||
|
info!(
|
||||||
|
"[Validation] Playlist length: <yellow>{}</>",
|
||||||
|
sec_to_time(begin - config.playlist.start_sec.unwrap())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
"Validation done, in <yellow>{:.3?}</>, playlist length: <yellow>{}</> ...",
|
||||||
|
timer.elapsed(),
|
||||||
|
sec_to_time(begin - config.playlist.start_sec.unwrap())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ use std::{
|
|||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process::{exit, ChildStderr, Command, Stdio},
|
process::{exit, ChildStderr, Command, Stdio},
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
time::{self, UNIX_EPOCH},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
@ -427,11 +426,12 @@ pub fn time_to_sec(time_str: &str) -> f64 {
|
|||||||
|
|
||||||
/// Convert floating number (seconds) to a formatted time string.
|
/// Convert floating number (seconds) to a formatted time string.
|
||||||
pub fn sec_to_time(sec: f64) -> String {
|
pub fn sec_to_time(sec: f64) -> String {
|
||||||
let d = UNIX_EPOCH + time::Duration::from_millis((sec * 1000.0) as u64);
|
format!(
|
||||||
// Create DateTime from SystemTime
|
"{:0>2}:{:0>2}:{:06.3}",
|
||||||
let date_time = DateTime::<Utc>::from(d);
|
(sec / 60.0 / 60.0) as i32,
|
||||||
|
(sec / 60.0 % 60.0) as i32,
|
||||||
date_time.format("%H:%M:%S%.3f").to_string()
|
(sec % 60.0),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get file extension
|
/// get file extension
|
||||||
@ -463,22 +463,27 @@ pub fn sum_durations(clip_list: &Vec<Media>) -> f64 {
|
|||||||
pub fn get_delta(config: &PlayoutConfig, begin: &f64) -> (f64, f64) {
|
pub fn get_delta(config: &PlayoutConfig, begin: &f64) -> (f64, f64) {
|
||||||
let mut current_time = time_in_seconds();
|
let mut current_time = time_in_seconds();
|
||||||
let start = config.playlist.start_sec.unwrap();
|
let start = config.playlist.start_sec.unwrap();
|
||||||
let length = time_to_sec(&config.playlist.length);
|
let length = config.playlist.length_sec.unwrap_or(86400.0);
|
||||||
let mut target_length = 86400.0;
|
let mut target_length = 86400.0;
|
||||||
|
|
||||||
if length > 0.0 && length != target_length {
|
if length > 0.0 && length != target_length {
|
||||||
target_length = length
|
target_length = length
|
||||||
}
|
}
|
||||||
|
|
||||||
if begin == &start && start == 0.0 && 86400.0 - current_time < 4.0 {
|
if begin == &start && start == 0.0 && 86400.0 - current_time < 4.0 {
|
||||||
current_time -= target_length
|
current_time -= 86400.0
|
||||||
} else if start >= current_time && begin != &start {
|
} else if start >= current_time && begin != &start {
|
||||||
current_time += target_length
|
current_time += 86400.0
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut current_delta = begin - current_time;
|
let mut current_delta = begin - current_time;
|
||||||
|
|
||||||
if is_close(current_delta, 86400.0, config.general.stop_threshold) {
|
if is_close(
|
||||||
current_delta -= 86400.0
|
current_delta.abs(),
|
||||||
|
86400.0,
|
||||||
|
config.general.stop_threshold + 2.0,
|
||||||
|
) {
|
||||||
|
current_delta = current_delta.abs() - 86400.0
|
||||||
}
|
}
|
||||||
|
|
||||||
let total_delta = if current_time < start {
|
let total_delta = if current_time < start {
|
||||||
|
288
tests/assets/playlists/2024/03/2024-03-19.json
Normal file
288
tests/assets/playlists/2024/03/2024-03-19.json
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
{
|
||||||
|
"channel": "Test 1",
|
||||||
|
"date": "2024-02-01",
|
||||||
|
"program": [
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 10.0,
|
||||||
|
"duration": 10.0,
|
||||||
|
"source": "tests/assets/media_sorted/DarkGray_00-00-10.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 30.0,
|
||||||
|
"duration": 30.0,
|
||||||
|
"source": "tests/assets/media_sorted/Olive_00-00-30.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 15.0,
|
||||||
|
"duration": 15.0,
|
||||||
|
"source": "tests/assets/media_sorted/Indigo_00-00-15.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 25.0,
|
||||||
|
"duration": 25.0,
|
||||||
|
"source": "tests/assets/media_sorted/DarkOrchid_00-00-25.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 45.0,
|
||||||
|
"duration": 45.0,
|
||||||
|
"source": "tests/assets/media_sorted/Orange_00-00-45.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 20.0,
|
||||||
|
"duration": 20.0,
|
||||||
|
"source": "tests/assets/media_sorted/LightGoldenRodYellow_00-00-20.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 30.0,
|
||||||
|
"duration": 30.0,
|
||||||
|
"source": "tests/assets/media_sorted/Cyan_00-00-30.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 50.0,
|
||||||
|
"duration": 50.0,
|
||||||
|
"source": "tests/assets/media_sorted/Cornsilk_00-00-50.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 15.0,
|
||||||
|
"duration": 15.0,
|
||||||
|
"source": "tests/assets/media_sorted/LightSeaGreen_00-00-15.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 30.0,
|
||||||
|
"duration": 30.0,
|
||||||
|
"source": "tests/assets/media_sorted/Yellow_00-00-30.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 30.0,
|
||||||
|
"duration": 30.0,
|
||||||
|
"source": "tests/assets/media_sorted/Aqua_00-00-30.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 1500.0,
|
||||||
|
"duration": 1500.0,
|
||||||
|
"source": "tests/assets/media_sorted/MediumSeaGreen_00-25-00.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 1800.0,
|
||||||
|
"duration": 1800.0,
|
||||||
|
"source": "tests/assets/media_sorted/MediumOrchid_00-30-00.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 3600.0,
|
||||||
|
"duration": 3600.0,
|
||||||
|
"source": "tests/assets/media_sorted/IndianRed_01-00-00.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 3600.0,
|
||||||
|
"duration": 3600.0,
|
||||||
|
"source": "tests/assets/media_sorted/ForestGreen_01-00-00.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 3600.0,
|
||||||
|
"duration": 3600.0,
|
||||||
|
"source": "tests/assets/media_sorted/Plum_01-00-00.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 3600.0,
|
||||||
|
"duration": 3600.0,
|
||||||
|
"source": "tests/assets/media_sorted/IndianRed_01-00-00.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 3600.0,
|
||||||
|
"duration": 3600.0,
|
||||||
|
"source": "tests/assets/media_sorted/ForestGreen_01-00-00.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 3600.0,
|
||||||
|
"duration": 3600.0,
|
||||||
|
"source": "tests/assets/media_sorted/Plum_01-00-00.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 3600.0,
|
||||||
|
"duration": 3600.0,
|
||||||
|
"source": "tests/assets/media_sorted/IndianRed_01-00-00.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 3600.0,
|
||||||
|
"duration": 3600.0,
|
||||||
|
"source": "tests/assets/media_sorted/ForestGreen_01-00-00.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 3600.0,
|
||||||
|
"duration": 3600.0,
|
||||||
|
"source": "tests/assets/media_sorted/Plum_01-00-00.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 3600.0,
|
||||||
|
"duration": 3600.0,
|
||||||
|
"source": "tests/assets/media_sorted/IndianRed_01-00-00.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 3600.0,
|
||||||
|
"duration": 3600.0,
|
||||||
|
"source": "tests/assets/media_sorted/ForestGreen_01-00-00.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 3600.0,
|
||||||
|
"duration": 3600.0,
|
||||||
|
"source": "tests/assets/media_sorted/Plum_01-00-00.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 3600.0,
|
||||||
|
"duration": 3600.0,
|
||||||
|
"source": "tests/assets/media_sorted/IndianRed_01-00-00.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 3600.0,
|
||||||
|
"duration": 3600.0,
|
||||||
|
"source": "tests/assets/media_sorted/ForestGreen_01-00-00.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 3600.0,
|
||||||
|
"duration": 3600.0,
|
||||||
|
"source": "tests/assets/media_sorted/Plum_01-00-00.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 3600.0,
|
||||||
|
"duration": 3600.0,
|
||||||
|
"source": "tests/assets/media_sorted/IndianRed_01-00-00.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 3600.0,
|
||||||
|
"duration": 3600.0,
|
||||||
|
"source": "tests/assets/media_sorted/ForestGreen_01-00-00.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 3600.0,
|
||||||
|
"duration": 3600.0,
|
||||||
|
"source": "tests/assets/media_sorted/Plum_01-00-00.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 3600.0,
|
||||||
|
"duration": 3600.0,
|
||||||
|
"source": "tests/assets/media_sorted/IndianRed_01-00-00.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 3600.0,
|
||||||
|
"duration": 3600.0,
|
||||||
|
"source": "tests/assets/media_sorted/ForestGreen_01-00-00.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 3600.0,
|
||||||
|
"duration": 3600.0,
|
||||||
|
"source": "tests/assets/media_sorted/Plum_01-00-00.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 3600.0,
|
||||||
|
"duration": 3600.0,
|
||||||
|
"source": "tests/assets/media_sorted/IndianRed_01-00-00.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 1800.0,
|
||||||
|
"duration": 1800.0,
|
||||||
|
"source": "tests/assets/media_sorted/MediumOrchid_00-30-00.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 1500.0,
|
||||||
|
"duration": 1500.0,
|
||||||
|
"source": "tests/assets/media_sorted/MediumSeaGreen_00-25-00.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 10.0,
|
||||||
|
"duration": 10.0,
|
||||||
|
"source": "tests/assets/media_sorted/DarkGray_00-00-10.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 30.0,
|
||||||
|
"duration": 30.0,
|
||||||
|
"source": "tests/assets/media_sorted/Olive_00-00-30.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 15.0,
|
||||||
|
"duration": 15.0,
|
||||||
|
"source": "tests/assets/media_sorted/Indigo_00-00-15.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 25.0,
|
||||||
|
"duration": 25.0,
|
||||||
|
"source": "tests/assets/media_sorted/DarkOrchid_00-00-25.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 45.0,
|
||||||
|
"duration": 45.0,
|
||||||
|
"source": "tests/assets/media_sorted/Orange_00-00-45.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 20.0,
|
||||||
|
"duration": 20.0,
|
||||||
|
"source": "tests/assets/media_sorted/LightGoldenRodYellow_00-00-20.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 30.0,
|
||||||
|
"duration": 30.0,
|
||||||
|
"source": "tests/assets/media_sorted/Cyan_00-00-30.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 50.0,
|
||||||
|
"duration": 50.0,
|
||||||
|
"source": "tests/assets/media_sorted/Cornsilk_00-00-50.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 15.0,
|
||||||
|
"duration": 15.0,
|
||||||
|
"source": "tests/assets/media_sorted/LightSeaGreen_00-00-15.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": 0,
|
||||||
|
"out": 30.0,
|
||||||
|
"duration": 30.0,
|
||||||
|
"source": "tests/assets/media_sorted/Yellow_00-00-30.mp4"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user