fixes to time conversion and play head ux + improvements to file scanner
This commit is contained in:
@@ -18,12 +18,21 @@
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-rows: repeat(3, 1fr);
|
||||
aspect-ratio: 4/3;
|
||||
width: auto;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
video {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
image-rendering: smooth;
|
||||
aspect-ratio: 4/3;
|
||||
}
|
||||
|
||||
.emptyVideo {
|
||||
width: 100%;
|
||||
aspect-ratio: 16/9;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,23 +49,73 @@
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
#playhead {
|
||||
#playControls {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
grid-template-columns: $labelWidth 1fr;
|
||||
grid-template-areas: "Label Input";
|
||||
input {
|
||||
background-color: $featureBackroundDark;
|
||||
border-bottom: 1px solid $mainAccent;
|
||||
|
||||
.inputs {
|
||||
grid-area: Input;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
appearance: none;
|
||||
background: transparent;
|
||||
outline: none;
|
||||
/* opacity: 0; */
|
||||
|
||||
&::-webkit-slider-thumb {
|
||||
background-color: transparent;
|
||||
appearance: none;
|
||||
display: block;
|
||||
width: 1px;
|
||||
height: 10px;
|
||||
}
|
||||
&::-webkit-slider-runnable-track {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.indicator {
|
||||
.playHead {
|
||||
width: 1px;
|
||||
background-color: $secondaryAccentLight;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
height: 300px;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
z-index: 2;
|
||||
&::before {
|
||||
display: block;
|
||||
content: "";
|
||||
border: 11px solid transparent;
|
||||
border-top: 20px solid $secondaryAccentLight;
|
||||
transform: translateX(-11px);
|
||||
}
|
||||
}
|
||||
|
||||
.timelineMarkers {
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
.marker {
|
||||
position: absolute;
|
||||
border-left: 1px dashed #fff;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use chrono::DateTime;
|
||||
use chrono::{DateTime, Local, TimeZone};
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::rpc::azki::{MediaChannel, MediaEntry, PlaybackInfo};
|
||||
@@ -24,32 +24,39 @@ pub fn Timeline(playback_info: Option<PlaybackInfo>, zoom: Signal<f32>, time: Si
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn PlayHead(start: i64, time: Signal<i64>, zoom: Signal<f32>) -> Element
|
||||
fn PlayControls(start: i64, time: Signal<i64>, zoom: Signal<f32>) -> Element
|
||||
{
|
||||
let time_string = get_time_string(start, time());
|
||||
|
||||
let p = (time() as f32 / SEC_PER_DAY) * 100.0;
|
||||
rsx! {
|
||||
div {
|
||||
id: "playhead",
|
||||
input {
|
||||
style: "width: calc({zoom() * 100.0}%);",
|
||||
type: "range",
|
||||
value: "{time()}",
|
||||
max: "{SEC_PER_DAY}",
|
||||
oninput: move |e|{
|
||||
if let Ok(t) = e.value().parse() {
|
||||
time.set(t);
|
||||
}
|
||||
}
|
||||
},
|
||||
id: "playControls",
|
||||
style: "width: calc({zoom() * 100.0}%);",
|
||||
div{
|
||||
class: "time",
|
||||
"{time_string}"
|
||||
}
|
||||
div{
|
||||
class: "indicator",
|
||||
style: "left: {p}%"
|
||||
class: "inputs",
|
||||
input {
|
||||
type: "range",
|
||||
value: "{time()}",
|
||||
max: "{SEC_PER_DAY}",
|
||||
oninput: move |e|{
|
||||
if let Ok(t) = e.value().parse() {
|
||||
time.set(t);
|
||||
}
|
||||
}
|
||||
},
|
||||
div{
|
||||
class: "timelineMarkers",
|
||||
{(0..23).map(|i| rsx!{ div{ class: "marker", style: "left: {((i + 1) as f32/24.0) * 100.0}%;" } })}
|
||||
}
|
||||
div{
|
||||
class: "playHead",
|
||||
style: "left: {p}%"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,7 +68,7 @@ fn TrackList(channels: Vec<MediaChannel>, start: i64, zoom: Signal<f32>, time: S
|
||||
rsx! {
|
||||
div {
|
||||
id: "tracklist",
|
||||
PlayHead { start, time, zoom }
|
||||
PlayControls { start, time, zoom }
|
||||
{channels.iter().map(|c|rsx!{
|
||||
TimelineTrack { channel: c.clone(), start, zoom }
|
||||
})}
|
||||
@@ -102,7 +109,10 @@ fn Clip(media: MediaEntry, start: i64) -> Element
|
||||
{
|
||||
let meta = media.metadata.unwrap();
|
||||
let date = media.date.unwrap();
|
||||
let timestamp = date.seconds - start;
|
||||
// let timestamp = date.seconds - start;
|
||||
|
||||
let utc_date = DateTime::from_timestamp_secs(date.seconds).expect("Failed to convert clip date to utc");
|
||||
let timestamp = utc_date.timestamp() - start;
|
||||
let duration = (meta.duration as f32 / SEC_PER_DAY) * 100.0;
|
||||
let offset = (timestamp as f32 / SEC_PER_DAY) * 100.0;
|
||||
rsx! {
|
||||
@@ -116,6 +126,6 @@ fn Clip(media: MediaEntry, start: i64) -> Element
|
||||
fn get_time_string(start: i64, time: i64) -> String
|
||||
{
|
||||
let date_time = DateTime::from_timestamp_secs(start + time).expect("Failed to convert time");
|
||||
let local = date_time.naive_local();
|
||||
return local.to_string();
|
||||
let local: DateTime<Local> = DateTime::from(date_time);
|
||||
return format!("{}", local.format("%Y-%m-%d %H:%M"));
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use chrono::DateTime;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::{
|
||||
@@ -25,6 +26,7 @@ fn ViewportFull(playback_info: PlaybackInfo, time: Signal<i64>) -> Element
|
||||
id: "viewport",
|
||||
{playback_info.channels.iter().map(|c| {
|
||||
let entry = get_entry(c, start, cur_time);
|
||||
|
||||
rsx!{
|
||||
Video{ entry }
|
||||
}
|
||||
@@ -52,6 +54,7 @@ fn get_entry(channel: &MediaChannel, start: i64, time: i64) -> Option<MediaEntry
|
||||
.find(|e| {
|
||||
let meta = e.metadata.unwrap();
|
||||
let date = e.date.unwrap();
|
||||
|
||||
let timestamp = date.seconds - start;
|
||||
|
||||
return timestamp <= time && timestamp + meta.duration as i64 >= time;
|
||||
@@ -64,13 +67,23 @@ fn Video(entry: Option<MediaEntry>) -> Element
|
||||
{
|
||||
match entry
|
||||
{
|
||||
Some(entry) => rsx! {
|
||||
video{
|
||||
autoplay: "true",
|
||||
muted: "true",
|
||||
src: "{MEDIA_HOST}/m/v/{entry.id}"
|
||||
Some(entry) =>
|
||||
{
|
||||
info!("date: {}, name: {}", entry.date.unwrap().to_string(), entry.file_path);
|
||||
rsx! {
|
||||
video{
|
||||
autoplay: "true",
|
||||
muted: "true",
|
||||
controls: true,
|
||||
src: "{MEDIA_HOST}/m/v/{entry.id}"
|
||||
}
|
||||
}
|
||||
}
|
||||
None => rsx! {
|
||||
div {
|
||||
class: "emptyVideo",
|
||||
"No Video"
|
||||
}
|
||||
},
|
||||
None => rsx! { div{ "No Video" }},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user