From 62f288384932c2b7faa099d456162af37edef1a8 Mon Sep 17 00:00:00 2001 From: Joshua Strobl Date: Tue, 17 Aug 2021 19:27:33 +0300 Subject: [PATCH] Implement playback speed and jump forwards / backwards functionality. Ref #14. Additionally fixed some bugs around the KotoButton showing the badge text label when it shouldn't, styling related to margins, and various legacy null and string valid checks. --- data/vectors/multimedia-backwards-jump.svg | 9 ++ data/vectors/multimedia-forwards-jump.svg | 9 ++ src/components/button.c | 86 +++++++++++++++++--- src/components/button.h | 10 +++ src/config/config.c | 46 +++++++++++ src/koto-playerbar.c | 95 ++++++++++++++++++++++ src/koto-playerbar.h | 21 +++++ src/koto.gresource.xml | 2 + src/playback/engine.c | 79 +++++++++++++++++- src/playback/engine.h | 17 +++- theme/_button.scss | 4 +- theme/_main.scss | 2 +- theme/_player-bar.scss | 17 ++++ theme/_vars.scss | 6 +- theme/components/_writer-page.scss | 2 +- theme/pages/_audiobook-library.scss | 4 +- theme/pages/_music-local.scss | 2 +- 17 files changed, 387 insertions(+), 24 deletions(-) create mode 100644 data/vectors/multimedia-backwards-jump.svg create mode 100644 data/vectors/multimedia-forwards-jump.svg diff --git a/data/vectors/multimedia-backwards-jump.svg b/data/vectors/multimedia-backwards-jump.svg new file mode 100644 index 0000000..f5c9016 --- /dev/null +++ b/data/vectors/multimedia-backwards-jump.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/data/vectors/multimedia-forwards-jump.svg b/data/vectors/multimedia-forwards-jump.svg new file mode 100644 index 0000000..9db39bf --- /dev/null +++ b/data/vectors/multimedia-forwards-jump.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/components/button.c b/src/components/button.c index f04f545..779128a 100644 --- a/src/components/button.c +++ b/src/components/button.c @@ -62,6 +62,7 @@ enum { PROP_IMAGE_FILE_PATH, PROP_ICON_NAME, PROP_ALT_ICON_NAME, + PROP_RESOURCE_PATH, N_BTN_PROPERTIES }; @@ -86,6 +87,7 @@ struct _KotoButton { gchar * badge_text; gchar * icon_name; gchar * alt_icon_name; + gchar * resource_path; gchar * text; KotoButtonImagePosition image_position; @@ -181,6 +183,14 @@ static void koto_button_class_init(KotoButtonClass * c) { G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE ); + btn_props[PROP_RESOURCE_PATH] = g_param_spec_string( + "resource-path", + "Resource Path to an Icon", + "Resource Path to an Icon", + NULL, + G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE + ); + g_object_class_install_properties(gobject_class, N_BTN_PROPERTIES, btn_props); } @@ -191,6 +201,8 @@ static void koto_button_init(KotoButton * self) { self->left_click_gesture = gtk_gesture_click_new(); // Set up our left click gesture self->right_click_gesture = gtk_gesture_click_new(); // Set up our right click gesture + self->resource_path = NULL; + gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(self->left_click_gesture), (int) KOTO_BUTTON_CLICK_TYPE_PRIMARY); // Only allow left clicks on left click gesture gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(self->right_click_gesture), (int) KOTO_BUTTON_CLICK_TYPE_SECONDARY); // Only allow right clicks on right click gesture @@ -242,6 +254,9 @@ static void koto_button_get_property( case PROP_ALT_ICON_NAME: g_value_set_string(val, self->alt_icon_name); break; + case PROP_RESOURCE_PATH: + g_value_set_string(val, self->resource_path); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); break; @@ -261,7 +276,7 @@ static void koto_button_set_property( koto_button_set_pixbuf_size(self, g_value_get_uint(val)); break; case PROP_TEXT: - if (val != NULL) { + if (koto_utils_string_is_valid(g_value_get_string(val))) { koto_button_set_text(self, (gchar*) g_value_get_string(val)); } @@ -287,6 +302,9 @@ static void koto_button_set_property( koto_button_show_image(self, TRUE); } break; + case PROP_RESOURCE_PATH: + koto_button_set_resource_path(self, g_strdup(g_value_get_string(val))); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); break; @@ -424,13 +442,22 @@ void koto_button_set_badge_text( return; } - if ((text == NULL) || (strcmp(text, "") == 0)) { // If the text is empty - self->badge_text = g_strdup(""); - } else { + if (koto_utils_string_is_valid(self->badge_text)) { // Have existing text g_free(self->badge_text); - self->badge_text = g_strdup(text); } + if (!koto_utils_string_is_valid(text)) { // If the text is empty + self->badge_text = NULL; + + if (GTK_IS_LABEL(self->badge_label)) { // If badge label already exists + gtk_widget_hide(self->badge_label); // Hide the label + } + + return; + } + + self->badge_text = g_strdup(text); + if (GTK_IS_LABEL(self->badge_label)) { // If badge label already exists gtk_label_set_text(GTK_LABEL(self->badge_label), self->badge_text); } else { @@ -438,12 +465,6 @@ void koto_button_set_badge_text( gtk_box_append(GTK_BOX(self), self->badge_label); } - if (strcmp(self->badge_text, "") != 0) { // Empty badge - gtk_widget_hide(self->badge_label); // Hide our badge - } else { // Have some text - gtk_widget_show(self->badge_label); // Show our badge - } - g_object_notify_by_pspec(G_OBJECT(self), btn_props[PROP_BADGE_TEXT]); } @@ -561,6 +582,34 @@ void koto_button_set_pixbuf_size( g_object_notify_by_pspec(G_OBJECT(self), btn_props[PROP_PIX_SIZE]); } +void koto_button_set_resource_path( + KotoButton * self, + gchar * resource_path +) { + if (!KOTO_IS_BUTTON(self)) { + return; + } + + if (!koto_utils_string_is_valid(resource_path)) { // Not a valid string + return; + } + + if (koto_utils_string_is_valid(self->resource_path)) { // Have a resource path already + g_free(self->resource_path); // Free it + } + + self->resource_path = g_strdup(resource_path); + + if (GTK_IS_IMAGE(self->button_pic)) { // Already have a button image + gtk_image_set_from_resource(GTK_IMAGE(self->button_pic), self->resource_path); + } else { + self->button_pic = gtk_image_new_from_resource(self->resource_path); // Create a new image from the resource + gtk_image_set_pixel_size(GTK_IMAGE(self->button_pic), self->pix_size); + gtk_image_set_icon_size(GTK_IMAGE(self->button_pic), GTK_ICON_SIZE_INHERIT); // Inherit height of parent widget + gtk_box_prepend(GTK_BOX(self), self->button_pic); // Prepend to the box + } +} + void koto_button_set_text( KotoButton * self, gchar * text @@ -590,6 +639,7 @@ void koto_button_set_text( } else { // If we do not have a button label if (koto_utils_string_is_valid(self->text)) { // If we have text self->button_label = gtk_label_new(self->text); // Create our label + gtk_widget_add_css_class(self->button_label, "button-label"); gtk_widget_set_hexpand(self->button_label, TRUE); gtk_label_set_xalign(GTK_LABEL(self->button_label), 0); @@ -752,3 +802,17 @@ KotoButton * koto_button_new_with_file( NULL ); } + +KotoButton * koto_button_new_with_resource ( + gchar * resource_path, + KotoButtonPixbufSize size +) { + return g_object_new( + KOTO_TYPE_BUTTON, + "resource-path", + resource_path, + "pixbuf-size", + koto_get_pixbuf_size(size), + NULL + ); +} diff --git a/src/components/button.h b/src/components/button.h index f41359b..70648bc 100644 --- a/src/components/button.h +++ b/src/components/button.h @@ -66,6 +66,11 @@ KotoButton * koto_button_new_with_file( KotoButtonPixbufSize size ); +KotoButton * koto_button_new_with_resource( + gchar * resource_path, + KotoButtonPixbufSize size +); + void koto_button_add_click_handler( KotoButton * self, KotoButtonClickType button, @@ -127,6 +132,11 @@ void koto_button_set_image_position( KotoButtonImagePosition pos ); +void koto_button_set_resource_path( + KotoButton * self, + gchar * resource_path +); + void koto_button_set_pixbuf( KotoButton * self, GdkPixbuf * pix diff --git a/src/config/config.c b/src/config/config.c index d97403f..e31b4d2 100644 --- a/src/config/config.c +++ b/src/config/config.c @@ -37,6 +37,8 @@ enum { PROP_PLAYBACK_CONTINUE_ON_PLAYLIST, PROP_PLAYBACK_LAST_USED_VOLUME, PROP_PLAYBACK_MAINTAIN_SHUFFLE, + PROP_PLAYBACK_JUMP_BACKWARDS_INCREMENT, + PROP_PLAYBACK_JUMP_FORWARDS_INCREMENT, PROP_PREFERRED_ALBUM_SORT_TYPE, PROP_UI_ALBUM_INFO_SHOW_DESCRIPTION, PROP_UI_ALBUM_INFO_SHOW_GENRES, @@ -73,6 +75,8 @@ struct _KotoConfig { gboolean playback_continue_on_playlist; gdouble playback_last_used_volume; gboolean playback_maintain_shuffle; + guint playback_jump_backwards_increment; + guint playback_jump_forwards_increment; /* Misc Prefs */ @@ -146,6 +150,26 @@ static void koto_config_class_init(KotoConfigClass * c) { G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE ); + config_props[PROP_PLAYBACK_JUMP_BACKWARDS_INCREMENT] = g_param_spec_uint( + "playback-jump-backwards-increment", + "Jump Backwards Increment", + "Jump Backwards Increment", + 5, // 5s + 90, // 1min30s + 10, // 10s + G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE + ); + + config_props[PROP_PLAYBACK_JUMP_FORWARDS_INCREMENT] = g_param_spec_uint( + "playback-jump-forwards-increment", + "Jump Forwards Increment", + "Jump Forwards Increment", + 5, // 5s + 90, // 1min30s + 30, // 30s + G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE + ); + config_props[PROP_PREFERRED_ALBUM_SORT_TYPE] = g_param_spec_string( "artist-preferred-album-sort-type", "Preferred album sort type (chronological or alphabetical-only)", @@ -235,6 +259,12 @@ static void koto_config_get_property( case PROP_PLAYBACK_MAINTAIN_SHUFFLE: g_value_set_boolean(val, self->playback_maintain_shuffle); break; + case PROP_PLAYBACK_JUMP_BACKWARDS_INCREMENT: + g_value_set_uint(val, self->playback_jump_backwards_increment); + break; + case PROP_PLAYBACK_JUMP_FORWARDS_INCREMENT: + g_value_set_uint(val, self->playback_jump_forwards_increment); + break; case PROP_UI_ALBUM_INFO_SHOW_DESCRIPTION: g_value_set_boolean(val, self->ui_album_info_show_description); break; @@ -277,6 +307,12 @@ static void koto_config_set_property( case PROP_PLAYBACK_MAINTAIN_SHUFFLE: self->playback_maintain_shuffle = g_value_get_boolean(val); break; + case PROP_PLAYBACK_JUMP_BACKWARDS_INCREMENT: + self->playback_jump_backwards_increment = g_value_get_uint(val); + break; + case PROP_PLAYBACK_JUMP_FORWARDS_INCREMENT: + self->playback_jump_forwards_increment = g_value_get_uint(val); + break; case PROP_PREFERRED_ALBUM_SORT_TYPE: self->preferred_album_sort_type = g_strcmp0(g_value_get_string(val), "alphabetical") ? KOTO_PREFERRED_ALBUM_ALWAYS_ALPHABETICAL : KOTO_PREFERRED_ALBUM_SORT_TYPE_DEFAULT; break; @@ -434,6 +470,8 @@ void koto_config_load( if (playback_section) { // Have playback section toml_datum_t continue_on_playlist = toml_bool_in(playback_section, "continue-on-playlist"); + toml_datum_t jump_backwards_increment = toml_int_in(playback_section, "jump-backwards-increment"); + toml_datum_t jump_forwards_increment = toml_int_in(playback_section, "jump-forwards-increment"); toml_datum_t last_used_volume = toml_double_in(playback_section, "last-used-volume"); toml_datum_t maintain_shuffle = toml_bool_in(playback_section, "maintain-shuffle"); @@ -441,6 +479,14 @@ void koto_config_load( g_object_set(self, "playback-continue-on-playlist", continue_on_playlist.u.b, NULL); } + if (jump_backwards_increment.ok && (self->playback_jump_backwards_increment != jump_backwards_increment.u.i)) { // If we have a jump-backwards-increment set and it is different + g_object_set(self, "playback-jump-backwards-increment", (guint) jump_backwards_increment.u.i, NULL); + } + + if (jump_forwards_increment.ok && (self->playback_jump_forwards_increment != jump_forwards_increment.u.i)) { // If we have a jump-backwards-increment set and it is different + g_object_set(self, "playback-jump-forwards-increment", (guint) jump_forwards_increment.u.i, NULL); + } + if (last_used_volume.ok && (self->playback_last_used_volume != last_used_volume.u.d)) { // If we have last-used-volume set and they are different g_object_set(self, "playback-last-used-volume", last_used_volume.u.d, NULL); } diff --git a/src/koto-playerbar.c b/src/koto-playerbar.c index 7070e30..be1d5ce 100644 --- a/src/koto-playerbar.c +++ b/src/koto-playerbar.c @@ -64,6 +64,12 @@ struct _KotoPlayerBar { GtkWidget * playback_album; GtkWidget * playback_artist; + /* Advanced Controls (Jump Back / Forwards, Speed) */ + GtkWidget * advanced_controls_section; + KotoButton * backwards_jump_button; + KotoButton * forwards_jump_button; + GtkWidget * playback_speed_input; + /* Misc Widgets */ GtkWidget * playback_position_label; @@ -236,6 +242,40 @@ void koto_playerbar_create_primary_controls(KotoPlayerBar * self) { gtk_box_append(GTK_BOX(self->primary_controls_section), GTK_WIDGET(self->forward_button)); koto_button_add_click_handler(self->forward_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_go_forwards), self); } + + self->advanced_controls_section = gtk_center_box_new(); + gtk_widget_add_css_class(self->advanced_controls_section, "playerbar-advanced-controls"); + + self->backwards_jump_button = koto_button_new_with_resource("/com/github/joshstrobl/koto/multimedia-backwards-jump", KOTO_BUTTON_PIXBUF_SIZE_NORMAL); + koto_button_add_click_handler(self->backwards_jump_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_jump_backwards), self); + + self->forwards_jump_button = koto_button_new_with_resource("/com/github/joshstrobl/koto/multimedia-forwards-jump", KOTO_BUTTON_PIXBUF_SIZE_NORMAL); + koto_button_add_click_handler(self->forwards_jump_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_jump_forwards), self); + + self->playback_speed_input = gtk_entry_new_with_buffer(gtk_entry_buffer_new("1.0", -1)); // Create an input that defaults to 1.0 + gtk_entry_set_max_length(GTK_ENTRY(self->playback_speed_input), 4); // Max of 4 characters (e.g. "0.25" but allow for "2" or "2.0") + gtk_editable_set_max_width_chars(GTK_EDITABLE(self->playback_speed_input), 4); // Set to be as wide as 4 characters + gtk_widget_set_valign(self->playback_speed_input, GTK_ALIGN_CENTER); // Only align center, don't vexpand + + gtk_center_box_set_start_widget(GTK_CENTER_BOX(self->advanced_controls_section), GTK_WIDGET(self->backwards_jump_button)); + gtk_center_box_set_center_widget(GTK_CENTER_BOX(self->advanced_controls_section), GTK_WIDGET(self->playback_speed_input)); + gtk_center_box_set_end_widget(GTK_CENTER_BOX(self->advanced_controls_section), GTK_WIDGET(self->forwards_jump_button)); + + gtk_box_append(GTK_BOX(self->primary_controls_section), self->advanced_controls_section); + + gtk_entry_set_input_hints( + GTK_ENTRY(self->playback_speed_input), + GTK_INPUT_HINT_NONE | GTK_INPUT_HINT_NO_SPELLCHECK | GTK_INPUT_HINT_NO_EMOJI // No fanciness please + ); + + gtk_entry_set_input_purpose( + GTK_ENTRY(self->playback_speed_input), + GTK_INPUT_PURPOSE_NUMBER // Numbers allow . so use that + ); + + gtk_entry_set_placeholder_text(GTK_ENTRY(self->playback_speed_input), "1.0"); + + g_signal_connect(self->playback_speed_input, "activate", G_CALLBACK(koto_playerbar_handle_speed_input_activate), self); // Handle our activate (enter key on entry) } void koto_playerbar_create_secondary_controls(KotoPlayerBar * self) { @@ -313,6 +353,29 @@ void koto_playerbar_go_forwards( koto_playback_engine_forwards(playback_engine); } +void koto_playerbar_handle_speed_input_activate( + GtkEntry * playback_speed_input, + gpointer user_data +) { + KotoPlayerBar * self = user_data; + + if (!KOTO_IS_PLAYERBAR(self)) { + return; + } + + GtkEntryBuffer * entry_buffer = gtk_entry_get_buffer(playback_speed_input); // Get the entry buffer + const gchar * text = gtk_entry_buffer_get_text(entry_buffer); // Get the text for the buffer + + gdouble rate = g_strtod(text, NULL); // Attempt to convert + gdouble fixed_rate = koto_playback_engine_get_sane_playback_rate(rate); + + gchar * conv = g_strdup_printf("%f", fixed_rate); + gtk_entry_buffer_set_text(entry_buffer, g_utf8_substring(conv, 0, 4), -1); // Set our entry value to the converted string of the new rate, or if it is something like 2 then it sets it to 2.0 + g_free(conv); + + koto_playback_engine_set_playback_rate(playback_engine, fixed_rate); +} + void koto_playerbar_handle_is_playing( KotoPlaybackEngine * engine, gpointer user_data @@ -538,6 +601,38 @@ void koto_playerbar_handle_volume_button_change( koto_playback_engine_set_volume(playback_engine, (double) value / 100); } +void koto_playerbar_jump_backwards( + GtkGestureClick * gesture, + int n_press, + double x, + double y, + gpointer data +) { + (void) gesture; + (void) n_press; + (void) x; + (void) y; + (void) data; + + koto_playback_engine_jump_backwards(playback_engine); +} + +void koto_playerbar_jump_forwards( + GtkGestureClick * gesture, + int n_press, + double x, + double y, + gpointer data +) { + (void) gesture; + (void) n_press; + (void) x; + (void) y; + (void) data; + + koto_playback_engine_jump_forwards(playback_engine); +} + void koto_playerbar_reset_progressbar(KotoPlayerBar * self) { if (!KOTO_IS_PLAYERBAR(self)) { return; diff --git a/src/koto-playerbar.h b/src/koto-playerbar.h index a3b3d2e..8abaeb7 100644 --- a/src/koto-playerbar.h +++ b/src/koto-playerbar.h @@ -105,6 +105,11 @@ void koto_playerbar_handle_progressbar_value_changed( gpointer data ); +void koto_playerbar_handle_speed_input_activate( + GtkEntry * playback_speed_input, + gpointer user_data +); + void koto_playerbar_handle_tick_duration( KotoPlaybackEngine * engine, gpointer user_data @@ -131,6 +136,22 @@ void koto_playerbar_handle_volume_button_change( gpointer user_data ); +void koto_playerbar_jump_backwards( + GtkGestureClick * gesture, + int n_press, + double x, + double y, + gpointer data +); + +void koto_playerbar_jump_forwards( + GtkGestureClick * gesture, + int n_press, + double x, + double y, + gpointer data +); + void koto_playerbar_reset_progressbar(KotoPlayerBar * self); void koto_playerbar_set_progressbar_duration( diff --git a/src/koto.gresource.xml b/src/koto.gresource.xml index b09893c..daa9ffe 100644 --- a/src/koto.gresource.xml +++ b/src/koto.gresource.xml @@ -3,6 +3,8 @@ ../data/genres/business-and-personal-finance.png ../data/genres/foreign-languages.png + ../data/vectors/multimedia-backwards-jump.svg + ../data/vectors/multimedia-forwards-jump.svg ../data/genres/mystery-and-thriller.png ../data/genres/sci-fi.png ../data/genres/travel.png diff --git a/src/playback/engine.c b/src/playback/engine.c index 9033e0d..510f2bf 100644 --- a/src/playback/engine.c +++ b/src/playback/engine.c @@ -81,6 +81,7 @@ struct _KotoPlaybackEngine { guint playback_position; gint requested_playback_position; + gdouble rate; gdouble volume; }; @@ -210,6 +211,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->rate = 1.0; self->volume = 0.5; koto_playback_engine_set_volume(self, 0.5); @@ -408,6 +410,18 @@ gdouble koto_playback_engine_get_progress(KotoPlaybackEngine * self) { return progress; } +gdouble koto_playback_engine_get_sane_playback_rate(gdouble rate) { + if ((rate < 0.25) && (rate != 0)) { // 0.25 is probably the most reasonable limit + return 0.25; + } else if (rate > 2.5) { // Anything greater than 2.5 + return 2.5; + } else if (rate == 0) { // Possible conversion failure + return 1.0; + } else { + return rate; + } +} + GstState koto_playback_engine_get_state(KotoPlaybackEngine * self) { return GST_STATE(self->player); } @@ -424,6 +438,55 @@ gdouble koto_playback_engine_get_volume(KotoPlaybackEngine * self) { return KOTO_IS_PLAYBACK_ENGINE(self) ? self->volume : 0; } +void koto_playback_engine_jump_backwards(KotoPlaybackEngine * self) { + if (!KOTO_IS_PLAYBACK_ENGINE(self)) { // Not an engine + return; + } + + if (!self->is_playing) { // Is not playing + return; + } + + gdouble cur_pos = koto_playback_engine_get_progress(self); // Get the current position + guint backwards_increment = 10; + g_object_get( + config, + "playback-jump-backwards-increment", // Get our backwards increment from our settings + &backwards_increment, + NULL + ); + + gint new_pos = (gint) cur_pos - backwards_increment; + + if (new_pos < 0) { // Before the beginning of time + new_pos = 0; + } + + koto_playback_engine_set_position(self, new_pos); +} + +void koto_playback_engine_jump_forwards(KotoPlaybackEngine * self) { + if (!KOTO_IS_PLAYBACK_ENGINE(self)) { // Not an engine + return; + } + + if (!self->is_playing) { // Is not playing + return; + } + + gdouble cur_pos = koto_playback_engine_get_progress(self); // Get the current position + guint forwards_increment = 30; + g_object_get( + config, + "playback-jump-forwards-increment", // Get our forward increment from our settings + &forwards_increment, + NULL + ); + + gint new_pos = (gint) cur_pos + forwards_increment; + koto_playback_engine_set_position(self, new_pos); +} + gboolean koto_playback_engine_monitor_changed( GstBus * bus, GstMessage * msg, @@ -516,6 +579,20 @@ void koto_playback_engine_pause(KotoPlaybackEngine * self) { koto_update_mpris_playback_state(GST_STATE_PAUSED); } +void koto_playback_engine_set_playback_rate( + KotoPlaybackEngine * self, + gdouble rate +) { + if (!KOTO_IS_PLAYBACK_ENGINE(self)) { // Not a playback engine + return; + } + + self->rate = koto_playback_engine_get_sane_playback_rate(rate); // Get a fixed rate and set our current rate to it + gst_element_set_state(self->player, GST_STATE_PAUSED); // Set our state to paused + koto_playback_engine_set_position(self, koto_playback_engine_get_progress(self)); // Basically creates a new seek event + gst_element_set_state(self->player, GST_STATE_PLAYING); // Set our state to play +} + void koto_playback_engine_set_position( KotoPlaybackEngine * self, int position @@ -526,7 +603,7 @@ void koto_playback_engine_set_position( gst_element_seek( self->playbin, - 1.0, + self->rate, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, GST_SEEK_TYPE_SET, diff --git a/src/playback/engine.h b/src/playback/engine.h index ecf9d45..8d10a8d 100644 --- a/src/playback/engine.h +++ b/src/playback/engine.h @@ -66,16 +66,24 @@ KotoTrack * koto_playback_engine_get_current_track(KotoPlaybackEngine * self); gint64 koto_playback_engine_get_duration(KotoPlaybackEngine * self); -GstState koto_playback_engine_get_state(KotoPlaybackEngine * self); - gdouble koto_playback_engine_get_progress(KotoPlaybackEngine * self); +; + +gdouble koto_playback_engine_get_sane_playback_rate(gdouble rate); + +GstState koto_playback_engine_get_state(KotoPlaybackEngine * self); + gboolean koto_playback_engine_get_track_repeat(KotoPlaybackEngine * self); gboolean koto_playback_engine_get_track_shuffle(KotoPlaybackEngine * self); gdouble koto_playback_engine_get_volume(KotoPlaybackEngine * self); +void koto_playback_engine_jump_backwards(KotoPlaybackEngine * self); + +void koto_playback_engine_jump_forwards(KotoPlaybackEngine * self); + void koto_playback_engine_mute(KotoPlaybackEngine * self); gboolean koto_playback_engine_monitor_changed( @@ -90,6 +98,11 @@ void koto_playback_engine_play(KotoPlaybackEngine * self); void koto_playback_engine_toggle(KotoPlaybackEngine * self); +void koto_playback_engine_set_playback_rate( + KotoPlaybackEngine * self, + gdouble rate +); + void koto_playback_engine_set_position( KotoPlaybackEngine * self, int position diff --git a/theme/_button.scss b/theme/_button.scss index ab470c1..3622206 100644 --- a/theme/_button.scss +++ b/theme/_button.scss @@ -3,8 +3,8 @@ .koto-button { border-width: 0; - & > image { - margin-right: 10px; + & > .button-label { + margin-left: 10px; } &:not(.active) { diff --git a/theme/_main.scss b/theme/_main.scss index a02659b..ea45551 100644 --- a/theme/_main.scss +++ b/theme/_main.scss @@ -47,6 +47,6 @@ window { .artist-view-content, // Has the albums .playlist-page, // Individual playlists .writer-page { // Writer page in Audiobook - padding: $itempadding; + padding: $padding; } } diff --git a/theme/_player-bar.scss b/theme/_player-bar.scss index 9c4f1ba..17580e6 100644 --- a/theme/_player-bar.scss +++ b/theme/_player-bar.scss @@ -15,6 +15,23 @@ } } + .playerbar-primary-controls, // Primary + .playerbar-secondary-controls { // Secondary + & > .koto-button { // Direct descendents + margin: 0 $quarterpadding; + } + } + + .playerbar-primary-controls { // Primary Controls + .playerbar-advanced-controls { // Advanced controls + margin-left: $padding; + + & > entry { // Inner GtkEntry + margin: 0 $halvedpadding; + } + } + } + .playerbar-info { // Central info section & > box { // Info labels margin-left: 2ex; diff --git a/theme/_vars.scss b/theme/_vars.scss index 2bf2ba5..348f9e9 100644 --- a/theme/_vars.scss +++ b/theme/_vars.scss @@ -4,6 +4,6 @@ $green: #60E078; $palewhite: #cccccc; $red : #FF4652; -$itempadding: 40px; -$halvedpadding: $itempadding / 2; -$quarterpadding: $itempadding / 4; \ No newline at end of file +$padding: 40px; +$halvedpadding: $padding / 2; +$quarterpadding: $padding / 4; \ No newline at end of file diff --git a/theme/components/_writer-page.scss b/theme/components/_writer-page.scss index c37f3cf..442201e 100644 --- a/theme/components/_writer-page.scss +++ b/theme/components/_writer-page.scss @@ -7,6 +7,6 @@ color: $text-color-faded; font-size: 4em; font-weight: bold; - padding-bottom: $itempadding; + padding-bottom: $padding; } } \ No newline at end of file diff --git a/theme/pages/_audiobook-library.scss b/theme/pages/_audiobook-library.scss index 6de0139..987d2be 100644 --- a/theme/pages/_audiobook-library.scss +++ b/theme/pages/_audiobook-library.scss @@ -5,7 +5,7 @@ .audiobook-library { // Library page .genres-banner { // Banner for genres list .large-banner { // Large banner with art for each genre - padding: $itempadding; + padding: $padding; .audiobook-genre-button { // Genre buttons .koto-button { @@ -17,7 +17,7 @@ } .writers-button-flow { // Flowbox of buttons for writers - padding: 0 $itempadding; // Horizontal padding of our standard item padding + padding: 0 $padding; // Horizontal padding of our standard item padding flowboxchild { padding: 0; diff --git a/theme/pages/_music-local.scss b/theme/pages/_music-local.scss index de5cda9..337d5d6 100644 --- a/theme/pages/_music-local.scss +++ b/theme/pages/_music-local.scss @@ -19,7 +19,7 @@ & > .album-list { & > flowboxchild > .album-view { & > overlay { - margin-right: $itempadding; + margin-right: $padding; } } }