Implement mimetype support reporting for MPRIS, start implementation of bulk of getters.
Implemented the following getters for MPRIS: - CanQuit - CanRaise - HasTrackList - Identity - DesktopEntry - SupportedUriSchemas - SupportedMimeTypes - Metadata - CanPlay / CanPause / CanSeek - CanControl - PlaybackStatus Implemented a koto_push_track_info_to_builder function that enables us to easily push KotoIndexedTrack as well as associated album and artist info to a GVariantBuilder for use in a GVariant for various getters. Implemented a koto_update_mpris_info_for_track function that emits a signal for PropertiesChanged + "Metadata" when our track info changes.
This commit is contained in:
parent
b1f4460a2e
commit
07c3c00f1e
17 changed files with 734 additions and 69 deletions
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"app-id" : "com.github.joshstrobl.koto",
|
||||
"runtime" : "org.gnome.Platform",
|
||||
"runtime-version" : "3.38",
|
||||
"runtime-version" : "40",
|
||||
"sdk" : "org.gnome.Sdk",
|
||||
"command" : "com.github.joshstrobl.koto",
|
||||
"finish-args" : [
|
||||
|
@ -29,9 +29,12 @@
|
|||
"sources" : [
|
||||
{
|
||||
"type" : "git",
|
||||
"url" : "file:///home/joshua/Code/Personal/Koto"
|
||||
"url" : "https://github.com/JoshStrobl/koto.git"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
"build-options" : {
|
||||
"env" : { }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,11 +18,14 @@
|
|||
#include <gstreamer-1.0/gst/gst.h>
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
#include "db/cartographer.h"
|
||||
#include "playlist/current.h"
|
||||
#include "playlist/playlist.h"
|
||||
#include "playback/engine.h"
|
||||
#include "koto-button.h"
|
||||
#include "koto-config.h"
|
||||
#include "koto-playerbar.h"
|
||||
|
||||
extern KotoCurrentPlaylist *current_playlist;
|
||||
extern KotoCartographer* koto_maps;
|
||||
extern KotoPlaybackEngine *playback_engine;
|
||||
|
||||
|
@ -85,9 +88,18 @@ static void koto_playerbar_constructed(GObject *obj) {
|
|||
gtk_range_set_increments(GTK_RANGE(self->progress_bar), 1, 1);
|
||||
gtk_range_set_round_digits(GTK_RANGE(self->progress_bar), 1);
|
||||
|
||||
GtkEventController *scroll_controller = gtk_event_controller_scroll_new(GTK_EVENT_CONTROLLER_SCROLL_HORIZONTAL);
|
||||
g_signal_connect(scroll_controller, "scroll-begin", G_CALLBACK(koto_playerbar_handle_progressbar_scroll_begin), self);
|
||||
gtk_widget_add_controller(GTK_WIDGET(self->progress_bar), scroll_controller);
|
||||
|
||||
GtkGesture *press_controller = gtk_gesture_click_new(); // Create a new GtkGestureLongPress
|
||||
gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(press_controller), 1); // Set to left click
|
||||
|
||||
g_signal_connect(GTK_GESTURE(press_controller), "begin", G_CALLBACK(koto_playerbar_handle_progressbar_gesture_begin), self);
|
||||
g_signal_connect(GTK_GESTURE(press_controller), "end", G_CALLBACK(koto_playerbar_handle_progressbar_gesture_end), self);
|
||||
g_signal_connect(press_controller, "pressed", G_CALLBACK(koto_playerbar_handle_progressbar_pressed), self);
|
||||
g_signal_connect(press_controller, "unpaired-release", G_CALLBACK(koto_playerbar_handle_progressbar_released), self);
|
||||
//g_signal_connect(press_controller, "unpaired-release", G_CALLBACK(koto_playerbar_handle_progressbar_unpaired_release), self);
|
||||
|
||||
gtk_widget_add_controller(GTK_WIDGET(self->progress_bar), GTK_EVENT_CONTROLLER(press_controller));
|
||||
|
||||
g_signal_connect(GTK_RANGE(self->progress_bar), "value-changed", G_CALLBACK(koto_playerbar_handle_progressbar_value_changed), self);
|
||||
|
@ -167,30 +179,29 @@ void koto_playerbar_create_primary_controls(KotoPlayerBar* bar) {
|
|||
bar->play_pause_button = koto_button_new_with_icon("", "media-playback-start-symbolic", "media-playback-pause-symbolic", KOTO_BUTTON_PIXBUF_SIZE_LARGE); // TODO: Have this take in a state and switch to a different icon if necessary
|
||||
bar->forward_button = koto_button_new_with_icon("", "media-skip-forward-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
|
||||
|
||||
if (bar->back_button != NULL) {
|
||||
if (KOTO_IS_BUTTON(bar->back_button)) {
|
||||
gtk_box_append(GTK_BOX(bar->primary_controls_section), GTK_WIDGET(bar->back_button));
|
||||
|
||||
GtkGesture *back_controller = gtk_gesture_click_new(); // Create a new GtkGestureClick
|
||||
g_signal_connect(back_controller, "pressed", G_CALLBACK(koto_playerbar_go_backwards), NULL);
|
||||
gtk_widget_add_controller(GTK_WIDGET(bar->back_button), GTK_EVENT_CONTROLLER(back_controller));
|
||||
}
|
||||
|
||||
if (bar->play_pause_button != NULL) {
|
||||
if (KOTO_IS_BUTTON(bar->play_pause_button)) {
|
||||
gtk_box_append(GTK_BOX(bar->primary_controls_section), GTK_WIDGET(bar->play_pause_button));
|
||||
|
||||
GtkGesture *controller = gtk_gesture_click_new(); // Create a new GtkGestureClick
|
||||
g_signal_connect(controller, "pressed", G_CALLBACK(koto_playerbar_toggle_play_pause), NULL);
|
||||
gtk_widget_add_controller(GTK_WIDGET(bar->play_pause_button), GTK_EVENT_CONTROLLER(controller));
|
||||
}
|
||||
|
||||
if (bar->forward_button != NULL) {
|
||||
if (KOTO_IS_BUTTON(bar->forward_button)) {
|
||||
gtk_box_append(GTK_BOX(bar->primary_controls_section), GTK_WIDGET(bar->forward_button));
|
||||
|
||||
GtkGesture *forwards_controller = gtk_gesture_click_new(); // Create a new GtkGestureClick
|
||||
g_signal_connect(forwards_controller, "pressed", G_CALLBACK(koto_playerbar_go_forwards), NULL);
|
||||
gtk_widget_add_controller(GTK_WIDGET(bar->forward_button), GTK_EVENT_CONTROLLER(forwards_controller));
|
||||
}
|
||||
|
||||
|
||||
GtkGesture *back_controller = gtk_gesture_click_new(); // Create a new GtkGestureClick
|
||||
g_signal_connect(back_controller, "pressed", G_CALLBACK(koto_playerbar_go_backwards), NULL);
|
||||
gtk_widget_add_controller(GTK_WIDGET(bar->back_button), GTK_EVENT_CONTROLLER(back_controller));
|
||||
|
||||
GtkGesture *controller = gtk_gesture_click_new(); // Create a new GtkGestureClick
|
||||
g_signal_connect(controller, "pressed", G_CALLBACK(koto_playerbar_toggle_play_pause), NULL);
|
||||
gtk_widget_add_controller(GTK_WIDGET(bar->play_pause_button), GTK_EVENT_CONTROLLER(controller));
|
||||
|
||||
GtkGesture *forwards_controller = gtk_gesture_click_new(); // Create a new GtkGestureClick
|
||||
g_signal_connect(forwards_controller, "pressed", G_CALLBACK(koto_playerbar_go_forwards), NULL);
|
||||
gtk_widget_add_controller(GTK_WIDGET(bar->forward_button), GTK_EVENT_CONTROLLER(forwards_controller));
|
||||
}
|
||||
|
||||
void koto_playerbar_create_secondary_controls(KotoPlayerBar* bar) {
|
||||
|
@ -198,30 +209,42 @@ void koto_playerbar_create_secondary_controls(KotoPlayerBar* bar) {
|
|||
bar->shuffle_button = koto_button_new_with_icon("", "media-playlist-shuffle-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
|
||||
bar->playlist_button = koto_button_new_with_icon("", "playlist-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
|
||||
bar->eq_button = koto_button_new_with_icon("", "multimedia-equalizer-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
|
||||
|
||||
bar->volume_button = gtk_volume_button_new(); // Have this take in a state and switch to a different icon if necessary
|
||||
g_object_set(bar->volume_button, "use-symbolic", TRUE, NULL);
|
||||
gtk_scale_button_set_value(GTK_SCALE_BUTTON(bar->volume_button), 0.5);
|
||||
|
||||
g_signal_connect(GTK_SCALE_BUTTON(bar->volume_button), "value-changed", G_CALLBACK(koto_playerbar_handle_volume_button_change), bar);
|
||||
|
||||
if (bar->repeat_button != NULL) {
|
||||
if (KOTO_IS_BUTTON(bar->repeat_button)) {
|
||||
gtk_box_append(GTK_BOX(bar->secondary_controls_section), GTK_WIDGET(bar->repeat_button));
|
||||
|
||||
GtkGesture *controller = gtk_gesture_click_new(); // Create a new GtkGestureClick
|
||||
g_signal_connect(controller, "pressed", G_CALLBACK(koto_playerbar_toggle_track_repeat), bar);
|
||||
gtk_widget_add_controller(GTK_WIDGET(bar->repeat_button), GTK_EVENT_CONTROLLER(controller));
|
||||
}
|
||||
|
||||
if (bar->shuffle_button != NULL) {
|
||||
if (KOTO_IS_BUTTON(bar->shuffle_button)) {
|
||||
gtk_box_append(GTK_BOX(bar->secondary_controls_section), GTK_WIDGET(bar->shuffle_button));
|
||||
|
||||
GtkGesture *controller = gtk_gesture_click_new(); // Create a new GtkGestureClick
|
||||
g_signal_connect(controller, "pressed", G_CALLBACK(koto_playerbar_toggle_playlist_shuffle), bar);
|
||||
gtk_widget_add_controller(GTK_WIDGET(bar->shuffle_button), GTK_EVENT_CONTROLLER(controller));
|
||||
}
|
||||
|
||||
if (bar->playlist_button != NULL) {
|
||||
if (KOTO_IS_BUTTON(bar->playlist_button)) {
|
||||
gtk_box_append(GTK_BOX(bar->secondary_controls_section), GTK_WIDGET(bar->playlist_button));
|
||||
}
|
||||
|
||||
if (bar->eq_button != NULL) {
|
||||
if (KOTO_IS_BUTTON(bar->eq_button)) {
|
||||
gtk_box_append(GTK_BOX(bar->secondary_controls_section), GTK_WIDGET(bar->eq_button));
|
||||
}
|
||||
|
||||
if (bar->volume_button != NULL) {
|
||||
if (GTK_IS_VOLUME_BUTTON(bar->volume_button)) {
|
||||
GtkAdjustment *granular_volume_change = gtk_adjustment_new(0.5, 0, 1.0, 0.02, 0.02, 0.02);
|
||||
g_object_set(bar->volume_button, "use-symbolic", TRUE, NULL);
|
||||
gtk_range_set_round_digits(GTK_RANGE(bar->volume_button), FALSE);
|
||||
gtk_scale_button_set_adjustment(GTK_SCALE_BUTTON(bar->volume_button), granular_volume_change); // Set our adjustment
|
||||
gtk_box_append(GTK_BOX(bar->secondary_controls_section), bar->volume_button);
|
||||
|
||||
g_signal_connect(GTK_SCALE_BUTTON(bar->volume_button), "value-changed", G_CALLBACK(koto_playerbar_handle_volume_button_change), bar);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -265,6 +288,35 @@ void koto_playerbar_handle_is_paused(KotoPlaybackEngine *engine, gpointer user_d
|
|||
koto_button_show_image(bar->play_pause_button, FALSE); // Set to FALSE to show play as the next action
|
||||
}
|
||||
|
||||
void koto_playerbar_handle_progressbar_scroll_begin(GtkEventControllerScroll *controller, gpointer data){
|
||||
(void) controller;
|
||||
g_message("scroll-begin");
|
||||
}
|
||||
|
||||
void koto_playerbar_handle_progressbar_gesture_begin(GtkGesture *gesture, GdkEventSequence *seq, gpointer data) {
|
||||
(void) gesture; (void) seq;
|
||||
KotoPlayerBar *bar = data;
|
||||
|
||||
if (!KOTO_IS_PLAYERBAR(bar)) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_message("Begin");
|
||||
bar->is_progressbar_seeking = TRUE;
|
||||
}
|
||||
|
||||
void koto_playerbar_handle_progressbar_gesture_end(GtkGesture *gesture, GdkEventSequence *seq, gpointer data) {
|
||||
(void) gesture; (void) seq;
|
||||
KotoPlayerBar *bar = data;
|
||||
|
||||
g_message("Ended");
|
||||
|
||||
if (!KOTO_IS_PLAYERBAR(bar)) {
|
||||
return;
|
||||
}
|
||||
bar->is_progressbar_seeking = FALSE;
|
||||
}
|
||||
|
||||
void koto_playerbar_handle_progressbar_pressed(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data) {
|
||||
(void) gesture; (void) n_press; (void) x; (void) y;
|
||||
KotoPlayerBar *bar = data;
|
||||
|
@ -273,21 +325,10 @@ void koto_playerbar_handle_progressbar_pressed(GtkGestureClick *gesture, int n_p
|
|||
return;
|
||||
}
|
||||
|
||||
g_message("Pressed");
|
||||
bar->is_progressbar_seeking = TRUE;
|
||||
}
|
||||
|
||||
void koto_playerbar_handle_progressbar_released(GtkGestureClick *gesture, double x, double y, guint button, GdkEventSequence *seq, gpointer data) {
|
||||
(void) gesture; (void) x; (void) y; (void) button; (void) seq;
|
||||
KotoPlayerBar *bar = data;
|
||||
|
||||
if (!KOTO_IS_PLAYERBAR(bar)) {
|
||||
return;
|
||||
}
|
||||
|
||||
bar->is_progressbar_seeking = FALSE;
|
||||
}
|
||||
|
||||
|
||||
void koto_playerbar_handle_progressbar_value_changed(GtkRange *progress_bar, gpointer data) {
|
||||
KotoPlayerBar *bar = data;
|
||||
|
||||
|
@ -301,6 +342,7 @@ void koto_playerbar_handle_progressbar_value_changed(GtkRange *progress_bar, gpo
|
|||
|
||||
int desired_position = (int) gtk_range_get_value(progress_bar);
|
||||
|
||||
g_message("value changed");
|
||||
koto_playback_engine_set_position(playback_engine, desired_position); // Update our position
|
||||
}
|
||||
|
||||
|
@ -366,6 +408,41 @@ void koto_playerbar_toggle_play_pause(GtkGestureClick *gesture, int n_press, dou
|
|||
koto_playback_engine_toggle(playback_engine);
|
||||
}
|
||||
|
||||
void koto_playerbar_toggle_playlist_shuffle(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data) {
|
||||
(void) gesture; (void) n_press; (void) x; (void) y;
|
||||
KotoPlayerBar *bar = data;
|
||||
|
||||
if (!KOTO_IS_PLAYERBAR(bar)) {
|
||||
return;
|
||||
}
|
||||
|
||||
KotoPlaylist *playlist = koto_current_playlist_get_playlist(current_playlist);
|
||||
|
||||
if (!KOTO_IS_PLAYLIST(playlist)) { // Don't have a playlist currently
|
||||
gtk_widget_remove_css_class(GTK_WIDGET(bar->shuffle_button), "active"); // Remove active state
|
||||
return;
|
||||
}
|
||||
|
||||
gboolean currently_shuffling = FALSE;
|
||||
g_object_get(playlist, "is-shuffle-enabled", ¤tly_shuffling, NULL); // Get the current is-shuffle-enabled
|
||||
|
||||
(currently_shuffling) ? gtk_widget_remove_css_class(GTK_WIDGET(bar->shuffle_button), "active") : gtk_widget_add_css_class(GTK_WIDGET(bar->shuffle_button), "active");
|
||||
g_object_set(playlist, "is-shuffle-enabled", !currently_shuffling, NULL); // Provide inverse value
|
||||
}
|
||||
|
||||
void koto_playerbar_toggle_track_repeat(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data) {
|
||||
(void) gesture; (void) n_press; (void) x; (void) y;
|
||||
KotoPlayerBar *bar = data;
|
||||
|
||||
if (koto_playback_engine_get_track_repeat(playback_engine)) { // Toggled on at the moment
|
||||
gtk_widget_remove_css_class(GTK_WIDGET(bar->repeat_button), "active"); // Remove active CSS class
|
||||
} else {
|
||||
gtk_widget_add_css_class(GTK_WIDGET(bar->repeat_button), "active"); // Add active CSS class
|
||||
}
|
||||
|
||||
koto_playback_engine_toggle_track_repeat(playback_engine); // Toggle the state
|
||||
}
|
||||
|
||||
void koto_playerbar_update_track_info(KotoPlaybackEngine *engine, gpointer user_data) {
|
||||
if (!KOTO_IS_PLAYBACK_ENGINE(engine)) {
|
||||
return;
|
||||
|
|
|
@ -35,8 +35,10 @@ void koto_playerbar_go_backwards(GtkGestureClick *gesture, int n_press, double x
|
|||
void koto_playerbar_go_forwards(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data);
|
||||
void koto_playerbar_handle_is_playing(KotoPlaybackEngine *engine, gpointer user_data);
|
||||
void koto_playerbar_handle_is_paused(KotoPlaybackEngine *engine, gpointer user_data);
|
||||
void koto_playerbar_handle_progressbar_scroll_begin(GtkEventControllerScroll *controller, gpointer data);
|
||||
void koto_playerbar_handle_progressbar_gesture_begin(GtkGesture *gesture, GdkEventSequence *seq, gpointer data);
|
||||
void koto_playerbar_handle_progressbar_gesture_end(GtkGesture *gesture, GdkEventSequence *seq, gpointer data);
|
||||
void koto_playerbar_handle_progressbar_pressed(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data);
|
||||
void koto_playerbar_handle_progressbar_released(GtkGestureClick *gesture, double x, double y, guint button, GdkEventSequence *seq, gpointer data);
|
||||
void koto_playerbar_handle_progressbar_value_changed(GtkRange *progress_bar, gpointer data);
|
||||
void koto_playerbar_handle_tick_duration(KotoPlaybackEngine *engine, gpointer user_data);
|
||||
void koto_playerbar_handle_tick_track(KotoPlaybackEngine *engine, gpointer user_data);
|
||||
|
@ -45,6 +47,8 @@ void koto_playerbar_reset_progressbar(KotoPlayerBar* bar);
|
|||
void koto_playerbar_set_progressbar_duration(KotoPlayerBar* bar, gint64 duration);
|
||||
void koto_playerbar_set_progressbar_value(KotoPlayerBar* bar, gint64 progress);
|
||||
void koto_playerbar_toggle_play_pause(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data);
|
||||
void koto_playerbar_toggle_playlist_shuffle(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data);
|
||||
void koto_playerbar_toggle_track_repeat(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data);
|
||||
void koto_playerbar_update_track_info(KotoPlaybackEngine *engine, gpointer user_data);
|
||||
|
||||
G_END_DECLS
|
||||
|
|
|
@ -68,6 +68,18 @@ gchar* koto_utils_get_filename_without_extension(gchar *filename) {
|
|||
return stripped_file_name;
|
||||
}
|
||||
|
||||
gchar *koto_utils_replace_string_all(gchar *str, gchar *find, gchar *repl) {
|
||||
gchar *cleaned_string = "";
|
||||
gchar **split = g_strsplit(str, find, -1); // Split on find
|
||||
|
||||
for (guint i = 0; i < g_strv_length(split); i++) { // For each split
|
||||
cleaned_string = g_strjoin(repl, cleaned_string, split[i], NULL); // Join the strings with our replace string
|
||||
}
|
||||
|
||||
g_strfreev(split);
|
||||
return cleaned_string;
|
||||
}
|
||||
|
||||
gchar* koto_utils_unquote_string(gchar *s) {
|
||||
gchar *new_s = NULL;
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ G_BEGIN_DECLS
|
|||
|
||||
GtkWidget* koto_utils_create_image_from_filepath(gchar *filepath, gchar *fallback_icon, guint width, guint height);
|
||||
gchar* koto_utils_get_filename_without_extension(gchar *filename);
|
||||
gchar *koto_utils_replace_string_all(gchar *str, gchar *find, gchar *repl);
|
||||
gchar* koto_utils_unquote_string(gchar *s);
|
||||
|
||||
G_END_DECLS
|
||||
|
|
38
src/main.c
38
src/main.c
|
@ -19,33 +19,43 @@
|
|||
#include <gstreamer-1.0/gst/gst.h>
|
||||
#include "db/cartographer.h"
|
||||
#include "db/db.h"
|
||||
#include "playback/mimes.h"
|
||||
#include "playback/mpris.h"
|
||||
|
||||
#include "koto-config.h"
|
||||
#include "koto-window.h"
|
||||
|
||||
extern guint mpris_bus_id;
|
||||
extern GDBusNodeInfo *introspection_data;
|
||||
|
||||
extern KotoCartographer *koto_maps;
|
||||
extern sqlite3 *koto_db;
|
||||
|
||||
extern GHashTable *supported_mimes_hash;
|
||||
extern GList *supported_mimes;
|
||||
|
||||
GtkApplication *app = NULL;
|
||||
GtkWindow *main_window;
|
||||
|
||||
static void on_activate (GtkApplication *app) {
|
||||
g_assert(GTK_IS_APPLICATION (app));
|
||||
|
||||
GtkWindow *window;
|
||||
|
||||
window = gtk_application_get_active_window (app);
|
||||
if (window == NULL) {
|
||||
window = g_object_new(KOTO_TYPE_WINDOW, "application", app, "default-width", 1200, "default-height", 675, NULL);
|
||||
main_window = gtk_application_get_active_window (app);
|
||||
if (main_window == NULL) {
|
||||
main_window = g_object_new(KOTO_TYPE_WINDOW, "application", app, "default-width", 1200, "default-height", 675, NULL);
|
||||
}
|
||||
|
||||
gtk_window_present(window);
|
||||
gtk_window_present(main_window);
|
||||
}
|
||||
|
||||
static void on_shutdown(GtkApplication *app) {
|
||||
(void) app;
|
||||
close_db(); // Close the database
|
||||
g_bus_unown_name(mpris_bus_id);
|
||||
g_dbus_node_info_unref(introspection_data);
|
||||
}
|
||||
|
||||
int main (int argc, char *argv[]) {
|
||||
g_autoptr(GtkApplication) app = NULL;
|
||||
int ret;
|
||||
|
||||
/* Set up gettext translations */
|
||||
|
@ -56,8 +66,22 @@ int main (int argc, char *argv[]) {
|
|||
gtk_init();
|
||||
gst_init(&argc, &argv);
|
||||
|
||||
supported_mimes_hash = g_hash_table_new(g_str_hash, g_str_equal);
|
||||
supported_mimes = NULL; // Ensure our mimes GList is initialized
|
||||
koto_playback_engine_get_supported_mimetypes(supported_mimes);
|
||||
|
||||
g_message("Length: %d", g_list_length(supported_mimes));
|
||||
|
||||
koto_maps = koto_cartographer_new(); // Create our new cartographer and their collection of maps
|
||||
open_db(); // Open our database
|
||||
setup_mpris_interfaces(); // Set up our MPRIS interfaces
|
||||
|
||||
GList *md;
|
||||
md = NULL;
|
||||
for (md = supported_mimes; md != NULL; md = md->next) {
|
||||
g_message("Mimetype: %s", (gchar*) md->data);
|
||||
}
|
||||
g_list_free(md);
|
||||
|
||||
app = gtk_application_new ("com.github.joshstrobl.koto", G_APPLICATION_FLAGS_NONE);
|
||||
g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
|
||||
|
|
|
@ -12,6 +12,8 @@ koto_sources = [
|
|||
'pages/music/disc-view.c',
|
||||
'pages/music/music-local.c',
|
||||
'playback/engine.c',
|
||||
'playback/mimes.c',
|
||||
'playback/mpris.c',
|
||||
'playlist/current.c',
|
||||
'playlist/playlist.c',
|
||||
'main.c',
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include "../playlist/current.h"
|
||||
#include "../indexer/structs.h"
|
||||
#include "engine.h"
|
||||
#include "mpris.h"
|
||||
|
||||
enum {
|
||||
SIGNAL_IS_PLAYING,
|
||||
|
@ -52,7 +53,6 @@ struct _KotoPlaybackEngine {
|
|||
|
||||
gboolean is_muted;
|
||||
gboolean is_repeat_enabled;
|
||||
gboolean is_shuffle_enabled;
|
||||
|
||||
gboolean is_playing;
|
||||
|
||||
|
@ -163,6 +163,7 @@ static void koto_playback_engine_init(KotoPlaybackEngine *self) {
|
|||
self->suppress_video = gst_element_factory_make("fakesink", "suppress-video");
|
||||
|
||||
g_object_set(self->playbin, "video-sink", self->suppress_video, NULL);
|
||||
self->volume = 0.5;
|
||||
koto_playback_engine_set_volume(self, 0.5);
|
||||
|
||||
gst_bin_add(GST_BIN(self->player), self->playbin);
|
||||
|
@ -175,7 +176,6 @@ static void koto_playback_engine_init(KotoPlaybackEngine *self) {
|
|||
|
||||
self->is_muted = FALSE;
|
||||
self->is_repeat_enabled = FALSE;
|
||||
self->is_shuffle_enabled = FALSE;
|
||||
self->tick_duration_timer_running = FALSE;
|
||||
self->tick_track_timer_running = FALSE;
|
||||
|
||||
|
@ -205,7 +205,7 @@ void koto_playback_engine_current_playlist_changed() {
|
|||
return;
|
||||
}
|
||||
|
||||
koto_playback_engine_set_track_by_uuid(playback_engine, koto_playlist_get_current_uuid(playlist)); // Get the current UUID
|
||||
koto_playback_engine_set_track_by_uuid(playback_engine, koto_playlist_go_to_next(playlist)); // Go to "next" which is the first track
|
||||
}
|
||||
|
||||
void koto_playback_engine_forwards(KotoPlaybackEngine *self) {
|
||||
|
@ -215,7 +215,11 @@ void koto_playback_engine_forwards(KotoPlaybackEngine *self) {
|
|||
return;
|
||||
}
|
||||
|
||||
koto_playback_engine_set_track_by_uuid(self, koto_playlist_go_to_next(playlist));
|
||||
if (self->is_repeat_enabled) { // Is repeat enabled
|
||||
koto_playback_engine_set_position(self, 0); // Set position back to 0 to repeat the track
|
||||
} else { // Repeat not enabled
|
||||
koto_playback_engine_set_track_by_uuid(self, koto_playlist_go_to_next(playlist));
|
||||
}
|
||||
}
|
||||
|
||||
KotoIndexedTrack* koto_playback_engine_get_current_track(KotoPlaybackEngine *self) {
|
||||
|
@ -231,13 +235,6 @@ gint64 koto_playback_engine_get_duration(KotoPlaybackEngine *self) {
|
|||
return duration;
|
||||
}
|
||||
|
||||
GstState koto_playback_engine_get_state(KotoPlaybackEngine *self) {
|
||||
GstState current_state;
|
||||
gst_element_get_state(self->player, ¤t_state, NULL, GST_SECOND); // Get the current state, allowing up to a second to get it
|
||||
|
||||
return current_state;
|
||||
}
|
||||
|
||||
gint64 koto_playback_engine_get_progress(KotoPlaybackEngine *self) {
|
||||
gint64 progress = 0;
|
||||
if (gst_element_query_position(self->player, GST_FORMAT_TIME, &progress)) {
|
||||
|
@ -247,13 +244,26 @@ gint64 koto_playback_engine_get_progress(KotoPlaybackEngine *self) {
|
|||
return progress;
|
||||
}
|
||||
|
||||
GstState koto_playback_engine_get_state(KotoPlaybackEngine *self) {
|
||||
GstState current_state;
|
||||
gst_element_get_state(self->player, ¤t_state, NULL, GST_SECOND); // Get the current state, allowing up to a second to get it
|
||||
|
||||
return current_state;
|
||||
}
|
||||
|
||||
gboolean koto_playback_engine_get_track_repeat(KotoPlaybackEngine *self) {
|
||||
return self->is_repeat_enabled;
|
||||
}
|
||||
|
||||
gboolean koto_playback_engine_monitor_changed(GstBus *bus, GstMessage *msg, gpointer user_data) {
|
||||
(void) bus;
|
||||
KotoPlaybackEngine *self = user_data;
|
||||
|
||||
switch (GST_MESSAGE_TYPE(msg)) {
|
||||
case GST_MESSAGE_ASYNC_DONE:
|
||||
case GST_MESSAGE_DURATION_CHANGED: { // Duration changed
|
||||
koto_playback_engine_tick_duration(self);
|
||||
koto_playback_engine_tick_track(self);
|
||||
break;
|
||||
}
|
||||
case GST_MESSAGE_STATE_CHANGED: { // State changed
|
||||
|
@ -263,6 +273,7 @@ gboolean koto_playback_engine_monitor_changed(GstBus *bus, GstMessage *msg, gpoi
|
|||
gst_message_parse_state_changed(msg, &old_state, &new_state, &pending_state);
|
||||
|
||||
if (new_state == GST_STATE_PLAYING) { // Now playing
|
||||
koto_playback_engine_tick_duration(self);
|
||||
g_signal_emit(self, playback_engine_signals[SIGNAL_IS_PLAYING], 0); // Emit our is playing state signal
|
||||
} else if (new_state == GST_STATE_PAUSED) { // Now paused
|
||||
g_signal_emit(self, playback_engine_signals[SIGNAL_IS_PAUSED], 0); // Emit our is paused state signal
|
||||
|
@ -308,6 +319,10 @@ void koto_playback_engine_set_position(KotoPlaybackEngine *self, int position) {
|
|||
gst_element_seek_simple(self->playbin, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, position * NS);
|
||||
}
|
||||
|
||||
void koto_playback_engine_set_track_repeat(KotoPlaybackEngine *self, gboolean enable) {
|
||||
self->is_repeat_enabled = enable;
|
||||
}
|
||||
|
||||
void koto_playback_engine_set_track_by_uuid(KotoPlaybackEngine *self, gchar *track_uuid) {
|
||||
if (track_uuid == NULL) {
|
||||
return;
|
||||
|
@ -335,17 +350,20 @@ void koto_playback_engine_set_track_by_uuid(KotoPlaybackEngine *self, gchar *tra
|
|||
|
||||
// TODO: Add prior position state setting here, like picking up at a specific part of an audiobook or podcast
|
||||
koto_playback_engine_set_position(self, 0);
|
||||
koto_playback_engine_set_volume(self, self->volume); // Re-enforce our volume on the updated playbin
|
||||
|
||||
g_signal_emit(self, playback_engine_signals[SIGNAL_TRACK_CHANGE], 0); // Emit our track change signal
|
||||
koto_update_mpris_info_for_track(self->current_track);
|
||||
}
|
||||
|
||||
void koto_playback_engine_set_volume(KotoPlaybackEngine *self, gdouble volume) {
|
||||
g_object_set(self->playbin, "volume", volume, NULL);
|
||||
self->volume = volume;
|
||||
g_object_set(self->playbin, "volume", self->volume, NULL);
|
||||
}
|
||||
|
||||
void koto_playback_engine_stop(KotoPlaybackEngine *self) {
|
||||
gst_element_set_state(self->player, GST_STATE_NULL);
|
||||
GstPad *pad = gst_element_get_static_pad(self->playbin, "audio-sink"); // Get the static pad of the audio element
|
||||
GstPad *pad = gst_element_get_static_pad(self->player, "sink"); // Get the static pad of the audio element
|
||||
|
||||
if (!GST_IS_PAD(pad)) {
|
||||
return;
|
||||
|
@ -386,6 +404,10 @@ gboolean koto_playback_engine_tick_track(gpointer user_data) {
|
|||
return self->is_playing;
|
||||
}
|
||||
|
||||
void koto_playback_engine_toggle_track_repeat(KotoPlaybackEngine *self) {
|
||||
self->is_repeat_enabled = !self->is_repeat_enabled;
|
||||
}
|
||||
|
||||
KotoPlaybackEngine* koto_playback_engine_new() {
|
||||
return g_object_new(KOTO_TYPE_PLAYBACK_ENGINE, NULL);
|
||||
}
|
||||
|
|
|
@ -49,17 +49,19 @@ KotoIndexedTrack* koto_playback_engine_get_current_track(KotoPlaybackEngine *sel
|
|||
gint64 koto_playback_engine_get_duration(KotoPlaybackEngine *self);
|
||||
GstState koto_playback_engine_get_state(KotoPlaybackEngine *self);
|
||||
gint64 koto_playback_engine_get_progress(KotoPlaybackEngine *self);
|
||||
gboolean koto_playback_engine_get_track_repeat(KotoPlaybackEngine *self);
|
||||
void koto_playback_engine_mute(KotoPlaybackEngine *self);
|
||||
gboolean koto_playback_engine_monitor_changed(GstBus *bus, GstMessage *msg, gpointer user_data);
|
||||
void koto_playback_engine_pause(KotoPlaybackEngine *self);
|
||||
void koto_playback_engine_play(KotoPlaybackEngine *self);
|
||||
void koto_playback_engine_toggle(KotoPlaybackEngine *self);
|
||||
void koto_playback_engine_set_position(KotoPlaybackEngine *self, int position);
|
||||
void koto_playback_engine_set_repeat(KotoPlaybackEngine *self, gboolean enable);
|
||||
void koto_playback_engine_set_shuffle(KotoPlaybackEngine *self, gboolean enable);
|
||||
void koto_playback_engine_set_track_repeat(KotoPlaybackEngine *self, gboolean enable);
|
||||
void koto_playback_engine_set_track_by_uuid(KotoPlaybackEngine *self, gchar *track_uuid);
|
||||
void koto_playback_engine_set_volume(KotoPlaybackEngine *self, gdouble volume);
|
||||
void koto_playback_engine_stop(KotoPlaybackEngine *self);
|
||||
void koto_playback_engine_toggle_track_repeat(KotoPlaybackEngine *self);
|
||||
void koto_playback_engine_update_duration(KotoPlaybackEngine *self);
|
||||
|
||||
gboolean koto_playback_engine_tick_duration(gpointer user_data);
|
||||
gboolean koto_playback_engine_tick_track(gpointer user_data);
|
||||
|
|
66
src/playback/mimes.c
Normal file
66
src/playback/mimes.c
Normal file
|
@ -0,0 +1,66 @@
|
|||
/* mimes.c
|
||||
*
|
||||
* Copyright 2021 Joshua Strobl
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
#include <glib-2.0/glib.h>
|
||||
#include <gstreamer-1.0/gst/gst.h>
|
||||
#include "../koto-utils.h"
|
||||
|
||||
GHashTable *supported_mimes_hash = NULL;
|
||||
GList *supported_mimes = NULL;
|
||||
|
||||
gboolean koto_playback_engine_gst_caps_iter(GstCapsFeatures *features, GstStructure *structure, gpointer user_data) {
|
||||
(void) features; (void) user_data;
|
||||
gchar *caps_name = (gchar*) gst_structure_get_name(structure); // Get the name, typically a mimetype
|
||||
|
||||
if (g_str_has_prefix(caps_name, "unknown")) { // Is unknown
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (g_hash_table_contains(supported_mimes_hash, caps_name)) { // Found in list already
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
g_hash_table_insert(supported_mimes_hash, g_strdup(caps_name), TRUE);
|
||||
supported_mimes = g_list_prepend(supported_mimes, g_strdup(caps_name));
|
||||
supported_mimes = g_list_prepend(supported_mimes, g_strdup(koto_utils_replace_string_all(caps_name, "x-", "")));
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void koto_playback_engine_gst_pad_iter(gpointer list_data, gpointer user_data) {
|
||||
(void) user_data;
|
||||
GstStaticPadTemplate *templ = list_data;
|
||||
if (templ->direction == GST_PAD_SINK) { // Is a sink pad
|
||||
GstCaps *capabilities = gst_static_pad_template_get_caps(templ); // Get the capabilities
|
||||
gst_caps_foreach(capabilities, koto_playback_engine_gst_caps_iter, NULL); // Iterate over and add to mimes
|
||||
gst_caps_unref(capabilities);
|
||||
}
|
||||
}
|
||||
|
||||
void koto_playback_engine_get_supported_mimetypes() {
|
||||
// Credit for code goes to https://github.com/mopidy/mopidy/issues/812#issuecomment-75868363
|
||||
// These are GstElementFactory
|
||||
GList *elements = gst_element_factory_list_get_elements(GST_ELEMENT_FACTORY_TYPE_DEPAYLOADER | GST_ELEMENT_FACTORY_TYPE_DEMUXER | GST_ELEMENT_FACTORY_TYPE_PARSER | GST_ELEMENT_FACTORY_TYPE_DECODER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO, GST_RANK_NONE);
|
||||
|
||||
GList *ele;
|
||||
for (ele = elements; ele != NULL; ele = ele->next) { // For each of the elements
|
||||
// GList of GstStaticPadTemplate
|
||||
GList *static_pads = (GList*) gst_element_factory_get_static_pad_templates(ele->data); // Get the pads
|
||||
g_list_foreach(static_pads, koto_playback_engine_gst_pad_iter, NULL);
|
||||
}
|
||||
}
|
28
src/playback/mimes.h
Normal file
28
src/playback/mimes.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
/* mimes.h
|
||||
*
|
||||
* Copyright 2021 Joshua Strobl
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <glib-2.0/glib.h>
|
||||
#include <gstreamer-1.0/gst/gst.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
gboolean koto_bplayback_engine_gst_caps_iter(GstCapsFeatures *features, GstStructure *structure, gpointer user_data);
|
||||
void koto_playback_engine_gst_pad_iter(gpointer list_data, gpointer user_data);
|
||||
void koto_playback_engine_get_supported_mimetypes(GList *mimes);
|
||||
|
||||
G_END_DECLS
|
357
src/playback/mpris.c
Normal file
357
src/playback/mpris.c
Normal file
|
@ -0,0 +1,357 @@
|
|||
/* mpris.c
|
||||
*
|
||||
* Copyright 2021 Joshua Strobl
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// Huge thanks to the GLib folks for providing an example server test
|
||||
|
||||
#include <glib-2.0/glib.h>
|
||||
#include <glib-2.0/gio/gio.h>
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
#include "../db/cartographer.h"
|
||||
#include "../playlist/current.h"
|
||||
#include "../playlist/playlist.h"
|
||||
#include "mimes.h"
|
||||
#include "engine.h"
|
||||
|
||||
extern KotoCartographer *koto_maps;
|
||||
extern KotoCurrentPlaylist *current_playlist;
|
||||
extern GtkApplication *app;
|
||||
extern GtkWindow *main_window;
|
||||
extern KotoPlaybackEngine *playback_engine;
|
||||
extern GList *supported_mimes;
|
||||
|
||||
GDBusConnection *dbus_conn = NULL;
|
||||
guint mpris_bus_id = 0;
|
||||
GDBusNodeInfo *introspection_data = NULL;
|
||||
|
||||
static const gchar introspection_xml[] =
|
||||
"<node>"
|
||||
" <interface name='org.mpris.MediaPlayer2'>"
|
||||
" <method name='Raise' />"
|
||||
" <method name='Quit' />"
|
||||
" <property type='b' name='CanQuit' access='read' />"
|
||||
" <property type='b' name='CanRaise' access='read' />"
|
||||
" <property type='b' name='HasTrackList' access='read' />"
|
||||
" <property type='s' name='Identity' access='read' />"
|
||||
" <property type='s' name='DesktopEntry' access='read' />"
|
||||
" <property type='as' name='SupportedUriSchemas' access='read' />"
|
||||
" <property type='as' name='SupportedMimeTypes' access='read' />"
|
||||
" </interface>"
|
||||
" <interface name='org.mpris.MediaPlayer2.Player'>"
|
||||
" <method name='Next' />"
|
||||
" <method name='Previous' />"
|
||||
" <method name='Pause' />"
|
||||
" <method name='PlayPause' />"
|
||||
" <method name='Stop' />"
|
||||
" <method name='Play' />"
|
||||
" <method name='Seek'>"
|
||||
" <arg type='x' name='offset' />"
|
||||
" </method>"
|
||||
" <method name='SetPosition'>"
|
||||
" <arg type='o' name='track_id' />"
|
||||
" <arg type='x' name='pos' />"
|
||||
" </method>"
|
||||
" <property type='s' name='PlaybackStatus' access='read' />"
|
||||
" <property type='s' name='LoopStatus' access='readwrite' />"
|
||||
" <property type='d' name='Rate' access='readwrite' />"
|
||||
" <property type='b' name='Shuffle' access='readwrite' />"
|
||||
" <property type='a{sv}' name='Metadata' access='read' />"
|
||||
" <property type='d' name='Volume' access='readwrite' />"
|
||||
" <property type='x' name='Position' access='read' />"
|
||||
" <property type='d' name='MinimumRate' access='read' />"
|
||||
" <property type='d' name='MaximumRate' access='read' />"
|
||||
" <property type='b' name='CanGoNext' access='read' />"
|
||||
" <property type='b' name='CanGoPrevious' access='read' />"
|
||||
" <property type='b' name='CanPlay' access='read' />"
|
||||
" <property type='b' name='CanPause' access='read' />"
|
||||
" <property type='b' name='CanSeek' access='read' />"
|
||||
" <property type='b' name='CanControl' access='read' />"
|
||||
" </interface>"
|
||||
"</node>";
|
||||
|
||||
void handle_main_mpris_method_call(
|
||||
GDBusConnection *connection,
|
||||
const gchar *sender,
|
||||
const gchar *object_path,
|
||||
const gchar *interface_name,
|
||||
const gchar *method_name,
|
||||
GVariant *parameters,
|
||||
GDBusMethodInvocation *invocation,
|
||||
gpointer user_data
|
||||
) {
|
||||
if (g_strcmp0(interface_name, "org.mpris.MediaPlayer2") == 0) { // Root mediaplayer2 interface
|
||||
if (g_strcmp0(method_name, "Raise") == 0) { // Raise the window
|
||||
gtk_window_unminimize(main_window); // Ensure we unminimize the window
|
||||
gtk_window_present(main_window); // Present our main window
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_strcmp0(method_name, "Quit") == 0) { // Quit the application
|
||||
gtk_application_remove_window(app, main_window); // Remove the window, thereby closing the app
|
||||
return;
|
||||
}
|
||||
} else if (g_strcmp0(interface_name, "org.mpris.MediaPlayer2.koto") == 0) { // Root mediaplayer2 interface
|
||||
if (g_strcmp0(method_name, "Next") == 0) { // Next track
|
||||
koto_playback_engine_forwards(playback_engine);
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_strcmp0(method_name, "Previous") == 0) { // Previous track
|
||||
koto_playback_engine_backwards(playback_engine);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GVariant* handle_get_property(
|
||||
GDBusConnection *connection,
|
||||
const gchar *sender,
|
||||
const gchar *object_path,
|
||||
const gchar *interface_name,
|
||||
const gchar *property_name,
|
||||
GError **error,
|
||||
gpointer user_data
|
||||
) {
|
||||
GVariant *ret;
|
||||
ret = NULL;
|
||||
|
||||
g_message("asking for %s", property_name);
|
||||
|
||||
if (g_strcmp0(property_name, "CanQuit") == 0) { // If property is CanQuit
|
||||
ret = g_variant_new_boolean(TRUE); // Allow quitting. You can escape Hotel California for now.
|
||||
}
|
||||
|
||||
if (g_strcmp0(property_name, "CanRaise") == 0) { // If property is CanRaise
|
||||
ret = g_variant_new_boolean(TRUE); // Allow raising.
|
||||
}
|
||||
|
||||
if (g_strcmp0(property_name, "HasTrackList") == 0) { // If property is HasTrackList
|
||||
KotoPlaylist *playlist = koto_current_playlist_get_playlist(current_playlist);
|
||||
if (KOTO_IS_PLAYLIST(playlist)) {
|
||||
ret = g_variant_new_boolean(koto_playlist_get_length(playlist) > 0);
|
||||
} else { // Don't have a playlist
|
||||
ret = g_variant_new_boolean(FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
if (g_strcmp0(property_name, "Identity") == 0) { // Want identity
|
||||
ret = g_variant_new_string("Koto"); // Not Jason Bourne but close enough
|
||||
}
|
||||
|
||||
if (g_strcmp0(property_name, "DesktopEntry") == 0) { // Desktop Entry
|
||||
ret = g_variant_new_string("com.github.joshstrobl.koto");
|
||||
}
|
||||
|
||||
if (g_strcmp0(property_name, "SupportedUriSchemas") == 0) { // Supported URI Schemas
|
||||
GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE("as")); // Array of strings
|
||||
g_variant_builder_add(builder, "s", "file");
|
||||
ret = g_variant_new("as", builder);
|
||||
g_variant_builder_unref(builder); // Unref builder since we no longer need it
|
||||
}
|
||||
|
||||
if (g_strcmp0(property_name, "SupportedMimeTypes") == 0) { // Supported mimetypes
|
||||
GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE("as")); // Array of strings
|
||||
GList *mimes;
|
||||
mimes = NULL;
|
||||
|
||||
for (mimes = supported_mimes; mimes != NULL; mimes = mimes->next) { // For each mimetype
|
||||
g_variant_builder_add(builder, "s", mimes->data); // Add the mime as a string
|
||||
}
|
||||
|
||||
ret = g_variant_new("as", builder);
|
||||
g_variant_builder_unref(builder);
|
||||
g_list_free(mimes);
|
||||
}
|
||||
|
||||
if (g_strcmp0(property_name, "Metadata") == 0) { // Metadata
|
||||
GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE_VARDICT);
|
||||
|
||||
KotoIndexedTrack *current_track = koto_playback_engine_get_current_track(playback_engine);
|
||||
|
||||
if (KOTO_IS_INDEXED_TRACK(current_track)) { // Currently playing a track
|
||||
koto_push_track_info_to_builder(builder, current_track);
|
||||
}
|
||||
|
||||
ret = g_variant_builder_end(builder);
|
||||
}
|
||||
|
||||
if (
|
||||
(g_strcmp0(property_name, "CanPlay") == 0) ||
|
||||
(g_strcmp0(property_name, "CanPause") == 0) ||
|
||||
(g_strcmp0(property_name, "CanSeek") == 0)
|
||||
) {
|
||||
KotoIndexedTrack *current_track = koto_playback_engine_get_current_track(playback_engine);
|
||||
ret = g_variant_new_boolean(KOTO_IS_INDEXED_TRACK(current_track));
|
||||
}
|
||||
|
||||
if (g_strcmp0(property_name, "CanGoNext") == 0) { // Can Go Next
|
||||
// TODO: Add some actual logic here
|
||||
ret = g_variant_new_boolean(TRUE);
|
||||
}
|
||||
|
||||
if (g_strcmp0(property_name, "CanGoPrevious") == 0) { // Can Go Previous
|
||||
// TODO: Add some actual logic here
|
||||
ret = g_variant_new_boolean(TRUE);
|
||||
}
|
||||
|
||||
if (g_strcmp0(property_name, "CanControl") == 0){ // Can Control
|
||||
ret = g_variant_new_boolean(TRUE);
|
||||
}
|
||||
|
||||
if (g_strcmp0(property_name, "PlaybackStatus") == 0) { // Get our playback status
|
||||
GstState current_state = koto_playback_engine_get_state(playback_engine); // Get the current state
|
||||
|
||||
if (current_state == GST_STATE_PLAYING) {
|
||||
ret = g_variant_new_string("Playing");
|
||||
} else if (current_state == GST_STATE_PAUSED) {
|
||||
ret = g_variant_new_string("Paused");
|
||||
} else {
|
||||
ret = g_variant_new_string("Stopped");
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void koto_push_track_info_to_builder(GVariantBuilder *builder, KotoIndexedTrack *track) {
|
||||
if (!KOTO_IS_INDEXED_TRACK(track)) {
|
||||
return;
|
||||
}
|
||||
|
||||
gchar *album_art_path = NULL;
|
||||
gchar *album_name = NULL;
|
||||
gchar *album_uuid = NULL;
|
||||
gchar *artist_uuid = NULL;
|
||||
gchar *artist_name = NULL;
|
||||
gchar *track_name = NULL;
|
||||
gchar *track_path = NULL;
|
||||
gchar *track_uuid = NULL;
|
||||
guint track_position = 0;
|
||||
guint track_disc = 0;
|
||||
|
||||
g_object_get(track,
|
||||
"album-uuid", &album_uuid,
|
||||
"artist-uuid", &artist_uuid,
|
||||
"cd", &track_disc,
|
||||
"path", &track_path,
|
||||
"parsed-name", &track_name,
|
||||
"position", &track_position,
|
||||
"uuid", &track_uuid,
|
||||
NULL);
|
||||
|
||||
KotoIndexedArtist *artist = koto_cartographer_get_artist_by_uuid(koto_maps, artist_uuid);
|
||||
KotoIndexedAlbum *album = koto_cartographer_get_album_by_uuid(koto_maps, album_uuid);
|
||||
|
||||
g_object_get(album,
|
||||
"art-path", &album_art_path,
|
||||
"name", &album_name,
|
||||
NULL);
|
||||
|
||||
g_object_get(artist,
|
||||
"name", &artist_name,
|
||||
NULL);
|
||||
|
||||
g_variant_builder_add(builder, "{sv}", "mpris:trackid", g_variant_new_string(track_uuid));
|
||||
|
||||
g_message("Art path: %s", album_art_path);
|
||||
if (g_strcmp0(album_art_path, "") != 0) { // Not empty
|
||||
album_art_path = g_strconcat("file://", album_art_path); // Prepend with file://
|
||||
g_variant_builder_add(builder, "{sv}", "mpris:artUrl", g_variant_new_string(album_art_path));
|
||||
}
|
||||
|
||||
g_variant_builder_add(builder, "{sv}", "xesam:album", g_variant_new_string(album_name));
|
||||
|
||||
if (g_strcmp0(artist_name, "") != 0) { // Got artist name
|
||||
GVariant *artist_name_variant;
|
||||
GVariantBuilder *artist_list_builder = g_variant_builder_new(G_VARIANT_TYPE("as"));
|
||||
g_variant_builder_add(artist_list_builder, "s", artist_name);
|
||||
artist_name_variant = g_variant_new("as", artist_list_builder);
|
||||
g_variant_builder_unref(artist_list_builder);
|
||||
|
||||
g_variant_builder_add(builder, "{sv}", "xesam:artist", artist_name_variant);
|
||||
}
|
||||
|
||||
g_variant_builder_add(builder, "{sv}", "xesam:discNumber", g_variant_new_uint64(track_disc));
|
||||
g_variant_builder_add(builder, "{sv}", "xesam:title", g_variant_new_string(track_name));
|
||||
g_variant_builder_add(builder, "{sv}", "xesam:url", g_variant_new_string(track_path));
|
||||
g_variant_builder_add(builder, "{sv}", "xesam:trackNumber", g_variant_new_uint64(track_position));
|
||||
}
|
||||
|
||||
void koto_update_mpris_info_for_track(KotoIndexedTrack *track) {
|
||||
if (!KOTO_IS_INDEXED_TRACK(track)) {
|
||||
return;
|
||||
}
|
||||
|
||||
GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE_ARRAY);
|
||||
GVariantBuilder *metadata_builder = g_variant_builder_new(G_VARIANT_TYPE_VARDICT);
|
||||
GError *error = NULL;
|
||||
|
||||
koto_push_track_info_to_builder(metadata_builder, track);
|
||||
GVariant *metadata_ret = g_variant_builder_end(metadata_builder);
|
||||
g_variant_builder_add(builder, "{sv}", "Metadata", metadata_ret);
|
||||
|
||||
g_dbus_connection_emit_signal(dbus_conn,
|
||||
NULL,
|
||||
"/org/mpris/MediaPlayer2",
|
||||
"org.freedesktop.DBus.Properties",
|
||||
"PropertiesChanged",
|
||||
g_variant_new("(sa{sv}as)", "org.mpris.MediaPlayer2.Player", builder, NULL),
|
||||
&error
|
||||
);
|
||||
}
|
||||
|
||||
static const GDBusInterfaceVTable main_mpris_interface_vtable = {
|
||||
handle_main_mpris_method_call,
|
||||
handle_get_property,
|
||||
};
|
||||
|
||||
void on_main_mpris_bus_acquired(GDBusConnection *connection, const gchar *name, gpointer user_data) {
|
||||
dbus_conn = connection;
|
||||
g_dbus_connection_register_object(dbus_conn,
|
||||
"/org/mpris/MediaPlayer2",
|
||||
introspection_data->interfaces[0],
|
||||
&main_mpris_interface_vtable,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
);
|
||||
|
||||
g_dbus_connection_register_object(dbus_conn,
|
||||
"/org/mpris/MediaPlayer2",
|
||||
introspection_data->interfaces[1],
|
||||
&main_mpris_interface_vtable,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
);
|
||||
}
|
||||
|
||||
void setup_mpris_interfaces() {
|
||||
introspection_data = g_dbus_node_info_new_for_xml(introspection_xml, NULL);
|
||||
g_assert(introspection_data != NULL);
|
||||
|
||||
mpris_bus_id = g_bus_own_name(G_BUS_TYPE_SESSION,
|
||||
"org.mpris.MediaPlayer2.koto",
|
||||
G_BUS_NAME_OWNER_FLAGS_NONE,
|
||||
on_main_mpris_bus_acquired,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
);
|
||||
|
||||
g_assert(mpris_bus_id > 0);
|
||||
}
|
27
src/playback/mpris.h
Normal file
27
src/playback/mpris.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
/* mpris.h
|
||||
*
|
||||
* Copyright 2021 Joshua Strobl
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <glib-2.0/glib.h>
|
||||
#include <glib-2.0/gio/gio.h>
|
||||
#include "../indexer/structs.h"
|
||||
|
||||
void koto_push_track_info_to_builder(GVariantBuilder *builder, KotoIndexedTrack *track);
|
||||
void koto_update_mpris_info_for_track(KotoIndexedTrack *track);
|
||||
void handle_main_mpris_method_call(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data);
|
||||
GVariant* handle_get_property(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GError **error, gpointer user_data);
|
||||
void on_main_mpris_bus_acquired(GDBusConnection *connection, const gchar *name, gpointer user_data);
|
||||
void setup_mpris_interfaces();
|
|
@ -29,7 +29,7 @@ struct _KotoPlaylist {
|
|||
gchar *uuid;
|
||||
gchar *name;
|
||||
gchar *art_path;
|
||||
guint current_position;
|
||||
gint current_position;
|
||||
gboolean ephemeral;
|
||||
gboolean is_shuffle_enabled;
|
||||
|
||||
|
@ -45,6 +45,7 @@ enum {
|
|||
PROP_NAME,
|
||||
PROP_ART_PATH,
|
||||
PROP_EPHEMERAL,
|
||||
PROP_IS_SHUFFLE_ENABLED,
|
||||
N_PROPERTIES,
|
||||
};
|
||||
|
||||
|
@ -90,6 +91,14 @@ static void koto_playlist_class_init(KotoPlaylistClass *c) {
|
|||
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
|
||||
);
|
||||
|
||||
props[PROP_IS_SHUFFLE_ENABLED] = g_param_spec_boolean(
|
||||
"is-shuffle-enabled",
|
||||
"Is shuffling enabled",
|
||||
"Is shuffling enabled",
|
||||
FALSE,
|
||||
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
|
||||
);
|
||||
|
||||
g_object_class_install_properties(gobject_class, N_PROPERTIES, props);
|
||||
}
|
||||
|
||||
|
@ -109,6 +118,9 @@ static void koto_playlist_get_property(GObject *obj, guint prop_id, GValue *val,
|
|||
case PROP_EPHEMERAL:
|
||||
g_value_set_boolean(val, self->ephemeral);
|
||||
break;
|
||||
case PROP_IS_SHUFFLE_ENABLED:
|
||||
g_value_set_boolean(val, self->is_shuffle_enabled);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
|
||||
break;
|
||||
|
@ -131,6 +143,9 @@ static void koto_playlist_set_property(GObject *obj, guint prop_id, const GValue
|
|||
case PROP_EPHEMERAL:
|
||||
self->ephemeral = g_value_get_boolean(val);
|
||||
break;
|
||||
case PROP_IS_SHUFFLE_ENABLED:
|
||||
self->is_shuffle_enabled = g_value_get_boolean(val);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
|
||||
break;
|
||||
|
@ -138,11 +153,20 @@ static void koto_playlist_set_property(GObject *obj, guint prop_id, const GValue
|
|||
}
|
||||
|
||||
static void koto_playlist_init(KotoPlaylist *self) {
|
||||
self->current_position = 0; // Default to 0
|
||||
self->current_position = -1; // Default to -1 so first time incrementing puts it at 0
|
||||
self->is_shuffle_enabled = FALSE;
|
||||
self->played_tracks = g_queue_new(); // Set as an empty GQueue
|
||||
self->tracks = g_queue_new(); // Set as an empty GQueue
|
||||
}
|
||||
|
||||
void koto_playlist_add_to_played_tracks(KotoPlaylist *self, gchar *uuid) {
|
||||
if (g_queue_index(self->played_tracks, uuid) != -1) { // Already added
|
||||
return;
|
||||
}
|
||||
|
||||
g_queue_push_tail(self->played_tracks, uuid); // Add to end
|
||||
}
|
||||
|
||||
void koto_playlist_add_track(KotoPlaylist *self, KotoIndexedTrack *track) {
|
||||
gchar *uuid = NULL;
|
||||
g_object_get(track, "uuid", &uuid, NULL); // Get the UUID
|
||||
|
@ -228,12 +252,13 @@ gchar* koto_playlist_get_random_track(KotoPlaylist *self) {
|
|||
GRand* rando_calrissian = g_rand_new(); // Create a new RNG
|
||||
guint attempt = 0;
|
||||
|
||||
while (track_uuid != NULL) { // Haven't selected a track yet
|
||||
while (track_uuid == NULL) { // Haven't selected a track yet
|
||||
attempt++;
|
||||
gint32 *selected_item = g_rand_int_range(rando_calrissian, 0, (gint32) tracks_len);
|
||||
gchar *selected_track = g_queue_peek_nth(self->tracks, (guint) selected_item); // Get the UUID of the selected item
|
||||
|
||||
if (g_queue_index(self->played_tracks, selected_track) == -1) { // Haven't played the track
|
||||
self->current_position = (int) selected_item;
|
||||
track_uuid = selected_track;
|
||||
break;
|
||||
} else { // Failed to get the track
|
||||
|
@ -259,7 +284,9 @@ gchar* koto_playlist_get_uuid(KotoPlaylist *self) {
|
|||
|
||||
gchar* koto_playlist_go_to_next(KotoPlaylist *self) {
|
||||
if (self->is_shuffle_enabled) { // Shuffling enabled
|
||||
return koto_playlist_get_random_track(self); // Get a random track
|
||||
gchar *random_track_uuid = koto_playlist_get_random_track(self); // Get a random track
|
||||
koto_playlist_add_to_played_tracks(self, random_track_uuid);
|
||||
return random_track_uuid;
|
||||
}
|
||||
|
||||
gchar *current_uuid = koto_playlist_get_current_uuid(self); // Get the current UUID
|
||||
|
@ -269,7 +296,9 @@ gchar* koto_playlist_go_to_next(KotoPlaylist *self) {
|
|||
}
|
||||
|
||||
self->current_position++; // Increment our position
|
||||
return koto_playlist_get_current_uuid(self); // Return the new UUID
|
||||
current_uuid = koto_playlist_get_current_uuid(self); // Return the new UUID
|
||||
koto_playlist_add_to_played_tracks(self, current_uuid);
|
||||
return current_uuid;
|
||||
}
|
||||
|
||||
gchar* koto_playlist_go_to_previous(KotoPlaylist *self) {
|
||||
|
@ -287,6 +316,10 @@ gchar* koto_playlist_go_to_previous(KotoPlaylist *self) {
|
|||
return koto_playlist_get_current_uuid(self); // Return the new UUID
|
||||
}
|
||||
|
||||
void koto_playlist_remove_from_played_tracks(KotoPlaylist *self, gchar *uuid) {
|
||||
g_queue_remove(self->played_tracks, uuid);
|
||||
}
|
||||
|
||||
void koto_playlist_remove_track(KotoPlaylist *self, KotoIndexedTrack *track) {
|
||||
gchar *track_uuid = NULL;
|
||||
g_object_get(track, "uuid", &track_uuid, NULL);
|
||||
|
|
|
@ -35,6 +35,7 @@ G_DECLARE_FINAL_TYPE(KotoPlaylist, koto_playlist, KOTO, PLAYLIST, GObject);
|
|||
|
||||
KotoPlaylist* koto_playlist_new();
|
||||
KotoPlaylist* koto_playlist_new_with_uuid(const gchar *uuid);
|
||||
void koto_playlist_add_to_played_tracks(KotoPlaylist *self, gchar *uuid);
|
||||
void koto_playlist_add_track(KotoPlaylist *self, KotoIndexedTrack *track);
|
||||
void koto_playlist_add_track_by_uuid(KotoPlaylist *self, const gchar *uuid);
|
||||
void koto_playlist_commit(KotoPlaylist *self);
|
||||
|
@ -48,6 +49,7 @@ GQueue* koto_playlist_get_tracks(KotoPlaylist *self);
|
|||
gchar* koto_playlist_get_uuid(KotoPlaylist *self);
|
||||
gchar* koto_playlist_go_to_next(KotoPlaylist *self);
|
||||
gchar* koto_playlist_go_to_previous(KotoPlaylist *self);
|
||||
void koto_playlist_remove_from_played_tracks(KotoPlaylist *self, gchar *uuid);
|
||||
void koto_playlist_remove_track(KotoPlaylist *self, KotoIndexedTrack *track);
|
||||
void koto_playlist_remove_track_by_uuid(KotoPlaylist *self, gchar *uuid);
|
||||
void koto_playlist_set_artwork(KotoPlaylist *self, const gchar *path);
|
||||
|
|
|
@ -2,4 +2,8 @@
|
|||
& > image {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
&.active > image {
|
||||
color: $green;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
$grey: #2e2e2e;
|
||||
$midnight: #1d1d1d;
|
||||
$darkgrey: #666666;
|
||||
$green: #60E078;
|
||||
|
||||
$itempadding: 40px;
|
||||
$halvedpadding: $itempadding / 2;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue