diff --git a/src/koto-playerbar.c b/src/koto-playerbar.c index 72460e6..50b5267 100644 --- a/src/koto-playerbar.c +++ b/src/koto-playerbar.c @@ -17,11 +17,13 @@ #include #include +#include "db/cartographer.h" #include "playback/engine.h" #include "koto-button.h" #include "koto-config.h" #include "koto-playerbar.h" +extern KotoCartographer* koto_maps; extern KotoPlaybackEngine *playback_engine; struct _KotoPlayerBar { @@ -53,6 +55,10 @@ struct _KotoPlayerBar { GtkWidget *playback_title; GtkWidget *playback_album; GtkWidget *playback_artist; + + gint64 last_recorded_duration; + + gboolean is_progressbar_seeking; }; struct _KotoPlayerBarClass { @@ -77,6 +83,14 @@ static void koto_playerbar_constructed(GObject *obj) { gtk_scale_set_draw_value(GTK_SCALE(self->progress_bar), FALSE); gtk_scale_set_digits(GTK_SCALE(self->progress_bar), 0); gtk_range_set_increments(GTK_RANGE(self->progress_bar), 1, 1); + gtk_range_set_round_digits(GTK_RANGE(self->progress_bar), 1); + + GtkGesture *press_controller = gtk_gesture_click_new(); // Create a new GtkGestureLongPress + 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); + 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); self->controls = gtk_center_box_new(); gtk_center_box_set_baseline_position(GTK_CENTER_BOX(self->controls), GTK_BASELINE_POSITION_CENTER); @@ -101,13 +115,16 @@ static void koto_playerbar_constructed(GObject *obj) { // Set up the bindings - g_signal_connect(playback_engine, "duration-changed", G_CALLBACK(koto_playerbar_handle_duration_change), self); - g_signal_connect(playback_engine, "play-state-changed", G_CALLBACK(koto_playerbar_handle_engine_state_change), self); - g_signal_connect(playback_engine, "progress-changed", G_CALLBACK(koto_playerbar_handle_progress_change), self); + g_signal_connect(playback_engine, "is-playing", G_CALLBACK(koto_playerbar_handle_is_playing), self); + g_signal_connect(playback_engine, "is-paused", G_CALLBACK(koto_playerbar_handle_is_paused), self); + g_signal_connect(playback_engine, "tick-duration", G_CALLBACK(koto_playerbar_handle_tick_duration), self); + g_signal_connect(playback_engine, "tick-track", G_CALLBACK(koto_playerbar_handle_tick_track), self); + g_signal_connect(playback_engine, "track-changed", G_CALLBACK(koto_playerbar_update_track_info), self); } static void koto_playerbar_init(KotoPlayerBar *self) { - (void) self; + self->last_recorded_duration = 0; + self->is_progressbar_seeking = FALSE; } KotoPlayerBar* koto_playerbar_new(void) { @@ -182,8 +199,11 @@ void koto_playerbar_create_secondary_controls(KotoPlayerBar* bar) { 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) { gtk_box_append(GTK_BOX(bar->secondary_controls_section), GTK_WIDGET(bar->repeat_button)); } @@ -217,16 +237,7 @@ void koto_playerbar_go_forwards(GtkGestureClick *gesture, int n_press, double x, koto_playback_engine_forwards(playback_engine); } -void koto_playerbar_handle_duration_change(KotoPlaybackEngine *engine, gpointer user_data) { - if (!KOTO_IS_PLAYBACK_ENGINE(engine)) { - return; - } - - KotoPlayerBar *bar = user_data; - koto_playerbar_set_progressbar_duration(bar, koto_playback_engine_get_duration(engine)); -} - -void koto_playerbar_handle_engine_state_change(KotoPlaybackEngine *engine, gpointer user_data) { +void koto_playerbar_handle_is_playing(KotoPlaybackEngine *engine, gpointer user_data) { if (!KOTO_IS_PLAYBACK_ENGINE(engine)) { return; } @@ -237,22 +248,96 @@ void koto_playerbar_handle_engine_state_change(KotoPlaybackEngine *engine, gpoin return; } - GstState new_state = koto_playback_engine_get_state(playback_engine); - - if (new_state == GST_STATE_PLAYING) { // Now playing - koto_button_show_image(bar->play_pause_button, TRUE); // Set to TRUE to show pause as the next action - } else if (new_state == GST_STATE_PAUSED) { // Now paused - koto_button_show_image(bar->play_pause_button, FALSE); // Set to FALSE to show play as the next action - } + koto_button_show_image(bar->play_pause_button, TRUE); // Set to TRUE to show pause as the next action } -void koto_playerbar_handle_progress_change(KotoPlaybackEngine *engine, gpointer user_data) { +void koto_playerbar_handle_is_paused(KotoPlaybackEngine *engine, gpointer user_data) { if (!KOTO_IS_PLAYBACK_ENGINE(engine)) { return; } KotoPlayerBar *bar = user_data; - koto_playerbar_set_progressbar_value(bar, koto_playback_engine_get_progress(engine)); + + if (!KOTO_IS_PLAYERBAR(bar)) { + return; + } + + koto_button_show_image(bar->play_pause_button, FALSE); // Set to FALSE to show play as the next action +} + +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; + + if (!KOTO_IS_PLAYERBAR(bar)) { + return; + } + + 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; + + if (!KOTO_IS_PLAYERBAR(bar)) { + return; + } + + if (!bar->is_progressbar_seeking) { + return; + } + + int desired_position = (int) gtk_range_get_value(progress_bar); + + koto_playback_engine_set_position(playback_engine, desired_position); // Update our position +} + +void koto_playerbar_handle_tick_duration(KotoPlaybackEngine *engine, gpointer user_data) { + if (!KOTO_IS_PLAYBACK_ENGINE(engine)) { + return; + } + + KotoPlayerBar *bar = user_data; + + if (!KOTO_IS_PLAYERBAR(bar)) { + return; + } + + koto_playerbar_set_progressbar_duration(bar, koto_playback_engine_get_duration(engine)); +} + + +void koto_playerbar_handle_tick_track(KotoPlaybackEngine *engine, gpointer user_data) { + if (!KOTO_IS_PLAYBACK_ENGINE(engine)) { + return; + } + + KotoPlayerBar *bar = user_data; + + if (!KOTO_IS_PLAYERBAR(bar)) { + return; + } + + if (!bar->is_progressbar_seeking) { + koto_playerbar_set_progressbar_value(bar, koto_playback_engine_get_progress(engine)); + } +} + +void koto_playerbar_handle_volume_button_change(GtkScaleButton *button, double value, gpointer user_data) { + (void) button; (void) user_data; + koto_playback_engine_set_volume(playback_engine, value); } void koto_playerbar_reset_progressbar(KotoPlayerBar* bar) { @@ -261,18 +346,17 @@ void koto_playerbar_reset_progressbar(KotoPlayerBar* bar) { } void koto_playerbar_set_progressbar_duration(KotoPlayerBar* bar, gint64 duration) { - if (duration < 0) { + if (duration <= 0) { return; } - gtk_range_set_range(GTK_RANGE(bar->progress_bar), 0, duration); + if (duration != bar->last_recorded_duration) { // Duration is different than what we recorded + bar->last_recorded_duration = duration; + gtk_range_set_range(GTK_RANGE(bar->progress_bar), 0, bar->last_recorded_duration); + } } void koto_playerbar_set_progressbar_value(KotoPlayerBar* bar, gint64 progress) { - if (progress < 0) { - return; - } - gtk_range_set_value(GTK_RANGE(bar->progress_bar), progress); } @@ -282,6 +366,71 @@ void koto_playerbar_toggle_play_pause(GtkGestureClick *gesture, int n_press, dou koto_playback_engine_toggle(playback_engine); } +void koto_playerbar_update_track_info(KotoPlaybackEngine *engine, gpointer user_data) { + if (!KOTO_IS_PLAYBACK_ENGINE(engine)) { + return; + } + + KotoPlayerBar *bar = user_data; + + if (!KOTO_IS_PLAYERBAR(bar)) { + return; + } + + KotoIndexedTrack *current_track = koto_playback_engine_get_current_track(playback_engine); // Get the current track from the playback engine + + if (!KOTO_IS_INDEXED_TRACK(current_track)) { + return; + } + + gchar *track_name = NULL; + gchar *artist_uuid = NULL; + gchar *album_uuid = NULL; + + g_object_get(current_track, "parsed-name", &track_name, "artist-uuid", &artist_uuid, "album-uuid", &album_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_free(artist_uuid); + g_free(album_uuid); + + if ((track_name != NULL) && (strcmp(track_name, "") != 0)) { // Have a track name + gtk_label_set_text(GTK_LABEL(bar->playback_title), track_name); // Set the label + } + + if (KOTO_IS_INDEXED_ARTIST(artist)) { + gchar *artist_name = NULL; + g_object_get(artist, "name", &artist_name, NULL); + + if ((artist_name != NULL) && (strcmp(artist_name, "") != 0)) { // Have an artist name + gtk_label_set_text(GTK_LABEL(bar->playback_artist), artist_name); + gtk_widget_show(bar->playback_artist); + } else { // Don't have an artist name somehow + gtk_widget_hide(bar->playback_artist); + } + } + + if (KOTO_IS_INDEXED_ALBUM(album)) { + gchar *album_name = NULL; + gchar *art_path = NULL; + g_object_get(album, "name", &album_name, "art-path", &art_path, NULL); // Get album name and art path + + if ((album_name != NULL) && (strcmp(album_name, "") != 0)) { // Have an album name + gtk_label_set_text(GTK_LABEL(bar->playback_album), album_name); + gtk_widget_show(bar->playback_album); + } else { + gtk_widget_hide(bar->playback_album); + } + + if ((art_path != NULL) && g_path_is_absolute(art_path)) { // Have an album artist path + gtk_image_set_from_file(GTK_IMAGE(bar->artwork), art_path); // Update the art + } else { + gtk_image_set_from_icon_name(GTK_IMAGE(bar->artwork), "audio-x-generic-symbolic"); // Use generic instead + } + } +} + GtkWidget* koto_playerbar_get_main(KotoPlayerBar* bar) { return bar->main; } diff --git a/src/koto-playerbar.h b/src/koto-playerbar.h index 5ad9300..23838ae 100644 --- a/src/koto-playerbar.h +++ b/src/koto-playerbar.h @@ -33,12 +33,18 @@ void koto_playerbar_create_primary_controls(KotoPlayerBar* bar); void koto_playerbar_create_secondary_controls(KotoPlayerBar* bar); void koto_playerbar_go_backwards(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data); void koto_playerbar_go_forwards(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data); -void koto_playerbar_handle_duration_change(KotoPlaybackEngine *engine, gpointer user_data); -void koto_playerbar_handle_engine_state_change(KotoPlaybackEngine *engine, gpointer user_data); -void koto_playerbar_handle_progress_change(KotoPlaybackEngine *engine, gpointer user_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_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); +void koto_playerbar_handle_volume_button_change(GtkScaleButton *button, double value, gpointer user_data); 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_update_track_info(KotoPlaybackEngine *engine, gpointer user_data); G_END_DECLS diff --git a/src/playback/engine.c b/src/playback/engine.c index 97967f5..9edee31 100644 --- a/src/playback/engine.c +++ b/src/playback/engine.c @@ -24,9 +24,12 @@ #include "engine.h" enum { - SIGNAL_DURATION_CHANGE, + SIGNAL_IS_PLAYING, + SIGNAL_IS_PAUSED, SIGNAL_PLAY_STATE_CHANGE, - SIGNAL_PROGRESS_CHANGE, + SIGNAL_TICK_DURATION, + SIGNAL_TICK_TRACK, + SIGNAL_TRACK_CHANGE, N_SIGNALS }; @@ -43,15 +46,19 @@ struct _KotoPlaybackEngine { GstElement *player; GstElement *playbin; GstElement *suppress_video; - GstBus *monitor; - gboolean is_paused; - gboolean is_playing; + KotoIndexedTrack *current_track; + gboolean is_muted; gboolean is_repeat_enabled; gboolean is_shuffle_enabled; - gboolean requested_playing; + + gboolean is_playing; + + gboolean tick_duration_timer_running; + gboolean tick_track_timer_running; + guint playback_position; gdouble volume; }; @@ -59,9 +66,12 @@ struct _KotoPlaybackEngine { struct _KotoPlaybackEngineClass { GObjectClass parent_class; - void (* duration_changed) (KotoPlaybackEngine *engine, gint64 duration); + void (* is_playing) (KotoPlaybackEngine *engine); + void (* is_paused) (KotoPlaybackEngine *engine); void (* play_state_changed) (KotoPlaybackEngine *engine); - void (* progress_changed) (KotoPlaybackEngine *engine, gint64 progress); + void (* tick_duration) (KotoPlaybackEngine *engine); + void (* tick_track) (KotoPlaybackEngine *engine); + void (* track_changed) (KotoPlaybackEngine *engine); }; G_DEFINE_TYPE(KotoPlaybackEngine, koto_playback_engine, G_TYPE_OBJECT); @@ -72,11 +82,23 @@ static void koto_playback_engine_class_init(KotoPlaybackEngineClass *c) { GObjectClass *gobject_class; gobject_class = G_OBJECT_CLASS(c); - playback_engine_signals[SIGNAL_DURATION_CHANGE] = g_signal_new( - "duration-changed", + playback_engine_signals[SIGNAL_IS_PLAYING] = g_signal_new( + "is-playing", G_TYPE_FROM_CLASS(gobject_class), G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoPlaybackEngineClass, duration_changed), + G_STRUCT_OFFSET(KotoPlaybackEngineClass, play_state_changed), + NULL, + NULL, + NULL, + G_TYPE_NONE, + 0 + ); + + playback_engine_signals[SIGNAL_IS_PAUSED] = g_signal_new( + "is-paused", + G_TYPE_FROM_CLASS(gobject_class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET(KotoPlaybackEngineClass, play_state_changed), NULL, NULL, NULL, @@ -96,11 +118,35 @@ static void koto_playback_engine_class_init(KotoPlaybackEngineClass *c) { 0 ); - playback_engine_signals[SIGNAL_PROGRESS_CHANGE] = g_signal_new( - "progress-changed", + playback_engine_signals[SIGNAL_TICK_DURATION] = g_signal_new( + "tick-duration", G_TYPE_FROM_CLASS(gobject_class), G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoPlaybackEngineClass, progress_changed), + G_STRUCT_OFFSET(KotoPlaybackEngineClass, tick_duration), + NULL, + NULL, + NULL, + G_TYPE_NONE, + 0 + ); + + playback_engine_signals[SIGNAL_TICK_TRACK] = g_signal_new( + "tick-track", + G_TYPE_FROM_CLASS(gobject_class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET(KotoPlaybackEngineClass, tick_track), + NULL, + NULL, + NULL, + G_TYPE_NONE, + 0 + ); + + playback_engine_signals[SIGNAL_TRACK_CHANGE] = g_signal_new( + "track-changed", + G_TYPE_FROM_CLASS(gobject_class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET(KotoPlaybackEngineClass, track_changed), NULL, NULL, NULL, @@ -110,27 +156,28 @@ static void koto_playback_engine_class_init(KotoPlaybackEngineClass *c) { } static void koto_playback_engine_init(KotoPlaybackEngine *self) { + self->current_track = NULL; + self->player = gst_pipeline_new("player"); self->playbin = gst_element_factory_make("playbin", NULL); self->suppress_video = gst_element_factory_make("fakesink", "suppress-video"); g_object_set(self->playbin, "video-sink", self->suppress_video, NULL); - g_object_set(self->playbin, "volume", 0.5, NULL); + koto_playback_engine_set_volume(self, 0.5); gst_bin_add(GST_BIN(self->player), self->playbin); self->monitor = gst_bus_new(); // Get the bus for the playbin if (GST_IS_BUS(self->monitor)) { gst_bus_add_watch(self->monitor, koto_playback_engine_monitor_changed, self); - gst_element_set_bus(self->playbin, self->monitor); // Set our bus to monitor changes + gst_element_set_bus(self->player, self->monitor); // Set our bus to monitor changes } self->is_muted = FALSE; - self->is_playing = FALSE; - self->is_paused = FALSE; self->is_repeat_enabled = FALSE; self->is_shuffle_enabled = FALSE; - self->requested_playing = FALSE; + self->tick_duration_timer_running = FALSE; + self->tick_track_timer_running = FALSE; if (KOTO_IS_CURRENT_PLAYLIST(current_playlist)) { g_signal_connect(current_playlist, "notify::current-playlist", G_CALLBACK(koto_playback_engine_current_playlist_changed), NULL); @@ -171,9 +218,13 @@ void koto_playback_engine_forwards(KotoPlaybackEngine *self) { koto_playback_engine_set_track_by_uuid(self, koto_playlist_go_to_next(playlist)); } +KotoIndexedTrack* koto_playback_engine_get_current_track(KotoPlaybackEngine *self) { + return self->current_track; +} + gint64 koto_playback_engine_get_duration(KotoPlaybackEngine *self) { gint64 duration = 0; - if (gst_element_query_duration(self->playbin, GST_FORMAT_TIME, &duration)) { + if (gst_element_query_duration(self->player, GST_FORMAT_TIME, &duration)) { duration = duration / NS; // Divide by NS to get seconds } @@ -182,18 +233,14 @@ gint64 koto_playback_engine_get_duration(KotoPlaybackEngine *self) { GstState koto_playback_engine_get_state(KotoPlaybackEngine *self) { GstState current_state; - GstStateChangeReturn ret = gst_element_get_state(self->playbin, ¤t_state, NULL, GST_SECOND); // Get the current state, allowing up to a second to get it - - if (ret != GST_STATE_CHANGE_SUCCESS) { // Got the data we need - return GST_STATE_NULL; - } + 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->playbin, GST_FORMAT_TIME, &progress)) { + if (gst_element_query_position(self->player, GST_FORMAT_TIME, &progress)) { progress = progress / NS; // Divide by NS to get seconds } @@ -205,34 +252,24 @@ gboolean koto_playback_engine_monitor_changed(GstBus *bus, GstMessage *msg, gpoi KotoPlaybackEngine *self = user_data; switch (GST_MESSAGE_TYPE(msg)) { - case GST_MESSAGE_ASYNC_DONE: { // Finished loading - if (self->requested_playing) { - self->is_playing = TRUE; - self->is_paused = FALSE; - g_timeout_add(50, koto_playback_engine_update_progress, self); - gst_element_set_state(self->playbin, GST_STATE_PLAYING); // Make double sure the state is playing - g_signal_emit(self, playback_engine_signals[SIGNAL_DURATION_CHANGE], 0); // Emit our duration signal - g_signal_emit(self, playback_engine_signals[SIGNAL_PLAY_STATE_CHANGE], 0); // Emit our playing signal - } + case GST_MESSAGE_DURATION_CHANGED: { // Duration changed + koto_playback_engine_tick_duration(self); break; } case GST_MESSAGE_STATE_CHANGED: { // State changed GstState old_state; - GstState requested_state; GstState new_state; - gst_message_parse_state_changed(msg, &old_state, &requested_state, &new_state); + GstState pending_state; + gst_message_parse_state_changed(msg, &old_state, &new_state, &pending_state); - if ((old_state == GST_STATE_PLAYING) && (requested_state == GST_STATE_PAUSED)) { - self->is_playing = FALSE; - self->is_paused = TRUE; + if (new_state == GST_STATE_PLAYING) { // Now playing + 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 + } - g_signal_emit(self, playback_engine_signals[SIGNAL_PLAY_STATE_CHANGE], 0); // Emit our paused signal - } else if (requested_state == GST_STATE_PLAYING && !self->is_playing) { - self->is_playing = TRUE; - self->is_paused = FALSE; - g_signal_emit(self, playback_engine_signals[SIGNAL_PLAY_STATE_CHANGE], 0); // Emit our paused signal - } else if (((old_state == GST_STATE_PLAYING) || (old_state == GST_STATE_PAUSED)) && (new_state == GST_STATE_NULL)) { // If we're freeing resources - gst_bus_post(self->monitor, gst_message_new_reset_time(GST_OBJECT(self->playbin), 0)); + if (new_state != GST_STATE_VOID_PENDING) { + g_signal_emit(self, playback_engine_signals[SIGNAL_PLAY_STATE_CHANGE], 0); // Emit our play state signal } break; @@ -248,13 +285,27 @@ gboolean koto_playback_engine_monitor_changed(GstBus *bus, GstMessage *msg, gpoi } void koto_playback_engine_play(KotoPlaybackEngine *self) { - self->requested_playing = TRUE; - gst_element_set_state(self->playbin, GST_STATE_PLAYING); // Set our state to play + self->is_playing = TRUE; + gst_element_set_state(self->player, GST_STATE_PLAYING); // Set our state to play + + if (!self->tick_duration_timer_running) { + self->tick_duration_timer_running = TRUE; + g_timeout_add(1000, koto_playback_engine_tick_duration, self); // Create a 1s duration tick + } + + if (!self->tick_track_timer_running) { + self->tick_track_timer_running = TRUE; + g_timeout_add(100, koto_playback_engine_tick_track, self); // Create a 100ms track tick + } } void koto_playback_engine_pause(KotoPlaybackEngine *self) { - self->requested_playing = FALSE; - gst_element_set_state(self->playbin, GST_STATE_PAUSED); // Set our state to paused + self->is_playing = FALSE; + gst_element_change_state(self->player, GST_STATE_CHANGE_PLAYING_TO_PAUSED); +} + +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_by_uuid(KotoPlaybackEngine *self, gchar *track_uuid) { @@ -268,6 +319,8 @@ void koto_playback_engine_set_track_by_uuid(KotoPlaybackEngine *self, gchar *tra return; } + self->current_track = track; + gchar *track_file_path = NULL; g_object_get(track, "path", &track_file_path, NULL); // Get the path to the track @@ -279,10 +332,19 @@ void koto_playback_engine_set_track_by_uuid(KotoPlaybackEngine *self, gchar *tra g_free(gst_filename); // Free the filename koto_playback_engine_play(self); // Play the new track + + // 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); + + g_signal_emit(self, playback_engine_signals[SIGNAL_TRACK_CHANGE], 0); // Emit our track change signal +} + +void koto_playback_engine_set_volume(KotoPlaybackEngine *self, gdouble volume) { + g_object_set(self->playbin, "volume", volume, NULL); } void koto_playback_engine_stop(KotoPlaybackEngine *self) { - gst_element_set_state(self->playbin, GST_STATE_NULL); + 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 if (!GST_IS_PAD(pad)) { @@ -293,17 +355,32 @@ void koto_playback_engine_stop(KotoPlaybackEngine *self) { } void koto_playback_engine_toggle(KotoPlaybackEngine *self) { - if (koto_playback_engine_get_state(self) == GST_STATE_PLAYING) { // Currently playing + if (self->is_playing) { // Currently playing koto_playback_engine_pause(self); // Pause } else { koto_playback_engine_play(self); // Play } } -gboolean koto_playback_engine_update_progress(gpointer user_data) { +gboolean koto_playback_engine_tick_duration(gpointer user_data) { KotoPlaybackEngine *self = user_data; + if (self->is_playing) { // Is playing - g_signal_emit(self, playback_engine_signals[SIGNAL_PROGRESS_CHANGE], 0); // Emit our progress change signal + g_signal_emit(self, playback_engine_signals[SIGNAL_TICK_DURATION], 0); // Emit our 1s track tick + } else { // Not playing so exiting timer + self->tick_duration_timer_running = FALSE; + } + + return self->is_playing; +} + +gboolean koto_playback_engine_tick_track(gpointer user_data) { + KotoPlaybackEngine *self = user_data; + + if (self->is_playing) { // Is playing + g_signal_emit(self, playback_engine_signals[SIGNAL_TICK_TRACK], 0); // Emit our 100ms track tick + } else { + self->tick_track_timer_running = FALSE; } return self->is_playing; diff --git a/src/playback/engine.h b/src/playback/engine.h index 9871cda..eb4991c 100644 --- a/src/playback/engine.h +++ b/src/playback/engine.h @@ -45,6 +45,7 @@ KotoPlaybackEngine* koto_playback_engine_new(); void koto_playback_engine_backwards(KotoPlaybackEngine *self); void koto_playback_engine_current_playlist_changed(); void koto_playback_engine_forwards(KotoPlaybackEngine *self); +KotoIndexedTrack* koto_playback_engine_get_current_track(KotoPlaybackEngine *self); gint64 koto_playback_engine_get_duration(KotoPlaybackEngine *self); GstState koto_playback_engine_get_state(KotoPlaybackEngine *self); gint64 koto_playback_engine_get_progress(KotoPlaybackEngine *self); @@ -53,11 +54,12 @@ gboolean koto_playback_engine_monitor_changed(GstBus *bus, GstMessage *msg, gpoi 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, guint position); +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_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_update_duration(KotoPlaybackEngine *self); -gboolean koto_playback_engine_update_progress(gpointer user_data); +gboolean koto_playback_engine_tick_duration(gpointer user_data); +gboolean koto_playback_engine_tick_track(gpointer user_data);