/* * Copyright (c) 2011- Osmo Antero. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License (GPL3), or any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Library General Public License 3 for more details. * * You should have received a copy of the GNU Library General Public * License 3 along with this program; if not, see /usr/share/common-licenses/GPL file * or . */ #include #include #include #include #include #include #include #include "dconf.h" #include "log.h" #include "utility.h" #include "support.h" #include "dbus-player.h" #include "audio-sources.h" // MPRIS2 compliant players. #include "dbus-mpris2.h" // Send commands to rec-manager.c #include "rec-manager-struct.h" // rec-manager.c #include "rec-manager.h" // List of players static GHashTable *g_player_list = NULL; static void dbus_player_disconnect_signals(); static void dbus_player_clear_list(); static void dbus_player_get_saved(); static void dbus_player_save(MediaPlayerRec *pl); static void dbus_player_delete_saved(const gchar *service_name); static gboolean dbus_player_msg_is_duplicate(MediaPlayerRec *player); static const gchar *status_name(gint status) __attribute__((unused)); gboolean add_player_to_list(gchar *desktop_file, gchar *service_name); void dbus_player_init() { LOG_DEBUG("Init dbus-player.c.\n"); g_player_list = NULL; mpris2_module_init(); } void dbus_player_exit() { LOG_DEBUG("Clean up dbus-player.c.\n"); // Disconnect DBus signals for all Media Players dbus_player_disconnect_signals(); // Clear the player list dbus_player_clear_list(); mpris2_module_exit(); } static RecorderCommand *convert_data(MediaPlayerRec *pl) { // Convert MediaPlayerRec to RecorderCommand if (!pl) return NULL; TrackInfo *tr = &pl->track; RecorderCommand *cmd = g_malloc0(sizeof(RecorderCommand)); cmd->title = g_strndup(tr->title, MPRIS_STRLEN); cmd->artist = g_strndup(tr->artist, MPRIS_STRLEN); // Actually a list of artists separated by '\v' (should we remove '\v'?) cmd->album = g_strndup(tr->album, MPRIS_STRLEN); cmd->genre = g_strndup(tr->genre, MPRIS_STRLEN); // Actually a list of genres separated by '\v' (should we remove '\v'?) cmd->albumArtist = g_strndup(tr->albumArtist, MPRIS_STRLEN); // Actually a list of albumArtists separated by '\v' (should we remove '\v'?) cmd->url = g_strndup(tr->url, MPRIS_STRLEN); cmd->artUrl = g_strndup(tr->artUrl, MPRIS_STRLEN); cmd->trackId = g_strndup(tr->trackId, MPRIS_STRLEN); cmd->trackNumber = tr->trackNumber; cmd->discNumber = tr->discNumber; cmd->trackLength = tr->trackLength; cmd->trackPos = tr->trackPos; cmd->flags = tr->flags; if (tr->status == PLAYER_STATUS_PAUSED) { cmd->type = RECORDING_PAUSE; } else if (tr->status == PLAYER_STATUS_PLAYING) { cmd->type = RECORDING_START; } else if (tr->status == PLAYER_STATUS_NOTIFY_MSG) { cmd->type = RECORDING_NOTIFY_MSG; } else { // tr.status == PLAYER_STATUS_CLOSED || // tr.status == PLAYER_STATUS_STOPPED cmd->type = RECORDING_STOP; } return cmd; } static gboolean dbus_player_msg_is_duplicate(MediaPlayerRec *player) { // Check if player->track (TrackInfo) is duplicate of the previous one. // Return TRUE if duplicate, otherwise FALSE. if (!player) return FALSE; // Track info TrackInfo *tr = &player->track; // Track info TrackInfo *prev_tr = &player->prev_track; // Check if message is duplicate of previous one //if (tr->trackLength != prev_tr->trackLength) return FALSE; //if (tr->trackPos != prev_tr->trackPos) return FALSE; if (tr->trackNumber != prev_tr->trackNumber) { return FALSE; } if (tr->discNumber != prev_tr->discNumber) { return FALSE; } if (tr->audioBitrate != prev_tr->audioBitrate) { return FALSE; } if (str_compare(tr->title, prev_tr->title, FALSE)) { return FALSE; } if (str_compare(tr->artist, prev_tr->artist, FALSE)) { return FALSE; } if (str_compare(tr->album, prev_tr->album, FALSE)) { return FALSE; } if (str_compare(tr->genre, prev_tr->genre, TRUE)) { return FALSE; } if (str_compare(tr->albumArtist, prev_tr->albumArtist, FALSE)) { return FALSE; } if (str_compare(tr->url, prev_tr->url, FALSE)) { return FALSE; } //if (str_compare(tr->artUrl, prev_tr->artUrl)) return FALSE; if (str_compare(tr->trackId, prev_tr->trackId, FALSE)) { return FALSE; } if (str_compare(tr->contentCreated, prev_tr->contentCreated, FALSE)) { return FALSE; } // This TrackInfo is 100% duplicate of the previous one return TRUE; } void dbus_player_process_data(gpointer player_rec, gboolean restart) { // Send message to the rec-manager.c MediaPlayerRec *player = (MediaPlayerRec*)player_rec; if (!player) return; // Current track TrackInfo *tr = &player->track; // Previous track (saved track data) TrackInfo *prev_tr = &player->prev_track; // Got PLAYING message from media player? if (tr->status == PLAYER_STATUS_PLAYING) { // Note ! // Check if this message from DBus is duplicate of previous one. // Some media players send numerous duplicate messages. Messages confuse audio recorder. if (dbus_player_msg_is_duplicate(player)) { // Increment duplicate msg count. This is just for debugging!. player->msg_count++; // Is recording paused? gboolean is_paused = rec_manager_is_paused(); if (is_paused) { LOG_PLAYER("%s: This DBus message (#%i) is duplicate of previous one. Continuing PAUSED recording.\n", player->app_name, player->msg_count+1); // Continue paused recording rec_manager_continue_recording(); } else { LOG_PLAYER("%s: This DBus message (#%i) is duplicate of previous one, %s. Dropping this message.\n", player->app_name, player->msg_count+1, status_name(tr->status)); } // Is duplicate. Drop this message. return; } // Start new recording // Stop the player first? if (restart ) { // || (tr->status == PLAYER_STATUS_STOP) rec_manager_stop_recording(); } // Save, remember this track for next time ! memcpy(prev_tr, tr, sizeof(TrackInfo)); // Set msg count to 1 (just for debugging) player->msg_count = 1; // Continues to LBL_1 } // Got PAUSED message from media player? if (tr->status == PLAYER_STATUS_PAUSED) { // Do nothing. // Continues to LBL_1 } // Got STOPPED message from media player? if (tr->status == PLAYER_STATUS_STOPPED) { // Nullify prev_tr (forget lastly received track data) memset(prev_tr, '\0', sizeof(TrackInfo)); } // LBL_1: // Debug: //dbus_player_debug_print(player); // Convert MediaPlayerRec to RecorderCommand RecorderCommand *cmd = convert_data(player); // Send this command to rec-manager queue. // Rec-manager will free the cmd structure after processing. rec_manager_send_command(cmd); } void dbus_player_player_changed(gchar *service_name) { // Re-connect DBus signals/methods for the given service_name (Media Player etc.) // Disconnect all signals/object methods dbus_player_disconnect_signals(); // Get MediaPlayerRec for this service_name MediaPlayerRec *player = dbus_player_lookup_service_name(service_name); if (player && player->func_set_signals) { LOG_PLAYER("Connect DBus signals for %s (%s).\n", player->app_name, player->service_name); // Start application (Media Player, etc) if (player->func_start_app) { player->func_start_app(player); } // Connect signals so we receive track-changed/start/stop messages from this app (over DBus) player->func_set_signals(player, TRUE); // TRUE=connect/register, FALSE=disconnect/unregister // Save this player in GSettings so user doesn't need to refresh the combo manually. dbus_player_save(player); } } static void dbus_player_disconnect_signals() { // Disconnect all signal-functions from the DBus GHashTableIter iter; gpointer key, value; g_hash_table_iter_init(&iter, g_player_list); while (g_hash_table_iter_next(&iter, &key, &value)) { MediaPlayerRec *player = (MediaPlayerRec*)value; if (player && player->func_set_signals) { // Disconnect signals player->func_set_signals(player, FALSE); // FALSE=disconnect/unregister } } } GHashTable *dbus_player_get_list_ref() { if (!g_player_list) { g_player_list = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, dbus_player_delete_item); } return g_player_list; } static gboolean dbus_palyer_remove_node(gpointer key, gpointer value, gpointer user_data) { // Return TRUE so this (key, value) pair gets removed and deleted. return TRUE; } static void dbus_player_clear_list() { // Delete the entire g_player_list if (g_player_list) { g_hash_table_foreach_remove(g_player_list, dbus_palyer_remove_node, NULL); g_hash_table_unref(g_player_list); } g_player_list = NULL; } void dbus_player_delete_item(gpointer data) { MediaPlayerRec *player = (MediaPlayerRec*)data; if (!player) return; LOG_PLAYER("dbus_player_delete_item: %s (%s).\n", player->app_name, player->service_name); if (player->func_set_signals) { // Disconnect signals player->func_set_signals(player, FALSE); // TRUE=connect, FALSE=disconnect } if (G_IS_DBUS_PROXY(player->proxy)) { g_object_unref(player->proxy); } player->proxy = NULL; if (G_IS_DBUS_PROXY(player->prop_proxy)) { g_object_unref(player->prop_proxy); } player->prop_proxy = NULL; g_free(player->service_name); g_free(player->desktop_file); g_free(player->exec_cmd); g_free(player->app_name); g_free(player->icon_name); g_free(player); } MediaPlayerRec *dbus_player_lookup_app_name(const gchar *app_name) { // Lookup player by its application name (p->app_name). // Typical app_names are "Amarok 2.3.2", "RhythmBox 2.3". GHashTableIter iter; gpointer key, value; if (str_length(app_name, 1024) < 1) return NULL; g_hash_table_iter_init(&iter, g_player_list); while (g_hash_table_iter_next(&iter, &key, &value)) { MediaPlayerRec *p = (MediaPlayerRec*)value; if (!g_strcmp0(p->app_name, app_name)) { return p; } } return NULL; } MediaPlayerRec *dbus_player_lookup_service_name(const gchar *service_name) { // Lookup player by its service name (p->service_name). // "org.mpris.MediaPlayer2.Player.banshee" is a typical service_name. GHashTableIter iter; gpointer key, value; if (str_length(service_name, 1024) < 1) return NULL; g_hash_table_iter_init(&iter, g_player_list); while (g_hash_table_iter_next(&iter, &key, &value)) { MediaPlayerRec *p = (MediaPlayerRec*)value; if (!g_strcmp0(p->service_name, service_name)) { return p; } } return NULL; } static const gchar *status_name(gint status) { static gchar s[80]; s[0] = '\0'; switch (status) { case PLAYER_STATUS_CLOSED: g_sprintf(s, "Status:%d PLAYER_STATUS_CLOSED (not running)", status); break; case PLAYER_STATUS_STOPPED: g_sprintf(s, "Status:%d PLAYER_STATUS_STOPPED", status); break; case PLAYER_STATUS_PAUSED: g_sprintf(s, "Status:%d PLAYER_STATUS_PAUSED", status); break; case PLAYER_STATUS_PLAYING: g_sprintf(s, "Status:%d PLAYER_STATUS_PLAYING", status); break; case PLAYER_STATUS_NOTIFY_MSG: // Simply a msg to the GUI. g_sprintf(s, "Status:%d PLAYER_STATUS_NOTIFY_MSG", status); break; default: g_sprintf(s, "Unknown status:%d\n", status); } return &s[0]; } void dbus_player_debug_print(MediaPlayerRec *p) { if (!p) return; LOG_PLAYER("------------------------------\n"); LOG_PLAYER("Player app name:%s\n", p->app_name); LOG_PLAYER("Service name:%s\n", p->service_name); LOG_PLAYER("Desktop file:%s.desktop\n", p->desktop_file); LOG_PLAYER("Executable command:%s\n", p->exec_cmd); TrackInfo *tr = &p->track; LOG_PLAYER("%s\n", status_name(tr->status)); if (tr->status != PLAYER_STATUS_NOTIFY_MSG) { LOG_PLAYER("Title:%s\n", tr->title); LOG_PLAYER("Artist:%s\n", tr->artist); LOG_PLAYER("Album:%s\n", tr->album); LOG_PLAYER("Genre:%s\n", tr->genre); LOG_PLAYER("AlbumArtist:%s\n", tr->albumArtist); LOG_PLAYER("Url:%s\n", tr->url); LOG_PLAYER("ArtUrl:%s\n", tr->artUrl); LOG_PLAYER("TrackId:%s\n", tr->trackId); LOG_PLAYER("ContentCreated:%s\n", tr->contentCreated); LOG_PLAYER("Track length in microsecs:%ld\n", tr->trackLength); LOG_PLAYER("Track pos in microsecs:%ld\n", tr->trackPos); LOG_PLAYER("Flags:%d\n", tr->flags); LOG_PLAYER("Msg count:%d\n", p->msg_count); } else { // Simply a msg to the GUI. LOG_PLAYER("Message:%s\n", tr->title); } LOG_PLAYER("------------------------------\n"); } GHashTable *dbus_player_get_player_list() { // Clear the old list dbus_player_clear_list(); // Populate the list. // Detect players that follow the org.mpris.MediaPlayer2.* standard. mpris2_detect_players(); // Add lastly used and saved players to the list dbus_player_get_saved(); // Make sure the most popular players are in the list (on our target distros). // Notice: Audio-recorder will automatically detect and remember the last used players. // Start your media-player, then press [Refresh]-button at end of Source: listbox to detect it. // You do not need to hard-code other, new players here. // Add Rhythmbox manually (check if installed) add_player_to_list("rhythmbox", "org.mpris.MediaPlayer2.rhythmbox"); return g_player_list; } gboolean add_player_to_list(gchar *desktop_file, gchar *service_name) { // Add player manually to the list. Check if it's installed. // Already in the list? if (dbus_player_lookup_service_name(service_name)) return TRUE; // New MediaPlayer record MediaPlayerRec *player = mpris2_player_new(service_name); // Set desktop file player->desktop_file = g_strdup(desktop_file); // Read rest from player's .desktop file get_details_from_desktop_file(player, desktop_file); // Find executable /usr/bin/exec_cmd gchar *path = find_command_path(player->exec_cmd); if (!path) { // Not installed. dbus_player_delete_item(player); return FALSE; } g_free(path); // Function to connect/disconnect event signals for this player player->func_set_signals = mpris2_set_signals; // Function to get track-info (album, title/song name/title, genre, etc.) player->func_get_info = mpris2_get_metadata; // Function to start/run the media player player->func_start_app = mpris2_start_app; // Function to check if this player is running player->func_check_is_running = mpris2_service_is_running; if (!dbus_player_lookup_app_name(player->app_name)) { // Add to list GHashTable *player_list = dbus_player_get_list_ref(); g_hash_table_insert(player_list, g_strdup(player->service_name), player); } else { // Duplicate or bad record. Free it. dbus_player_delete_item(player); } return TRUE; } void dbus_player_send_notification(gchar *msg) { RecorderCommand *cmd = g_malloc0(sizeof(RecorderCommand)); cmd->type = RECORDING_NOTIFY_MSG; // Convey message in the title field cmd->title = g_strndup(msg, MPRIS_STRLEN); // Send command to rec-manager.c. // It will free the cmd structure after processing. rec_manager_send_command(cmd); } // -------------------------------------------------------------------- // Support functions to read/write values to "players/saved-player-list" in GSettings. // -------------------------------------------------------------------- static void split_value(gchar *str, gchar **desktop_file, gchar **service_name) { // Split str on "\t" and return the parts. *desktop_file = NULL; *service_name = NULL; if (!str) return; // Split on '\t' gchar **args = g_strsplit(str, "\t", 3); // We cope only 2 arguments if (g_strv_length(args) != 2) { goto LBL_1; } if (args && args[0]) { *desktop_file = g_strdup(args[0]); if (args[1]) { *service_name = g_strdup(args[1]); } } LBL_1: // Delete args g_strfreev(args); } gchar *get_base_name(gchar *service_name) { // Take last part of service_name and return it. // Eg. take "vlc" from "org.mpris.MediaPlayer2.vlc" // Find last "." gchar *pos = g_strrstr0(service_name, "."); if (pos) { return g_strdup(pos+1); } return NULL; } static void dbus_player_delete_saved(const gchar *service_name) { // Delete service_name from GSettings. // See dconf-editor, key: /apps/audio-recorder/players/players/saved-player-list GList *list = NULL; GList *new_list = NULL; if (!service_name) return; str_trim((gchar*)service_name); // Get saved-player-list from GSettings. const gchar *conf_key = "players/saved-player-list"; conf_get_string_list((gchar*)conf_key, &list); GList *item = g_list_first(list); while (item) { // Take values. Eg: "amarok \t org.mpris.MediaPlayer2.amarok" gchar *str = (gchar*)item->data; gchar *desktop_file = NULL; gchar *service = NULL; // Split on '\t' split_value(str, &desktop_file, &service); // Service names match? if (!g_strcmp0(service_name, service)) { // Drop this node ; } else { // Keep this node new_list = g_list_append(new_list, g_strdup(str)); } g_free(desktop_file); g_free(service); item = g_list_next(item); } // Save changes to GSettings conf_save_string_list((gchar*)conf_key, new_list); // Free delete_list str_list_free(new_list); new_list = NULL; // Free list str_list_free(list); list = NULL; } static void dbus_player_save(MediaPlayerRec *pl) { // Save pl->app_name/pl->service_name to GSettings. // See dconf-editor, key: /apps/audio-recorder/players/players/saved-player-list GList *list = NULL; if (!pl->service_name) return; // Delete old value dbus_player_delete_saved(pl->service_name); // str must have format "vlc \t org.mpris.MediaPlayer2.vlc" gchar *str = g_strdup_printf("%s\t%s", check_null(pl->desktop_file), pl->service_name); // Get saved-player-list from Gsettings. const gchar *conf_key = "players/saved-player-list"; conf_get_string_list((gchar*)conf_key, &list); // Add new entry and save in GSettings/DConf list = g_list_prepend(list, g_strdup(str)); conf_save_string_list((gchar*)conf_key, list); #if defined(DEBUG_PLAYER) || defined(DEBUG_ALL) LOG_PLAYER("----------------------------\n"); str_list_print("New, saved saved-player-list", list); LOG_PLAYER("----------------------------\n"); #endif // Free list str_list_free(list); list = NULL; g_free(str); } static void dbus_player_get_saved() { // Get saved-player-list from GSettings. // See dconf-editor, key: /apps/audio-recorder/players/players/saved-player-list const gchar *conf_key = "players/saved-player-list"; // Get list GList *list = NULL; conf_get_string_list((gchar*)conf_key, &list); #if defined(DEBUG_PLAYER) || defined(DEBUG_ALL) LOG_PLAYER("----------------------------\n"); str_list_print("Get saved-player-list", list); LOG_PLAYER("----------------------------\n"); #endif // Add saved & still existing media-players to the list GList *item = g_list_first(list); while (item) { // Read values. Eg. "vlc \t org.mpris.MediaPlayer2.vlc" gchar *str = (gchar*)item->data; gchar *desktop_file = NULL; gchar *service_name = NULL; // Split on '\t' split_value(str, &desktop_file, &service_name); // We will not tolerate errors here. // Wipe out the entire list if one line is bad (eg. it has older format)! if (!(desktop_file && service_name)) { g_free(desktop_file); g_free(service_name); conf_save_string_list((gchar*)conf_key, NULL); goto LBL_1; } // Add media-player to the list (to be shown in the "Source:" listbox). if (!add_player_to_list(desktop_file, service_name)) { // It's probably uninstalled. Delete form GSettings too. LOG_PLAYER("Player %s, (%s) removed from the list. It's probably uninstalled.\n", desktop_file, service_name); dbus_player_delete_saved(service_name); } g_free(desktop_file); g_free(service_name); item = g_list_next(item); } LBL_1: { // Free list str_list_free(list); list = NULL; } } void get_details_from_desktop_file(MediaPlayerRec *pl, const gchar *desktop_file) { // Find AppInfo for the given .desktop file // I assume here that .desktop filenames are in ascii (not multibyte) strings if (!desktop_file) { goto LBL_1; } gchar *s = NULL; // Ends with ".desktop"? if (g_str_has_suffix(desktop_file, ".desktop")) { s = g_strdup(desktop_file); } else { // Add ".desktop" s = g_strdup_printf("%s.desktop", desktop_file); } // Get GDesktopAppInfo from propgram.desktop file GDesktopAppInfo *app_info = g_desktop_app_info_new(s); gchar *found_str = NULL; // .desktop file was found? if (!G_IS_DESKTOP_APP_INFO(app_info)) { // No. Search for it. g_free(s); s = NULL; // Find application's basename // Eg. "amarok" of "org.kde.amarok" gchar *p = strrchr(desktop_file, '.'); if (p && (str_length0(p) > 1)) { s = g_strdup(p + 1); } else { s = g_strdup(desktop_file); } gchar ***results = g_desktop_app_info_search(s); // results: gchar *results[][] // results is a 2-dim array of gchar* strings, the first (i dimension) contains results (another array) for each search string we passed in (in s). // Please see: https://developer.gnome.org/gio/stable/gio-Desktop-file-based-GAppInfo.html#g-desktop-app-info-search gchar *s2 = g_strdup_printf("%s.desktop", s); for (guint i=0; results && results[i] != NULL; i++) { for (guint j=0; results[i][j] != NULL; j++) { // Some nitpicking here gchar *s1_down = g_ascii_strdown(results[i][j], str_length0(results[i][j])); gchar *s2_down = g_ascii_strdown(s2, str_length0(s2)); if (g_strrstr(s1_down, s2_down)) { // Probably safe with utf8 (multibyte) // https://developer.gnome.org/glib/stable/glib-String-Utility-Functions.html#g-strrstr found_str = g_strdup(results[i][j]); } g_free(s1_down); g_free(s2_down); if (found_str) { break; } } // j... } // i... g_free(s2); // Free gchar *results[][] // Be carefull for (guint i=0; results[i] != NULL; i++) { g_strfreev(results[i]); } g_free(results); results = NULL; // Try again if (found_str) { // Load AppInfo app_info = g_desktop_app_info_new(found_str); } } g_free(found_str); g_free(s); // Do we have AppInfo? if (!G_IS_DESKTOP_APP_INFO(app_info)) { goto LBL_1; } // Read application title const gchar *app_name = g_app_info_get_name(G_APP_INFO(app_info)); if (!app_name) { app_name = g_app_info_get_display_name(G_APP_INFO(app_info)); } pl->app_name = g_strdup(app_name); // Read executable command and its arguments pl->exec_cmd = g_strdup((gchar*)g_app_info_get_commandline(G_APP_INFO(app_info))); pl->icon_name = g_desktop_app_info_get_string(app_info, "Icon"); if (!pl->icon_name){ pl->icon_name = g_strdup((gchar *)g_app_info_get_executable(G_APP_INFO(app_info))); } g_object_unref(app_info); // Remove %U from the exec command. // %u, %U in the exec command (.desktop file) is normally replaced by a file argument // Some media players show an error if the file argument is empty str_trim(pl->exec_cmd); gchar *p = g_strrstr(pl->exec_cmd, "%U"); if (!p) p = g_strrstr(pl->exec_cmd, "%u"); if (p) *p = '\0'; LBL_1: { // Basename // Eg. take "vlc" from "org.mpris.MediaPlayer2.vlc" gchar *base_name = get_base_name(pl->service_name); // Make sure these values are set if (!pl->app_name) { pl->app_name = g_strdup(base_name); } if (!pl->desktop_file) { pl->desktop_file = g_strdup(base_name); } if (!pl->exec_cmd) { pl->exec_cmd = g_strdup(base_name); } if (!pl->exec_cmd) { pl->exec_cmd = g_strdup(base_name); } if (!pl->icon_name) { pl->icon_name = g_strdup(base_name); } g_free(base_name); } } void dbus_player_reset_values(gchar *audio_source) { // Reset audio_source's internal values (in dbus-player.c module). // Get MediaPlayerRec for this audio_source (service name) MediaPlayerRec *player = dbus_player_lookup_service_name(audio_source); if (!player) { return; } // Nullify player->prev_track (TrackInfo record). // User has stopped recording from the GUI (Button or Menu), // We must clear MediaPlayerRec`s prev_track record. // User can later re-start recording from the media-player, and it is not counted as duplicate START_RECORDING message. // This is important for some media players. TrackInfo *tr = &player->prev_track; memset(tr, '\0', sizeof(TrackInfo)); }