mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-26 11:47:25 +01:00
player: Implement playback
This commit is contained in:
parent
9489aaf2ee
commit
c378305465
6 changed files with 335 additions and 31 deletions
196
Cargo.lock
generated
196
Cargo.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
"--socket=fallback-x11",
|
||||
"--device=dri",
|
||||
"--socket=wayland",
|
||||
"--socket=pulseaudio",
|
||||
"--filesystem=host"
|
||||
],
|
||||
"build-options": {
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
123
src/player.rs
123
src/player.rs
|
|
@ -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>()
|
||||
if let Some(item) = playlist.item(index) {
|
||||
if let Some(old_item) = playlist.item(self.current_index.get()) {
|
||||
old_item.downcast::<PlaylistItem>()
|
||||
.unwrap()
|
||||
.set_is_playing(false);
|
||||
}
|
||||
|
||||
self.current_index.set(index);
|
||||
let item = item.downcast::<PlaylistItem>().unwrap();
|
||||
let uri = glib::filename_to_uri(&item.path(), None)
|
||||
.expect("track path should be parsable as an URI");
|
||||
|
||||
if let Some(item) = playlist.item(index) {
|
||||
item.downcast::<PlaylistItem>()
|
||||
.unwrap()
|
||||
.set_is_playing(true);
|
||||
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);
|
||||
}
|
||||
|
||||
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> {
|
||||
|
|
|
|||
|
|
@ -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}")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue