mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-26 11:47:25 +01:00
Add playback and basic player UI
This commit is contained in:
parent
c12d8b01bd
commit
435fa23d76
14 changed files with 806 additions and 49 deletions
|
|
@ -7,6 +7,7 @@ edition = "2018"
|
||||||
anyhow = "1.0.33"
|
anyhow = "1.0.33"
|
||||||
diesel = { version = "1.4.5", features = ["sqlite"] }
|
diesel = { version = "1.4.5", features = ["sqlite"] }
|
||||||
diesel_migrations = "1.4.0"
|
diesel_migrations = "1.4.0"
|
||||||
|
fragile = "1.0.0"
|
||||||
futures = "0.3.6"
|
futures = "0.3.6"
|
||||||
futures-channel = "0.3.5"
|
futures-channel = "0.3.5"
|
||||||
gettext-rs = "0.5.0"
|
gettext-rs = "0.5.0"
|
||||||
|
|
@ -14,6 +15,8 @@ gio = "0.9.1"
|
||||||
glib = "0.10.2"
|
glib = "0.10.2"
|
||||||
gtk = { version = "0.9.2", features = ["v3_24"] }
|
gtk = { version = "0.9.2", features = ["v3_24"] }
|
||||||
gtk-macros = "0.2.0"
|
gtk-macros = "0.2.0"
|
||||||
|
gstreamer = "0.16.4"
|
||||||
|
gstreamer-player = "0.16.3"
|
||||||
libhandy = "0.7.0"
|
libhandy = "0.7.0"
|
||||||
pango = "0.9.1"
|
pango = "0.9.1"
|
||||||
rand = "0.7.3"
|
rand = "0.7.3"
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ project('musicus', 'rust',
|
||||||
|
|
||||||
dependency('glib-2.0', version: '>= 2.56')
|
dependency('glib-2.0', version: '>= 2.56')
|
||||||
dependency('gio-2.0', version: '>= 2.56')
|
dependency('gio-2.0', version: '>= 2.56')
|
||||||
|
dependency('gstreamer-1.0', version: '>= 1.12')
|
||||||
dependency('gtk+-3.0', version: '>= 3.24.7')
|
dependency('gtk+-3.0', version: '>= 3.24.7')
|
||||||
dependency('libhandy-1', version: '>= 1.0.0')
|
dependency('libhandy-1', version: '>= 1.0.0')
|
||||||
dependency('pango', version: '>= 1.0')
|
dependency('pango', version: '>= 1.0')
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
<file preprocess="xml-stripblanks">ui/person_list.ui</file>
|
<file preprocess="xml-stripblanks">ui/person_list.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/person_screen.ui</file>
|
<file preprocess="xml-stripblanks">ui/person_screen.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/person_selector.ui</file>
|
<file preprocess="xml-stripblanks">ui/person_selector.ui</file>
|
||||||
|
<file preprocess="xml-stripblanks">ui/player_bar.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/poe_list.ui</file>
|
<file preprocess="xml-stripblanks">ui/poe_list.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/preferences.ui</file>
|
<file preprocess="xml-stripblanks">ui/preferences.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/recording_editor.ui</file>
|
<file preprocess="xml-stripblanks">ui/recording_editor.ui</file>
|
||||||
|
|
|
||||||
230
res/ui/player_bar.ui
Normal file
230
res/ui/player_bar.ui
Normal file
|
|
@ -0,0 +1,230 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Generated with glade 3.38.1 -->
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk+" version="3.24"/>
|
||||||
|
<object class="GtkImage" id="play_image">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="icon-name">media-playback-start-symbolic</property>
|
||||||
|
</object>
|
||||||
|
<object class="GtkRevealer" id="widget">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="transition-type">slide-up</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSeparator">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="border-width">6</property>
|
||||||
|
<property name="spacing">12</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="valign">center</property>
|
||||||
|
<property name="spacing">6</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="previous_button">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="sensitive">False</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="receives-default">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImage">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="icon-name">media-skip-backward-symbolic</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="play_button">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="receives-default">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImage" id="pause_image">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="icon-name">media-playback-pause-symbolic</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="next_button">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="sensitive">False</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="receives-default">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImage">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="icon-name">media-skip-forward-symbolic</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="title_label">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="label" translatable="yes">Title</property>
|
||||||
|
<property name="ellipsize">end</property>
|
||||||
|
<attributes>
|
||||||
|
<attribute name="weight" value="bold"/>
|
||||||
|
</attributes>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="subtitle_label">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="label" translatable="yes">Subtitle</property>
|
||||||
|
<property name="ellipsize">end</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="playlist_button">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="receives-default">True</property>
|
||||||
|
<property name="valign">center</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImage">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="icon-name">view-list-bullet-symbolic</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="pack-type">end</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="spacing">2</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="position_label">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="label" translatable="yes">0:00</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="label" translatable="yes">/</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="duration_label">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="label" translatable="yes">0:00</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">3</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</interface>
|
||||||
|
|
@ -122,6 +122,23 @@
|
||||||
<property name="position">1</property>
|
<property name="position">1</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="add_to_playlist_button">
|
||||||
|
<property name="label" translatable="yes">Add to playlist</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="receives-default">True</property>
|
||||||
|
<property name="halign">end</property>
|
||||||
|
<style>
|
||||||
|
<class name="suggested-action"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
|
|
||||||
102
res/ui/window.ui
102
res/ui/window.ui
|
|
@ -26,9 +26,9 @@
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
<property name="halign">center</property>
|
<property name="halign">center</property>
|
||||||
<property name="valign">center</property>
|
<property name="valign">center</property>
|
||||||
|
<property name="border-width">18</property>
|
||||||
<property name="orientation">vertical</property>
|
<property name="orientation">vertical</property>
|
||||||
<property name="spacing">18</property>
|
<property name="spacing">18</property>
|
||||||
<property name="border-width">18</property>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkImage">
|
<object class="GtkImage">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
|
|
@ -230,78 +230,90 @@
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="HdyLeaflet" id="leaflet">
|
<object class="GtkBox" id="content_box">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkBox" id="sidebar_box">
|
<object class="HdyLeaflet" id="leaflet">
|
||||||
<property name="width-request">250</property>
|
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
<property name="hexpand">False</property>
|
|
||||||
<property name="orientation">vertical</property>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="HdyHeaderBar">
|
<object class="GtkBox" id="sidebar_box">
|
||||||
|
<property name="width-request">250</property>
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
<property name="title" translatable="yes">Musicus</property>
|
<property name="hexpand">False</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton" id="add_button">
|
<object class="HdyHeaderBar">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">True</property>
|
<property name="can-focus">False</property>
|
||||||
<property name="receives-default">True</property>
|
<property name="title" translatable="yes">Musicus</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkImage">
|
<object class="GtkButton" id="add_button">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">True</property>
|
||||||
<property name="icon-name">list-add-symbolic</property>
|
<property name="receives-default">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImage">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="icon-name">list-add-symbolic</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkMenuButton">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can-focus">True</property>
|
|
||||||
<property name="focus-on-click">False</property>
|
|
||||||
<property name="receives-default">True</property>
|
|
||||||
<property name="menu-model">menu</property>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkImage">
|
<object class="GtkMenuButton">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">True</property>
|
||||||
<property name="icon-name">open-menu-symbolic</property>
|
<property name="focus-on-click">False</property>
|
||||||
|
<property name="receives-default">True</property>
|
||||||
|
<property name="menu-model">menu</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImage">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="icon-name">open-menu-symbolic</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="pack-type">end</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="pack-type">end</property>
|
<property name="expand">False</property>
|
||||||
<property name="position">1</property>
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="name">sidebar</property>
|
||||||
<property name="fill">True</property>
|
</packing>
|
||||||
<property name="position">0</property>
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSeparator">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<style>
|
||||||
|
<class name="sidebar" />
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="navigatable">False</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="name">sidebar</property>
|
<property name="expand">True</property>
|
||||||
</packing>
|
<property name="fill">True</property>
|
||||||
</child>
|
<property name="position">0</property>
|
||||||
<child>
|
|
||||||
<object class="GtkSeparator">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can-focus">False</property>
|
|
||||||
<property name="orientation">vertical</property>
|
|
||||||
<style>
|
|
||||||
<class name="sidebar" />
|
|
||||||
</style>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="navigatable">False</property>
|
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use super::database::*;
|
use super::database::*;
|
||||||
|
use crate::player::*;
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use futures_channel::oneshot::Sender;
|
use futures_channel::oneshot::Sender;
|
||||||
use futures_channel::{mpsc, oneshot};
|
use futures_channel::{mpsc, oneshot};
|
||||||
|
|
@ -50,6 +51,7 @@ pub struct Backend {
|
||||||
action_sender: RefCell<Option<std::sync::mpsc::Sender<BackendAction>>>,
|
action_sender: RefCell<Option<std::sync::mpsc::Sender<BackendAction>>>,
|
||||||
settings: gio::Settings,
|
settings: gio::Settings,
|
||||||
music_library_path: RefCell<Option<PathBuf>>,
|
music_library_path: RefCell<Option<PathBuf>>,
|
||||||
|
player: RefCell<Option<Rc<Player>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Backend {
|
impl Backend {
|
||||||
|
|
@ -62,6 +64,7 @@ impl Backend {
|
||||||
action_sender: RefCell::new(None),
|
action_sender: RefCell::new(None),
|
||||||
settings: gio::Settings::new("de.johrpan.musicus"),
|
settings: gio::Settings::new("de.johrpan.musicus"),
|
||||||
music_library_path: RefCell::new(None),
|
music_library_path: RefCell::new(None),
|
||||||
|
player: RefCell::new(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -267,10 +270,16 @@ impl Backend {
|
||||||
self.music_library_path.borrow().clone()
|
self.music_library_path.borrow().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_player(&self) -> Option<Rc<Player>> {
|
||||||
|
self.player.borrow().clone()
|
||||||
|
}
|
||||||
|
|
||||||
async fn set_music_library_path_priv(&self, path: PathBuf) -> Result<()> {
|
async fn set_music_library_path_priv(&self, path: PathBuf) -> Result<()> {
|
||||||
self.music_library_path.replace(Some(path.clone()));
|
|
||||||
self.set_state(BackendState::Loading);
|
self.set_state(BackendState::Loading);
|
||||||
|
|
||||||
|
self.music_library_path.replace(Some(path.clone()));
|
||||||
|
self.player.replace(Some(Player::new(path.clone())));
|
||||||
|
|
||||||
if let Some(action_sender) = self.action_sender.borrow_mut().take() {
|
if let Some(action_sender) = self.action_sender.borrow_mut().take() {
|
||||||
action_sender.send(Stop)?;
|
action_sender.send(Stop)?;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ mod config;
|
||||||
mod backend;
|
mod backend;
|
||||||
mod database;
|
mod database;
|
||||||
mod dialogs;
|
mod dialogs;
|
||||||
|
mod player;
|
||||||
mod screens;
|
mod screens;
|
||||||
mod widgets;
|
mod widgets;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,10 +63,14 @@ sources = files(
|
||||||
'widgets/mod.rs',
|
'widgets/mod.rs',
|
||||||
'widgets/navigator.rs',
|
'widgets/navigator.rs',
|
||||||
'widgets/person_list.rs',
|
'widgets/person_list.rs',
|
||||||
|
'widgets/player_bar.rs',
|
||||||
'widgets/poe_list.rs',
|
'widgets/poe_list.rs',
|
||||||
'widgets/selector_row.rs',
|
'widgets/selector_row.rs',
|
||||||
'backend.rs',
|
'backend.rs',
|
||||||
|
'config.rs',
|
||||||
|
'config.rs.in',
|
||||||
'main.rs',
|
'main.rs',
|
||||||
|
'player.rs',
|
||||||
'resources.rs',
|
'resources.rs',
|
||||||
'resources.rs.in',
|
'resources.rs.in',
|
||||||
'window.rs',
|
'window.rs',
|
||||||
|
|
|
||||||
291
src/player.rs
Normal file
291
src/player.rs
Normal file
|
|
@ -0,0 +1,291 @@
|
||||||
|
use crate::database::*;
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use anyhow::Result;
|
||||||
|
use gstreamer_player::prelude::*;
|
||||||
|
use std::cell::{Cell, RefCell};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PlaylistItem {
|
||||||
|
pub recording: RecordingDescription,
|
||||||
|
pub tracks: Vec<TrackDescription>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Player {
|
||||||
|
music_library_path: PathBuf,
|
||||||
|
player: gstreamer_player::Player,
|
||||||
|
playlist: RefCell<Vec<PlaylistItem>>,
|
||||||
|
current_item: Cell<Option<usize>>,
|
||||||
|
current_track: Cell<Option<usize>>,
|
||||||
|
playing: Cell<bool>,
|
||||||
|
playlist_cb: RefCell<Option<Box<dyn Fn(Vec<PlaylistItem>) -> ()>>>,
|
||||||
|
track_cb: RefCell<Option<Box<dyn Fn(usize, usize) -> ()>>>,
|
||||||
|
duration_cb: RefCell<Option<Box<dyn Fn(u64) -> ()>>>,
|
||||||
|
playing_cb: RefCell<Option<Box<dyn Fn(bool) -> ()>>>,
|
||||||
|
position_cb: RefCell<Option<Box<dyn Fn(u64) -> ()>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Player {
|
||||||
|
pub fn new(music_library_path: PathBuf) -> Rc<Self> {
|
||||||
|
let dispatcher = gstreamer_player::PlayerGMainContextSignalDispatcher::new(None);
|
||||||
|
let player = gstreamer_player::Player::new(None, Some(&dispatcher.upcast()));
|
||||||
|
let mut config = player.get_config();
|
||||||
|
config.set_position_update_interval(250);
|
||||||
|
player.set_config(config).unwrap();
|
||||||
|
player.set_video_track_enabled(false);
|
||||||
|
|
||||||
|
let result = Rc::new(Self {
|
||||||
|
music_library_path,
|
||||||
|
player: player.clone(),
|
||||||
|
playlist: RefCell::new(Vec::new()),
|
||||||
|
current_item: Cell::new(None),
|
||||||
|
current_track: Cell::new(None),
|
||||||
|
playing: Cell::new(false),
|
||||||
|
playlist_cb: RefCell::new(None),
|
||||||
|
track_cb: RefCell::new(None),
|
||||||
|
duration_cb: RefCell::new(None),
|
||||||
|
playing_cb: RefCell::new(None),
|
||||||
|
position_cb: RefCell::new(None),
|
||||||
|
});
|
||||||
|
|
||||||
|
let clone = fragile::Fragile::new(result.clone());
|
||||||
|
player.connect_end_of_stream(move |_| {
|
||||||
|
let clone = clone.get();
|
||||||
|
if clone.has_next() {
|
||||||
|
clone.next().unwrap();
|
||||||
|
} else {
|
||||||
|
clone.player.stop();
|
||||||
|
|
||||||
|
if let Some(cb) = &*clone.playing_cb.borrow() {
|
||||||
|
cb(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let clone = fragile::Fragile::new(result.clone());
|
||||||
|
player.connect_position_updated(move |_, position| {
|
||||||
|
if let Some(cb) = &*clone.get().position_cb.borrow() {
|
||||||
|
cb(position.mseconds().unwrap());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let clone = fragile::Fragile::new(result.clone());
|
||||||
|
player.connect_duration_changed(move |_, duration| {
|
||||||
|
if let Some(cb) = &*clone.get().duration_cb.borrow() {
|
||||||
|
cb(duration.mseconds().unwrap());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_playlist_cb<F: Fn(Vec<PlaylistItem>) -> () + 'static>(&self, cb: F) {
|
||||||
|
self.playlist_cb.replace(Some(Box::new(cb)));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_track_cb<F: Fn(usize, usize) -> () + 'static>(&self, cb: F) {
|
||||||
|
self.track_cb.replace(Some(Box::new(cb)));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_duration_cb<F: Fn(u64) -> () + 'static>(&self, cb: F) {
|
||||||
|
self.duration_cb.replace(Some(Box::new(cb)));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_playing_cb<F: Fn(bool) -> () + 'static>(&self, cb: F) {
|
||||||
|
self.playing_cb.replace(Some(Box::new(cb)));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_position_cb<F: Fn(u64) -> () + 'static>(&self, cb: F) {
|
||||||
|
self.position_cb.replace(Some(Box::new(cb)));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_playlist(&self) -> Vec<PlaylistItem> {
|
||||||
|
self.playlist.borrow().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_current_item(&self) -> Option<usize> {
|
||||||
|
self.current_item.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_current_track(&self) -> Option<usize> {
|
||||||
|
self.current_track.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_duration(&self) -> gstreamer::ClockTime {
|
||||||
|
self.player.get_duration()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_playing(&self) -> bool {
|
||||||
|
self.playing.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_item(&self, item: PlaylistItem) -> Result<()> {
|
||||||
|
if item.tracks.is_empty() {
|
||||||
|
Err(anyhow!(
|
||||||
|
"Tried to add playlist item without tracks to playlist!"
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
let was_empty = {
|
||||||
|
let mut playlist = self.playlist.borrow_mut();
|
||||||
|
let was_empty = playlist.is_empty();
|
||||||
|
|
||||||
|
playlist.push(item);
|
||||||
|
|
||||||
|
was_empty
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(cb) = &*self.playlist_cb.borrow() {
|
||||||
|
cb(self.playlist.borrow().clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
if was_empty {
|
||||||
|
self.set_track(0, 0)?;
|
||||||
|
self.player.play();
|
||||||
|
self.playing.set(true);
|
||||||
|
|
||||||
|
if let Some(cb) = &*self.playing_cb.borrow() {
|
||||||
|
cb(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn play_pause(&self) {
|
||||||
|
if self.is_playing() {
|
||||||
|
self.player.pause();
|
||||||
|
self.playing.set(false);
|
||||||
|
|
||||||
|
if let Some(cb) = &*self.playing_cb.borrow() {
|
||||||
|
cb(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.player.play();
|
||||||
|
self.playing.set(true);
|
||||||
|
|
||||||
|
if let Some(cb) = &*self.playing_cb.borrow() {
|
||||||
|
cb(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_previous(&self) -> bool {
|
||||||
|
if let Some(current_item) = self.current_item.get() {
|
||||||
|
if let Some(current_track) = self.current_track.get() {
|
||||||
|
current_track > 0 || current_item > 0
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn previous(&self) -> Result<()> {
|
||||||
|
let mut current_item = self.current_item.get().ok_or(anyhow!("No current item!"))?;
|
||||||
|
let mut current_track = self
|
||||||
|
.current_track
|
||||||
|
.get()
|
||||||
|
.ok_or(anyhow!("No current track!"))?;
|
||||||
|
|
||||||
|
let playlist = self.playlist.borrow();
|
||||||
|
if current_track > 0 {
|
||||||
|
current_track -= 1;
|
||||||
|
} else if current_item > 0 {
|
||||||
|
current_item -= 1;
|
||||||
|
current_track = playlist[current_item].tracks.len() - 1;
|
||||||
|
} else {
|
||||||
|
return Err(anyhow!("No previous track!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.set_track(current_item, current_track)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_next(&self) -> bool {
|
||||||
|
if let Some(current_item) = self.current_item.get() {
|
||||||
|
if let Some(current_track) = self.current_track.get() {
|
||||||
|
let playlist = self.playlist.borrow();
|
||||||
|
let item = &playlist[current_item];
|
||||||
|
|
||||||
|
current_track + 1 < item.tracks.len() || current_item + 1 < playlist.len()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&self) -> Result<()> {
|
||||||
|
let mut current_item = self.current_item.get().ok_or(anyhow!("No current item!"))?;
|
||||||
|
let mut current_track = self
|
||||||
|
.current_track
|
||||||
|
.get()
|
||||||
|
.ok_or(anyhow!("No current track!"))?;
|
||||||
|
|
||||||
|
let playlist = self.playlist.borrow();
|
||||||
|
let item = &playlist[current_item];
|
||||||
|
if current_track + 1 < item.tracks.len() {
|
||||||
|
current_track += 1;
|
||||||
|
} else if current_item + 1 < playlist.len() {
|
||||||
|
current_item += 1;
|
||||||
|
current_track = 0;
|
||||||
|
} else {
|
||||||
|
return Err(anyhow!("No next track!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.set_track(current_item, current_track)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_track(&self, current_item: usize, current_track: usize) -> Result<()> {
|
||||||
|
let uri = format!(
|
||||||
|
"file://{}",
|
||||||
|
self.music_library_path
|
||||||
|
.join(
|
||||||
|
self.playlist
|
||||||
|
.borrow()
|
||||||
|
.get(current_item)
|
||||||
|
.ok_or(anyhow!("Playlist item doesn't exist!"))?
|
||||||
|
.tracks
|
||||||
|
.get(current_track)
|
||||||
|
.ok_or(anyhow!("Track doesn't exist!"))?
|
||||||
|
.file_name
|
||||||
|
.clone(),
|
||||||
|
)
|
||||||
|
.to_str()
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.player.set_uri(&uri);
|
||||||
|
if self.is_playing() {
|
||||||
|
self.player.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.current_item.set(Some(current_item));
|
||||||
|
self.current_track.set(Some(current_track));
|
||||||
|
|
||||||
|
if let Some(cb) = &*self.track_cb.borrow() {
|
||||||
|
cb(current_item, current_track);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(&self) {
|
||||||
|
self.player.stop();
|
||||||
|
self.playing.set(false);
|
||||||
|
self.current_item.set(None);
|
||||||
|
self.current_track.set(None);
|
||||||
|
self.playlist.replace(Vec::new());
|
||||||
|
|
||||||
|
if let Some(cb) = &*self.playing_cb.borrow() {
|
||||||
|
cb(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(cb) = &*self.playlist_cb.borrow() {
|
||||||
|
cb(Vec::new());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::backend::*;
|
use crate::backend::*;
|
||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
|
use crate::player::*;
|
||||||
use crate::widgets::*;
|
use crate::widgets::*;
|
||||||
use gettextrs::gettext;
|
use gettextrs::gettext;
|
||||||
use glib::clone;
|
use glib::clone;
|
||||||
|
|
@ -13,13 +14,13 @@ pub struct RecordingScreen {
|
||||||
backend: Rc<Backend>,
|
backend: Rc<Backend>,
|
||||||
widget: gtk::Box,
|
widget: gtk::Box,
|
||||||
stack: gtk::Stack,
|
stack: gtk::Stack,
|
||||||
|
tracks: RefCell<Vec<TrackDescription>>,
|
||||||
navigator: RefCell<Option<Rc<Navigator>>>,
|
navigator: RefCell<Option<Rc<Navigator>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RecordingScreen {
|
impl RecordingScreen {
|
||||||
pub fn new(backend: Rc<Backend>, recording: RecordingDescription) -> Rc<Self> {
|
pub fn new(backend: Rc<Backend>, recording: RecordingDescription) -> Rc<Self> {
|
||||||
let builder =
|
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_screen.ui");
|
||||||
gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_screen.ui");
|
|
||||||
|
|
||||||
get_widget!(builder, gtk::Box, widget);
|
get_widget!(builder, gtk::Box, widget);
|
||||||
get_widget!(builder, libhandy::HeaderBar, header);
|
get_widget!(builder, libhandy::HeaderBar, header);
|
||||||
|
|
@ -27,6 +28,7 @@ impl RecordingScreen {
|
||||||
get_widget!(builder, gtk::MenuButton, menu_button);
|
get_widget!(builder, gtk::MenuButton, menu_button);
|
||||||
get_widget!(builder, gtk::Stack, stack);
|
get_widget!(builder, gtk::Stack, stack);
|
||||||
get_widget!(builder, gtk::Frame, frame);
|
get_widget!(builder, gtk::Frame, frame);
|
||||||
|
get_widget!(builder, gtk::Button, add_to_playlist_button);
|
||||||
|
|
||||||
header.set_title(Some(&recording.work.get_title()));
|
header.set_title(Some(&recording.work.get_title()));
|
||||||
header.set_subtitle(Some(&recording.get_performers()));
|
header.set_subtitle(Some(&recording.get_performers()));
|
||||||
|
|
@ -88,6 +90,7 @@ impl RecordingScreen {
|
||||||
backend,
|
backend,
|
||||||
widget,
|
widget,
|
||||||
stack,
|
stack,
|
||||||
|
tracks: RefCell::new(Vec::new()),
|
||||||
navigator: RefCell::new(None),
|
navigator: RefCell::new(None),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -98,13 +101,25 @@ impl RecordingScreen {
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
add_to_playlist_button.connect_clicked(
|
||||||
|
clone!(@strong result, @strong recording => move |_| {
|
||||||
|
if let Some(player) = result.backend.get_player() {
|
||||||
|
player.add_item(PlaylistItem {
|
||||||
|
recording: (*recording).clone(),
|
||||||
|
tracks: result.tracks.borrow().clone(),
|
||||||
|
}).unwrap();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
let context = glib::MainContext::default();
|
let context = glib::MainContext::default();
|
||||||
let clone = result.clone();
|
let clone = result.clone();
|
||||||
let id = recording.id;
|
let id = recording.id;
|
||||||
context.spawn_local(async move {
|
context.spawn_local(async move {
|
||||||
let tracks = clone.backend.get_tracks(id).await.unwrap();
|
let tracks = clone.backend.get_tracks(id).await.unwrap();
|
||||||
list.show_items(tracks);
|
list.show_items(tracks.clone());
|
||||||
clone.stack.set_visible_child_name("content");
|
clone.stack.set_visible_child_name("content");
|
||||||
|
clone.tracks.replace(tracks);
|
||||||
});
|
});
|
||||||
|
|
||||||
result
|
result
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,9 @@ pub use navigator::*;
|
||||||
pub mod person_list;
|
pub mod person_list;
|
||||||
pub use person_list::*;
|
pub use person_list::*;
|
||||||
|
|
||||||
|
pub mod player_bar;
|
||||||
|
pub use player_bar::*;
|
||||||
|
|
||||||
pub mod poe_list;
|
pub mod poe_list;
|
||||||
pub use poe_list::*;
|
pub use poe_list::*;
|
||||||
|
|
||||||
|
|
|
||||||
161
src/widgets/player_bar.rs
Normal file
161
src/widgets/player_bar.rs
Normal file
|
|
@ -0,0 +1,161 @@
|
||||||
|
use crate::player::*;
|
||||||
|
use glib::clone;
|
||||||
|
use gtk::prelude::*;
|
||||||
|
use gtk_macros::get_widget;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
pub struct PlayerBar {
|
||||||
|
pub widget: gtk::Revealer,
|
||||||
|
title_label: gtk::Label,
|
||||||
|
subtitle_label: gtk::Label,
|
||||||
|
previous_button: gtk::Button,
|
||||||
|
play_button: gtk::Button,
|
||||||
|
next_button: gtk::Button,
|
||||||
|
position_label: gtk::Label,
|
||||||
|
duration_label: gtk::Label,
|
||||||
|
play_image: gtk::Image,
|
||||||
|
pause_image: gtk::Image,
|
||||||
|
player: Rc<RefCell<Option<Rc<Player>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlayerBar {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/player_bar.ui");
|
||||||
|
|
||||||
|
get_widget!(builder, gtk::Revealer, widget);
|
||||||
|
get_widget!(builder, gtk::Label, title_label);
|
||||||
|
get_widget!(builder, gtk::Label, subtitle_label);
|
||||||
|
get_widget!(builder, gtk::Button, previous_button);
|
||||||
|
get_widget!(builder, gtk::Button, play_button);
|
||||||
|
get_widget!(builder, gtk::Button, next_button);
|
||||||
|
get_widget!(builder, gtk::Label, position_label);
|
||||||
|
get_widget!(builder, gtk::Label, duration_label);
|
||||||
|
get_widget!(builder, gtk::Image, play_image);
|
||||||
|
get_widget!(builder, gtk::Image, pause_image);
|
||||||
|
|
||||||
|
let player = Rc::new(RefCell::new(None::<Rc<Player>>));
|
||||||
|
|
||||||
|
previous_button.connect_clicked(clone!(@strong player => move |_| {
|
||||||
|
if let Some(player) = &*player.borrow() {
|
||||||
|
player.previous().unwrap();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
play_button.connect_clicked(clone!(@strong player => move |_| {
|
||||||
|
if let Some(player) = &*player.borrow() {
|
||||||
|
player.play_pause();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
next_button.connect_clicked(clone!(@strong player => move |_| {
|
||||||
|
if let Some(player) = &*player.borrow() {
|
||||||
|
player.next().unwrap();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
Self {
|
||||||
|
widget,
|
||||||
|
title_label,
|
||||||
|
subtitle_label,
|
||||||
|
previous_button,
|
||||||
|
play_button,
|
||||||
|
next_button,
|
||||||
|
position_label,
|
||||||
|
duration_label,
|
||||||
|
play_image,
|
||||||
|
pause_image,
|
||||||
|
player: player,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_player(&self, player: Option<Rc<Player>>) {
|
||||||
|
self.player.replace(player.clone());
|
||||||
|
|
||||||
|
if let Some(player) = player {
|
||||||
|
let playlist = Rc::new(RefCell::new(Vec::<PlaylistItem>::new()));
|
||||||
|
|
||||||
|
player.set_playlist_cb(clone!(
|
||||||
|
@strong player,
|
||||||
|
@strong self.widget as widget,
|
||||||
|
@strong self.previous_button as previous_button,
|
||||||
|
@strong self.next_button as next_button,
|
||||||
|
@strong playlist
|
||||||
|
=> move |new_playlist| {
|
||||||
|
widget.set_reveal_child(!new_playlist.is_empty());
|
||||||
|
playlist.replace(new_playlist);
|
||||||
|
previous_button.set_sensitive(player.has_previous());
|
||||||
|
next_button.set_sensitive(player.has_next());
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
player.set_track_cb(clone!(
|
||||||
|
@strong player,
|
||||||
|
@strong playlist,
|
||||||
|
@strong self.previous_button as previous_button,
|
||||||
|
@strong self.next_button as next_button,
|
||||||
|
@strong self.title_label as title_label,
|
||||||
|
@strong self.subtitle_label as subtitle_label,
|
||||||
|
@strong self.position_label as position_label
|
||||||
|
=> move |current_item, current_track| {
|
||||||
|
previous_button.set_sensitive(player.has_previous());
|
||||||
|
next_button.set_sensitive(player.has_next());
|
||||||
|
|
||||||
|
let item = &playlist.borrow()[current_item];
|
||||||
|
let track = &item.tracks[current_track];
|
||||||
|
|
||||||
|
let mut parts = Vec::<String>::new();
|
||||||
|
for part in &track.work_parts {
|
||||||
|
parts.push(item.recording.work.parts[*part].title.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut title = item.recording.work.get_title();
|
||||||
|
if !parts.is_empty() {
|
||||||
|
title = format!("{}: {}", title, parts.join(", "));
|
||||||
|
}
|
||||||
|
|
||||||
|
title_label.set_text(&title);
|
||||||
|
subtitle_label.set_text(&item.recording.get_performers());
|
||||||
|
position_label.set_text("0:00");
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
player.set_duration_cb(clone!(
|
||||||
|
@strong self.duration_label as duration_label
|
||||||
|
=> move |ms| {
|
||||||
|
let min = ms / 60000;
|
||||||
|
let sec = (ms % 60000) / 1000;
|
||||||
|
duration_label.set_text(&format!("{}:{:02}", min, sec));
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
player.set_playing_cb(clone!(
|
||||||
|
@strong self.play_button as play_button,
|
||||||
|
@strong self.play_image as play_image,
|
||||||
|
@strong self.pause_image as pause_image
|
||||||
|
=> move |playing| {
|
||||||
|
if let Some(child) = play_button.get_child() {
|
||||||
|
play_button.remove( &child);
|
||||||
|
}
|
||||||
|
|
||||||
|
play_button.add(if playing {
|
||||||
|
&pause_image
|
||||||
|
} else {
|
||||||
|
&play_image
|
||||||
|
});
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
player.set_position_cb(clone!(
|
||||||
|
@strong self.position_label as position_label
|
||||||
|
=> move |ms| {
|
||||||
|
let min = ms / 60000;
|
||||||
|
let sec = (ms % 60000) / 1000;
|
||||||
|
position_label.set_text(&format!("{}:{:02}", min, sec));
|
||||||
|
}
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
self.widget.set_reveal_child(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -19,6 +19,7 @@ pub struct Window {
|
||||||
sidebar_box: gtk::Box,
|
sidebar_box: gtk::Box,
|
||||||
poe_list: Rc<PoeList>,
|
poe_list: Rc<PoeList>,
|
||||||
navigator: Rc<Navigator>,
|
navigator: Rc<Navigator>,
|
||||||
|
player_bar: PlayerBar,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Window {
|
impl Window {
|
||||||
|
|
@ -28,6 +29,7 @@ impl Window {
|
||||||
get_widget!(builder, libhandy::ApplicationWindow, window);
|
get_widget!(builder, libhandy::ApplicationWindow, window);
|
||||||
get_widget!(builder, gtk::Stack, stack);
|
get_widget!(builder, gtk::Stack, stack);
|
||||||
get_widget!(builder, gtk::Button, select_music_library_path_button);
|
get_widget!(builder, gtk::Button, select_music_library_path_button);
|
||||||
|
get_widget!(builder, gtk::Box, content_box);
|
||||||
get_widget!(builder, libhandy::Leaflet, leaflet);
|
get_widget!(builder, libhandy::Leaflet, leaflet);
|
||||||
get_widget!(builder, gtk::Button, add_button);
|
get_widget!(builder, gtk::Button, add_button);
|
||||||
get_widget!(builder, gtk::Box, sidebar_box);
|
get_widget!(builder, gtk::Box, sidebar_box);
|
||||||
|
|
@ -42,6 +44,9 @@ impl Window {
|
||||||
leaflet.set_visible_child(&sidebar_box);
|
leaflet.set_visible_child(&sidebar_box);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
let player_bar = PlayerBar::new();
|
||||||
|
content_box.add(&player_bar.widget);
|
||||||
|
|
||||||
let result = Rc::new(Self {
|
let result = Rc::new(Self {
|
||||||
backend,
|
backend,
|
||||||
window,
|
window,
|
||||||
|
|
@ -50,6 +55,7 @@ impl Window {
|
||||||
sidebar_box,
|
sidebar_box,
|
||||||
poe_list,
|
poe_list,
|
||||||
navigator,
|
navigator,
|
||||||
|
player_bar,
|
||||||
});
|
});
|
||||||
|
|
||||||
result.window.set_application(Some(app));
|
result.window.set_application(Some(app));
|
||||||
|
|
@ -290,6 +296,9 @@ impl Window {
|
||||||
BackendState::Ready => {
|
BackendState::Ready => {
|
||||||
clone.stack.set_visible_child_name("content");
|
clone.stack.set_visible_child_name("content");
|
||||||
clone.poe_list.clone().reload();
|
clone.poe_list.clone().reload();
|
||||||
|
|
||||||
|
let player = clone.backend.get_player().unwrap();
|
||||||
|
clone.player_bar.set_player(Some(player));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue