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:
Joshua Strobl 2021-04-06 10:41:15 +03:00
parent b1f4460a2e
commit 07c3c00f1e
17 changed files with 734 additions and 69 deletions

View file

@ -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, &current_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, &current_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);
}

View file

@ -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
View 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
View 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
View 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
View 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();