Fix Issue #890 -- https://github.com/mikebrady/shairport-sync/issues/890 Recognise situation where a track has no artwork properly. Less likely to drop the 'availabile' property. Always output the full metadata whenever any part of it has changed. Metadata coming from dacp is now incorporated,
323 lines
13 KiB
C
323 lines
13 KiB
C
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "config.h"
|
|
|
|
#include "common.h"
|
|
#include "player.h"
|
|
#include "rtsp.h"
|
|
|
|
#include "rtp.h"
|
|
|
|
#include "dacp.h"
|
|
|
|
#include "metadata_hub.h"
|
|
#include "mpris-service.h"
|
|
|
|
void mpris_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused)) void *userdata) {
|
|
// debug(1, "MPRIS metadata watcher called");
|
|
char response[100];
|
|
|
|
switch (argc->repeat_status) {
|
|
case RS_NOT_AVAILABLE:
|
|
strcpy(response, "Not Available");
|
|
break;
|
|
case RS_OFF:
|
|
strcpy(response, "None");
|
|
break;
|
|
case RS_ONE:
|
|
strcpy(response, "Track");
|
|
break;
|
|
case RS_ALL:
|
|
strcpy(response, "Playlist");
|
|
break;
|
|
}
|
|
|
|
media_player2_player_set_loop_status(mprisPlayerPlayerSkeleton, response);
|
|
|
|
switch (argc->player_state) {
|
|
case PS_NOT_AVAILABLE:
|
|
strcpy(response, "Not Available");
|
|
break;
|
|
case PS_STOPPED:
|
|
strcpy(response, "Stopped");
|
|
break;
|
|
case PS_PAUSED:
|
|
strcpy(response, "Paused");
|
|
break;
|
|
case PS_PLAYING:
|
|
strcpy(response, "Playing");
|
|
break;
|
|
}
|
|
|
|
media_player2_player_set_playback_status(mprisPlayerPlayerSkeleton, response);
|
|
|
|
/*
|
|
switch (argc->shuffle_state) {
|
|
case SS_NOT_AVAILABLE:
|
|
strcpy(response, "Not Available");
|
|
break;
|
|
case SS_OFF:
|
|
strcpy(response, "Off");
|
|
break;
|
|
case SS_ON:
|
|
strcpy(response, "On");
|
|
break;
|
|
}
|
|
|
|
media_player2_player_set_shuffle_status(mprisPlayerPlayerSkeleton, response);
|
|
*/
|
|
|
|
switch (argc->shuffle_status) {
|
|
case SS_NOT_AVAILABLE:
|
|
media_player2_player_set_shuffle(mprisPlayerPlayerSkeleton, FALSE);
|
|
break;
|
|
case SS_OFF:
|
|
media_player2_player_set_shuffle(mprisPlayerPlayerSkeleton, FALSE);
|
|
break;
|
|
case SS_ON:
|
|
media_player2_player_set_shuffle(mprisPlayerPlayerSkeleton, TRUE);
|
|
break;
|
|
default:
|
|
debug(1, "This should never happen.");
|
|
}
|
|
|
|
/*
|
|
// Add the TrackID if we have one
|
|
// Build the Track ID from the 16-byte item_composite_id in hex prefixed by
|
|
// /org/gnome/ShairportSync
|
|
char st[33];
|
|
char *pt = st;
|
|
int it;
|
|
int non_zero = 0;
|
|
for (it = 0; it < 16; it++) {
|
|
if (argc->track_metadata->item_composite_id[it])
|
|
non_zero = 1;
|
|
snprintf(pt, 3, "%02X", argc->track_metadata->item_composite_id[it]);
|
|
pt += 2;
|
|
}
|
|
*pt = 0;
|
|
|
|
if (non_zero) {
|
|
// debug(1, "Set ID using composite ID: \"0x%s\".", st);
|
|
char trackidstring[1024];
|
|
snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/%s", st);
|
|
GVariant *trackid = g_variant_new("o", trackidstring);
|
|
g_variant_builder_add(dict_builder, "{sv}", "mpris:trackid", trackid);
|
|
} else if ((argc->track_metadata) && (argc->track_metadata->item_id)) {
|
|
char trackidstring[128];
|
|
// debug(1, "Set ID using mper ID: \"%u\".",argc->item_id);
|
|
snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/mper_%u",
|
|
argc->track_metadata->item_id);
|
|
GVariant *trackid = g_variant_new("o", trackidstring);
|
|
g_variant_builder_add(dict_builder, "{sv}", "mpris:trackid", trackid);
|
|
}
|
|
|
|
*/
|
|
|
|
// Build the metadata array
|
|
debug(2, "Build metadata");
|
|
GVariantBuilder *dict_builder = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
|
|
|
|
// Add in the artwork URI if it exists.
|
|
if (argc->cover_art_pathname) {
|
|
GVariant *artUrl = g_variant_new("s", argc->cover_art_pathname);
|
|
g_variant_builder_add(dict_builder, "{sv}", "mpris:artUrl", artUrl);
|
|
}
|
|
|
|
// Add in the Track ID based on the 'mper' metadata if it is non-zero
|
|
if (argc->item_id != 0) {
|
|
char trackidstring[128];
|
|
snprintf(trackidstring, sizeof(trackidstring), "/org/gnome/ShairportSync/mper_%u",
|
|
argc->item_id);
|
|
GVariant *trackid = g_variant_new("o", trackidstring);
|
|
g_variant_builder_add(dict_builder, "{sv}", "mpris:trackid", trackid);
|
|
}
|
|
|
|
// Add the track name if it exists
|
|
if (argc->track_name) {
|
|
GVariant *track_name = g_variant_new("s", argc->track_name);
|
|
g_variant_builder_add(dict_builder, "{sv}", "xesam:title", track_name);
|
|
}
|
|
|
|
// Add the album name if it exists
|
|
if (argc->album_name) {
|
|
GVariant *album_name = g_variant_new("s", argc->album_name);
|
|
g_variant_builder_add(dict_builder, "{sv}", "xesam:album", album_name);
|
|
}
|
|
|
|
// Add the artist name if it exists
|
|
if (argc->artist_name) {
|
|
GVariantBuilder *artist_as = g_variant_builder_new(G_VARIANT_TYPE("as"));
|
|
g_variant_builder_add(artist_as, "s", argc->artist_name);
|
|
GVariant *artists = g_variant_builder_end(artist_as);
|
|
g_variant_builder_unref(artist_as);
|
|
g_variant_builder_add(dict_builder, "{sv}", "xesam:artist", artists);
|
|
}
|
|
|
|
// Add the genre if it exists
|
|
if (argc->genre) {
|
|
GVariantBuilder *genre_as = g_variant_builder_new(G_VARIANT_TYPE("as"));
|
|
g_variant_builder_add(genre_as, "s", argc->genre);
|
|
GVariant *genre = g_variant_builder_end(genre_as);
|
|
g_variant_builder_unref(genre_as);
|
|
g_variant_builder_add(dict_builder, "{sv}", "xesam:genre", genre);
|
|
}
|
|
|
|
if (argc->songtime_in_milliseconds) {
|
|
uint64_t track_length_in_microseconds = argc->songtime_in_milliseconds;
|
|
track_length_in_microseconds *= 1000; // to microseconds in 64-bit precision
|
|
// Make up the track name and album name
|
|
// debug(1, "Set tracklength to %lu.", track_length_in_microseconds);
|
|
GVariant *tracklength = g_variant_new("x", track_length_in_microseconds);
|
|
g_variant_builder_add(dict_builder, "{sv}", "mpris:length", tracklength);
|
|
}
|
|
|
|
GVariant *dict = g_variant_builder_end(dict_builder);
|
|
g_variant_builder_unref(dict_builder);
|
|
media_player2_player_set_metadata(mprisPlayerPlayerSkeleton, dict);
|
|
}
|
|
|
|
static gboolean on_handle_quit(MediaPlayer2 *skeleton, GDBusMethodInvocation *invocation,
|
|
__attribute__((unused)) gpointer user_data) {
|
|
debug(1, "quit requested (MPRIS interface).");
|
|
pthread_cancel(main_thread_id);
|
|
media_player2_complete_quit(skeleton, invocation);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean on_handle_next(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
|
|
__attribute__((unused)) gpointer user_data) {
|
|
send_simple_dacp_command("nextitem");
|
|
media_player2_player_complete_next(skeleton, invocation);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean on_handle_previous(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
|
|
__attribute__((unused)) gpointer user_data) {
|
|
send_simple_dacp_command("previtem");
|
|
media_player2_player_complete_previous(skeleton, invocation);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean on_handle_stop(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
|
|
__attribute__((unused)) gpointer user_data) {
|
|
send_simple_dacp_command("stop");
|
|
media_player2_player_complete_stop(skeleton, invocation);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean on_handle_pause(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
|
|
__attribute__((unused)) gpointer user_data) {
|
|
send_simple_dacp_command("pause");
|
|
media_player2_player_complete_pause(skeleton, invocation);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean on_handle_play_pause(MediaPlayer2Player *skeleton,
|
|
GDBusMethodInvocation *invocation,
|
|
__attribute__((unused)) gpointer user_data) {
|
|
send_simple_dacp_command("playpause");
|
|
media_player2_player_complete_play_pause(skeleton, invocation);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean on_handle_play(MediaPlayer2Player *skeleton, GDBusMethodInvocation *invocation,
|
|
__attribute__((unused)) gpointer user_data) {
|
|
send_simple_dacp_command("play");
|
|
media_player2_player_complete_play(skeleton, invocation);
|
|
return TRUE;
|
|
}
|
|
|
|
static void on_mpris_name_acquired(GDBusConnection *connection, const gchar *name,
|
|
__attribute__((unused)) gpointer user_data) {
|
|
|
|
const char *empty_string_array[] = {NULL};
|
|
|
|
// debug(1, "MPRIS well-known interface name \"%s\" acquired on the %s bus.", name,
|
|
// (config.mpris_service_bus_type == DBT_session) ? "session" : "system");
|
|
mprisPlayerSkeleton = media_player2_skeleton_new();
|
|
mprisPlayerPlayerSkeleton = media_player2_player_skeleton_new();
|
|
|
|
g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(mprisPlayerSkeleton), connection,
|
|
"/org/mpris/MediaPlayer2", NULL);
|
|
g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(mprisPlayerPlayerSkeleton), connection,
|
|
"/org/mpris/MediaPlayer2", NULL);
|
|
|
|
media_player2_set_desktop_entry(mprisPlayerSkeleton, "shairport-sync");
|
|
media_player2_set_identity(mprisPlayerSkeleton, "Shairport Sync");
|
|
media_player2_set_can_quit(mprisPlayerSkeleton, TRUE);
|
|
media_player2_set_can_raise(mprisPlayerSkeleton, FALSE);
|
|
media_player2_set_has_track_list(mprisPlayerSkeleton, FALSE);
|
|
media_player2_set_supported_uri_schemes(mprisPlayerSkeleton, empty_string_array);
|
|
media_player2_set_supported_mime_types(mprisPlayerSkeleton, empty_string_array);
|
|
|
|
media_player2_player_set_playback_status(mprisPlayerPlayerSkeleton, "Stopped");
|
|
media_player2_player_set_loop_status(mprisPlayerPlayerSkeleton, "None");
|
|
media_player2_player_set_volume(mprisPlayerPlayerSkeleton, 0.5);
|
|
media_player2_player_set_minimum_rate(mprisPlayerPlayerSkeleton, 1.0);
|
|
media_player2_player_set_maximum_rate(mprisPlayerPlayerSkeleton, 1.0);
|
|
media_player2_player_set_can_go_next(mprisPlayerPlayerSkeleton, TRUE);
|
|
media_player2_player_set_can_go_previous(mprisPlayerPlayerSkeleton, TRUE);
|
|
media_player2_player_set_can_play(mprisPlayerPlayerSkeleton, TRUE);
|
|
media_player2_player_set_can_pause(mprisPlayerPlayerSkeleton, TRUE);
|
|
media_player2_player_set_can_seek(mprisPlayerPlayerSkeleton, FALSE);
|
|
media_player2_player_set_can_control(mprisPlayerPlayerSkeleton, TRUE);
|
|
|
|
g_signal_connect(mprisPlayerSkeleton, "handle-quit", G_CALLBACK(on_handle_quit), NULL);
|
|
|
|
g_signal_connect(mprisPlayerPlayerSkeleton, "handle-play", G_CALLBACK(on_handle_play), NULL);
|
|
g_signal_connect(mprisPlayerPlayerSkeleton, "handle-pause", G_CALLBACK(on_handle_pause), NULL);
|
|
g_signal_connect(mprisPlayerPlayerSkeleton, "handle-play-pause", G_CALLBACK(on_handle_play_pause),
|
|
NULL);
|
|
g_signal_connect(mprisPlayerPlayerSkeleton, "handle-stop", G_CALLBACK(on_handle_stop), NULL);
|
|
g_signal_connect(mprisPlayerPlayerSkeleton, "handle-next", G_CALLBACK(on_handle_next), NULL);
|
|
g_signal_connect(mprisPlayerPlayerSkeleton, "handle-previous", G_CALLBACK(on_handle_previous),
|
|
NULL);
|
|
|
|
add_metadata_watcher(mpris_metadata_watcher, NULL);
|
|
|
|
debug(1, "MPRIS service started at \"%s\" on the %s bus.", name,
|
|
(config.mpris_service_bus_type == DBT_session) ? "session" : "system");
|
|
}
|
|
|
|
static void on_mpris_name_lost_again(__attribute__((unused)) GDBusConnection *connection,
|
|
const gchar *name,
|
|
__attribute__((unused)) gpointer user_data) {
|
|
warn("Could not acquire an MPRIS interface named \"%s\" on the %s bus.", name,
|
|
(config.mpris_service_bus_type == DBT_session) ? "session" : "system");
|
|
}
|
|
|
|
static void on_mpris_name_lost(__attribute__((unused)) GDBusConnection *connection,
|
|
__attribute__((unused)) const gchar *name,
|
|
__attribute__((unused)) gpointer user_data) {
|
|
// debug(1, "Could not acquire MPRIS interface \"%s\" on the %s bus -- will try adding the process
|
|
// "
|
|
// "number to the end of it.",
|
|
// name,(mpris_bus_type==G_BUS_TYPE_SESSION) ? "session" : "system");
|
|
pid_t pid = getpid();
|
|
char interface_name[256] = "";
|
|
snprintf(interface_name, sizeof(interface_name), "org.mpris.MediaPlayer2.ShairportSync.i%d", pid);
|
|
GBusType mpris_bus_type = G_BUS_TYPE_SYSTEM;
|
|
if (config.mpris_service_bus_type == DBT_session)
|
|
mpris_bus_type = G_BUS_TYPE_SESSION;
|
|
// debug(1, "Looking for an MPRIS interface \"%s\" on the %s bus.",interface_name,
|
|
// (mpris_bus_type==G_BUS_TYPE_SESSION) ? "session" : "system");
|
|
g_bus_own_name(mpris_bus_type, interface_name, G_BUS_NAME_OWNER_FLAGS_NONE, NULL,
|
|
on_mpris_name_acquired, on_mpris_name_lost_again, NULL, NULL);
|
|
}
|
|
|
|
int start_mpris_service() {
|
|
mprisPlayerSkeleton = NULL;
|
|
mprisPlayerPlayerSkeleton = NULL;
|
|
GBusType mpris_bus_type = G_BUS_TYPE_SYSTEM;
|
|
if (config.mpris_service_bus_type == DBT_session)
|
|
mpris_bus_type = G_BUS_TYPE_SESSION;
|
|
// debug(1, "Looking for an MPRIS interface \"org.mpris.MediaPlayer2.ShairportSync\" on the %s
|
|
// bus.",(mpris_bus_type==G_BUS_TYPE_SESSION) ? "session" : "system");
|
|
g_bus_own_name(mpris_bus_type, "org.mpris.MediaPlayer2.ShairportSync",
|
|
G_BUS_NAME_OWNER_FLAGS_NONE, NULL, on_mpris_name_acquired, on_mpris_name_lost,
|
|
NULL, NULL);
|
|
return 0; // this is just to quieten a compiler warning
|
|
}
|