use adw::subclass::prelude::*;
use gst::{bus::BusWatchGuard, ClockTime};
use gst_play::{Play as GstPlay, PlayMessage};
use gtk::{gio, glib, glib::clone, prelude::*, CompositeTemplate};
use tracing::{error, warn};
use super::video_player_renderer::VideoPlayerRenderer;
mod imp {
use std::cell::{Cell, OnceCell, RefCell};
use glib::subclass::InitializingObject;
use super::*;
#[derive(Debug, Default, CompositeTemplate, glib::Properties)]
#[template(resource = "/org/gnome/Fractal/ui/components/media/video_player.ui")]
#[properties(wrapper_type = super::VideoPlayer)]
pub struct VideoPlayer {
#[property(get, set = Self::set_compact, explicit_notify)]
pub compact: Cell<bool>,
pub duration_handler: RefCell<Option<glib::SignalHandlerId>>,
#[template_child]
pub video: TemplateChild<gtk::Picture>,
#[template_child]
pub timestamp: TemplateChild<gtk::Label>,
#[template_child]
#[property(get = Self::player, type = GstPlay)]
pub player: TemplateChild<GstPlay>,
pub bus_guard: OnceCell<BusWatchGuard>,
pub file: RefCell<Option<gio::File>>,
}
#[glib::object_subclass]
impl ObjectSubclass for VideoPlayer {
const NAME: &'static str = "VideoPlayer";
type Type = super::VideoPlayer;
type ParentType = adw::Bin;
fn class_init(klass: &mut Self::Class) {
VideoPlayerRenderer::ensure_type();
Self::bind_template(klass);
}
fn instance_init(obj: &InitializingObject<Self>) {
obj.init_template();
}
}
#[glib::derived_properties]
impl ObjectImpl for VideoPlayer {
fn constructed(&self) {
self.parent_constructed();
let obj = self.obj();
let bus_guard = self
.player
.message_bus()
.add_watch_local(clone!(
#[weak]
obj,
#[upgrade_or]
glib::ControlFlow::Break,
move |_, message| {
match PlayMessage::parse(message) {
Ok(PlayMessage::DurationChanged { duration }) => {
obj.duration_changed(duration)
}
Ok(PlayMessage::Warning { error, .. }) => {
warn!("Warning playing video: {error}");
}
Ok(PlayMessage::Error { error, .. }) => {
error!("Error playing video: {error}");
}
_ => {}
}
glib::ControlFlow::Continue
}
))
.unwrap();
self.bus_guard.set(bus_guard).unwrap();
}
fn dispose(&self) {
self.player.message_bus().set_flushing(true);
}
}
impl WidgetImpl for VideoPlayer {
fn map(&self) {
self.parent_map();
self.player.play();
}
fn unmap(&self) {
self.player.stop();
self.parent_unmap();
}
}
impl BinImpl for VideoPlayer {}
impl VideoPlayer {
fn player(&self) -> GstPlay {
self.player.clone()
}
fn set_compact(&self, compact: bool) {
if self.compact.get() == compact {
return;
}
self.compact.set(compact);
self.obj().notify_compact();
}
}
}
glib::wrapper! {
pub struct VideoPlayer(ObjectSubclass<imp::VideoPlayer>)
@extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
}
impl VideoPlayer {
pub fn new() -> Self {
glib::Object::new()
}
pub fn play_media_file(&self, file: gio::File) {
self.imp().file.replace(Some(file.clone()));
self.duration_changed(None);
let player = self.player();
player.set_uri(Some(file.uri().as_ref()));
player.set_audio_track_enabled(false);
}
fn duration_changed(&self, duration: Option<ClockTime>) {
let label = if let Some(duration) = duration {
let mut time = duration.seconds();
let sec = time % 60;
time -= sec;
let min = (time % (60 * 60)) / 60;
time -= min * 60;
let hour = time / (60 * 60);
if hour > 0 {
format!("{hour}:{min:02}:{sec:02}")
} else {
format!("{min:02}:{sec:02}")
}
} else {
"--:--".to_owned()
};
self.imp().timestamp.set_label(&label);
}
}