player: Implement playback

This commit is contained in:
Elias Projahn 2023-11-03 17:48:27 +01:00
parent 9489aaf2ee
commit c378305465
6 changed files with 335 additions and 31 deletions

196
Cargo.lock generated
View file

@ -49,6 +49,12 @@ version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
[[package]]
name = "atomic_refcell"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41e67cd8309bbd06cd603a9e693a784ac2e5d1e955f11286e355089fcab3047c"
[[package]]
name = "autocfg"
version = "1.1.0"
@ -140,6 +146,12 @@ version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
[[package]]
name = "either"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
[[package]]
name = "fallible-iterator"
version = "0.2.0"
@ -162,6 +174,12 @@ dependencies = [
"rustc_version",
]
[[package]]
name = "fragile"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa"
[[package]]
name = "futures-channel"
version = "0.3.28"
@ -458,6 +476,125 @@ dependencies = [
"system-deps",
]
[[package]]
name = "gstreamer"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b369a1eb2f7db49920d3d590bd988c5fb56dbf2347e1efb60307fe953546ee5d"
dependencies = [
"cfg-if",
"futures-channel",
"futures-core",
"futures-util",
"glib",
"gstreamer-sys",
"itertools",
"libc",
"muldiv",
"num-integer",
"num-rational",
"option-operations",
"paste",
"pretty-hex",
"smallvec",
"thiserror",
]
[[package]]
name = "gstreamer-base"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fe38a6d5c1e516ce3fd6069e972a540d315448ed69fdadad739e6c6c6eb2a01"
dependencies = [
"atomic_refcell",
"cfg-if",
"glib",
"gstreamer",
"gstreamer-base-sys",
"libc",
]
[[package]]
name = "gstreamer-base-sys"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4ca701f9078fe115b29b24c80910b577f9cb5b039182f050dbadf5933594b64"
dependencies = [
"glib-sys",
"gobject-sys",
"gstreamer-sys",
"libc",
"system-deps",
]
[[package]]
name = "gstreamer-player"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e68dc9772932f6133a9517742918b13ab5414db1f47e19daebc3027a1c3d20d2"
dependencies = [
"glib",
"gstreamer",
"gstreamer-player-sys",
"gstreamer-video",
"libc",
]
[[package]]
name = "gstreamer-player-sys"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5ef4d00b43d0aa94e9a518e6ef4a4c504b4b855304a0a5f4ed1493d5e5ca66c"
dependencies = [
"glib-sys",
"gobject-sys",
"gstreamer-sys",
"gstreamer-video-sys",
"libc",
"system-deps",
]
[[package]]
name = "gstreamer-sys"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f86bf9de67a6ab7af67ac11588f4939e984a936030437219f269fe969d79ad8c"
dependencies = [
"glib-sys",
"gobject-sys",
"libc",
"system-deps",
]
[[package]]
name = "gstreamer-video"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01b4d3141362b3d44a684e697d2bc55fea73d023315449cda83f0f4324531d64"
dependencies = [
"cfg-if",
"futures-channel",
"glib",
"gstreamer",
"gstreamer-base",
"gstreamer-video-sys",
"libc",
]
[[package]]
name = "gstreamer-video-sys"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cdc36baab839921b05d2468524da649f373dccc5f966c75e564029dc135b1c"
dependencies = [
"glib-sys",
"gobject-sys",
"gstreamer-base-sys",
"gstreamer-sys",
"libc",
"system-deps",
]
[[package]]
name = "gtk4"
version = "0.7.3"
@ -576,6 +713,15 @@ dependencies = [
"hashbrown 0.12.3",
]
[[package]]
name = "itertools"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
dependencies = [
"either",
]
[[package]]
name = "js-sys"
version = "0.3.64"
@ -683,12 +829,20 @@ dependencies = [
"autocfg",
]
[[package]]
name = "muldiv"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "956787520e75e9bd233246045d19f42fb73242759cc57fba9611d940ae96d4b0"
[[package]]
name = "musicus"
version = "0.1.0"
dependencies = [
"chrono",
"fragile",
"gettext-rs",
"gstreamer-player",
"gtk4",
"libadwaita",
"log",
@ -710,6 +864,27 @@ dependencies = [
"winapi",
]
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.16"
@ -754,6 +929,15 @@ version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "option-operations"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c26d27bb1aeab65138e4bf7666045169d1717febcc9ff870166be8348b223d0"
dependencies = [
"paste",
]
[[package]]
name = "overload"
version = "0.1.1"
@ -785,6 +969,12 @@ dependencies = [
"system-deps",
]
[[package]]
name = "paste"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
[[package]]
name = "pin-project-lite"
version = "0.2.9"
@ -809,6 +999,12 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "pretty-hex"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6fa0831dd7cc608c38a5e323422a0077678fa5744aa2be4ad91c4ece8eec8d5"
[[package]]
name = "proc-macro-crate"
version = "1.3.1"

View file

@ -6,7 +6,9 @@ edition = "2021"
[dependencies]
adw = { package = "libadwaita", version = "0.5", features = ["v1_4"] }
chrono = "0.4"
fragile = "2"
gettext-rs = { version = "0.7", features = ["gettext-system"] }
gstreamer-player = "0.21"
gtk = { package = "gtk4", version = "0.7", features = ["v4_12", "blueprint"] }
log = "0.4"
once_cell = "1"

View file

@ -13,6 +13,7 @@
"--socket=fallback-x11",
"--device=dri",
"--socket=wayland",
"--socket=pulseaudio",
"--filesystem=host"
],
"build-options": {

View file

@ -18,10 +18,12 @@ use self::{application::MusicusApplication, window::MusicusWindow};
use config::{GETTEXT_PACKAGE, LOCALEDIR, PKGDATADIR};
use gettextrs::{bind_textdomain_codeset, bindtextdomain, textdomain};
use gstreamer_player::gst;
use gtk::{gio, glib, prelude::*};
fn main() -> glib::ExitCode {
tracing_subscriber::fmt::init();
gst::init().expect("Failed to initialize GStreamer!");
bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR).expect("Unable to bind the text domain");
bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8")

View file

@ -1,5 +1,12 @@
use crate::playlist_item::PlaylistItem;
use gtk::{gio, glib, glib::Properties, prelude::*, subclass::prelude::*};
use fragile::Fragile;
use gstreamer_player::gst;
use gtk::{
gio,
glib::{self, Properties},
prelude::*,
subclass::prelude::*,
};
use std::cell::{Cell, OnceCell};
mod imp {
@ -17,34 +24,48 @@ mod imp {
#[property(get, set = Self::set_current_index)]
pub current_index: Cell<u32>,
#[property(get, set)]
pub current_time: Cell<u32>,
pub duration_ms: Cell<u64>,
#[property(get, set)]
pub remaining_time: Cell<u32>,
pub current_time_ms: Cell<u64>,
#[property(get, set = Self::set_position)]
pub position: Cell<f64>,
#[property(get, construct_only)]
pub player: OnceCell<gstreamer_player::Player>,
}
impl MusicusPlayer {
pub fn set_current_index(&self, index: u32) {
let playlist = self.playlist.get().unwrap();
if let Some(item) = playlist.item(self.current_index.get()) {
item.downcast::<PlaylistItem>()
.unwrap()
.set_is_playing(false);
}
self.current_index.set(index);
if let Some(item) = playlist.item(index) {
item.downcast::<PlaylistItem>()
.unwrap()
.set_is_playing(true);
if let Some(old_item) = playlist.item(self.current_index.get()) {
old_item.downcast::<PlaylistItem>()
.unwrap()
.set_is_playing(false);
}
let item = item.downcast::<PlaylistItem>().unwrap();
let uri = glib::filename_to_uri(&item.path(), None)
.expect("track path should be parsable as an URI");
let player = self.player.get().unwrap();
player.set_uri(Some(&uri));
if self.playing.get() {
player.play();
}
self.current_index.set(index);
item.set_is_playing(true);
}
}
pub fn set_position(&self, position: f64) {
self.position.set(position);
self.player
.get()
.unwrap()
.seek(gst::ClockTime::from_mseconds(
(position * self.duration_ms.get() as f64) as u64,
));
}
}
@ -55,7 +76,63 @@ mod imp {
}
#[glib::derived_properties]
impl ObjectImpl for MusicusPlayer {}
impl ObjectImpl for MusicusPlayer {
fn constructed(&self) {
self.parent_constructed();
let player = self.player.get().unwrap();
let mut config = player.config();
config.set_position_update_interval(250);
player.set_config(config).unwrap();
player.set_video_track_enabled(false);
let obj = Fragile::new(self.obj().to_owned());
player.connect_end_of_stream(move |_| {
obj.get().next();
});
let obj = Fragile::new(self.obj().to_owned());
player.connect_position_updated(move |_, current_time| {
if let Some(current_time) = current_time {
let obj = obj.get();
let imp = obj.imp();
let current_time_ms = current_time.mseconds();
let duration_ms = imp.duration_ms.get();
let mut position = current_time_ms as f64 / duration_ms as f64;
if position > 1.0 {
position = 1.0
}
imp.current_time_ms.set(current_time_ms);
obj.notify_current_time_ms();
imp.position.set(position);
obj.notify_position();
}
});
let obj = Fragile::new(self.obj().to_owned());
player.connect_duration_changed(move |_, duration| {
if let Some(duration) = duration {
let obj = obj.get();
let imp = obj.imp();
let duration_ms = duration.mseconds();
imp.duration_ms.set(duration_ms);
obj.notify_duration_ms();
imp.current_time_ms.set(0);
obj.notify_current_time_ms();
imp.position.set(0.0);
obj.notify_position();
}
});
}
}
}
glib::wrapper! {
@ -64,14 +141,22 @@ glib::wrapper! {
impl MusicusPlayer {
pub fn new() -> Self {
let player = gstreamer_player::Player::new(
None::<gstreamer_player::PlayerVideoRenderer>,
Some(gstreamer_player::PlayerGMainContextSignalDispatcher::new(
None,
)),
);
glib::Object::builder()
.property("active", false)
.property("playing", false)
.property("playlist", gio::ListStore::new::<PlaylistItem>())
.property("current-index", 0u32)
.property("current-time", 0u32)
.property("remaining-time", 10000u32)
.property("current-time-ms", 0u64)
.property("duration-ms", 60_000u64)
.property("position", 0.0)
.property("player", player)
.build()
}
@ -82,15 +167,21 @@ impl MusicusPlayer {
playlist.append(&track);
}
self.set_active(true);
if !self.active() && playlist.n_items() > 0 {
self.set_active(true);
self.set_current_index(0);
self.play();
}
}
pub fn play(&self) {
self.set_playing(true)
self.player().play();
self.set_playing(true);
}
pub fn pause(&self) {
self.set_playing(false)
self.player().pause();
self.set_playing(false);
}
pub fn current_item(&self) -> Option<PlaylistItem> {

View file

@ -111,17 +111,17 @@ mod imp {
.playlist()
.connect_items_changed(clone!(@weak obj => move |_, _, _, _| obj.imp().update()));
player
.bind_property("current-time", &self.current_time_label.get(), "label")
.transform_to(|_, t: u32| Some(format!("{:0>2}:{:0>2}", t / 60, t % 60)))
.sync_create()
.build();
player.connect_current_time_ms_notify(clone!(@weak obj => move |player| {
let imp = obj.imp();
imp.current_time_label.set_label(&format_time(player.current_time_ms()));
imp.remaining_time_label.set_label(&format_time(player.duration_ms() - player.current_time_ms()));
}));
player
.bind_property("remaining-time", &self.remaining_time_label.get(), "label")
.transform_to(|_, t: u32| Some(format!("{:0>2}:{:0>2}", t / 60, t % 60)))
.sync_create()
.build();
player.connect_duration_ms_notify(clone!(@weak obj => move |player| {
let imp = obj.imp();
imp.current_time_label.set_label(&format_time(player.current_time_ms()));
imp.remaining_time_label.set_label(&format_time(player.duration_ms() - player.current_time_ms()));
}));
player
.bind_property("position", &self.slider.adjustment(), "value")
@ -187,3 +187,15 @@ impl PlayerBar {
}
}
}
fn format_time(time_ms: u64) -> String {
let s = time_ms / 1000;
let (m, s) = (s / 60, s % 60);
let (h, m) = (m / 60, m % 60);
if h > 0 {
format!("{h:0>2}:{m:0>2}:{s:0>2}")
} else {
format!("{m:0>2}:{s:0>2}")
}
}