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;
}
}
}