messenger-gtk

Gtk+3 graphical user interfaces for GNUnet Messenger
Log | Files | Refs | Submodules | README | LICENSE

commit 90aeae98ca55fffcfbd598255d62fd258d287e1d
parent 056af2fb44379238609a2a28ede614ee3c5defed
Author: TheJackiMonster <thejackimonster@gmail.com>
Date:   Sun,  4 Dec 2022 18:40:40 +0100

Implement video playback from local file

Signed-off-by: TheJackiMonster <thejackimonster@gmail.com>

Diffstat:
Mresources/ui/play_media.ui | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/ui/new_contact.c | 9++++++++-
Msrc/ui/new_contact.h | 2--
Msrc/ui/play_media.c | 354+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/ui/play_media.h | 18++++++++++++++++++
5 files changed, 433 insertions(+), 14 deletions(-)

diff --git a/resources/ui/play_media.ui b/resources/ui/play_media.ui @@ -25,7 +25,7 @@ Author: Tobias Frisch <requires lib="libhandy" version="1.2"/> <object class="HdyWindow" id="play_media_window"> <property name="width-request">250</property> - <property name="height-request">180</property> + <property name="height-request">250</property> <property name="can-focus">False</property> <property name="modal">True</property> <property name="window-position">center-on-parent</property> @@ -82,11 +82,64 @@ Author: Tobias Frisch <property name="flap-position">end</property> <property name="modal">False</property> <child type="content"> - <object class="GtkImage"> + <object class="GtkStack" id="preview_stack"> <property name="visible">True</property> <property name="can-focus">False</property> - <property name="icon-name">folder-videos-symbolic</property> - <property name="icon_size">6</property> + <child> + <object class="GtkBox" id="video_box"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="name">page0</property> + <property name="title" translatable="yes">page0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="fail_box"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">center</property> + <property name="valign">center</property> + <property name="orientation">vertical</property> + <property name="spacing">8</property> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="pixel-size">64</property> + <property name="icon-name">action-unavailable-symbolic</property> + <property name="icon_size">3</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">Video source invalid!</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="name">page1</property> + <property name="title" translatable="yes">page1</property> + <property name="position">1</property> + </packing> + </child> </object> </child> <child type="separator"> @@ -109,6 +162,7 @@ Author: Tobias Frisch <child> <object class="GtkButton" id="play_pause_button"> <property name="visible">True</property> + <property name="sensitive">False</property> <property name="can-focus">True</property> <property name="receives-default">True</property> <property name="relief">none</property> @@ -119,6 +173,7 @@ Author: Tobias Frisch <child> <object class="GtkImage"> <property name="visible">True</property> + <property name="sensitive">False</property> <property name="can-focus">False</property> <property name="icon-name">media-playback-start-symbolic</property> </object> @@ -149,6 +204,7 @@ Author: Tobias Frisch <child> <object class="GtkVolumeButton" id="volume_button"> <property name="visible">True</property> + <property name="sensitive">False</property> <property name="can-focus">True</property> <property name="focus-on-click">False</property> <property name="receives-default">True</property> diff --git a/src/ui/new_contact.c b/src/ui/new_contact.c @@ -76,7 +76,10 @@ static void _disable_video_processing(UI_NEW_CONTACT_Handle *handle, gboolean drop_pipeline) { - gtk_stack_set_visible_child(handle->preview_stack, handle->fail_box); + GNUNET_assert(handle); + + if (handle->preview_stack) + gtk_stack_set_visible_child(handle->preview_stack, handle->fail_box); if ((!(handle->pipeline)) || (!drop_pipeline)) return; @@ -252,6 +255,8 @@ void ui_new_contact_dialog_init(MESSENGER_Application *app, UI_NEW_CONTACT_Handle *handle) { + GNUNET_assert((app) && (handle)); + _setup_gst_pipeline(handle); pthread_create( @@ -345,6 +350,8 @@ ui_new_contact_dialog_init(MESSENGER_Application *app, void ui_new_contact_dialog_cleanup(UI_NEW_CONTACT_Handle *handle) { + GNUNET_assert(handle); + pthread_join(handle->video_tid, NULL); g_object_unref(handle->builder); diff --git a/src/ui/new_contact.h b/src/ui/new_contact.h @@ -39,8 +39,6 @@ typedef struct UI_NEW_CONTACT_Handle GstElement *scanner; GstElement *sink; - guint bus_watch; - GtkBuilder *builder; GtkDialog *dialog; diff --git a/src/ui/play_media.c b/src/ui/play_media.c @@ -27,6 +27,8 @@ #include "../application.h" #include "../ui.h" +#include <gst/gst.h> + static void handle_back_button_click(GtkButton *button, gpointer user_data) @@ -36,19 +38,131 @@ handle_back_button_click(GtkButton *button, } static void +_disable_video_processing(UI_PLAY_MEDIA_Handle *handle, + gboolean drop_pipeline) +{ + GNUNET_assert(handle); + + if (handle->preview_stack) + gtk_stack_set_visible_child(handle->preview_stack, handle->fail_box); + + if ((!(handle->pipeline)) || (!drop_pipeline)) + return; + + gst_element_set_state(handle->pipeline, GST_STATE_NULL); +} + +static gboolean +_adjust_playing_media_position(UI_PLAY_MEDIA_Handle *handle) +{ + gint64 pos, len; + + if (!(handle->pipeline)) + return FALSE; + + if (!gst_element_query_position(handle->pipeline, GST_FORMAT_TIME, &pos)) + return FALSE; + + if (!gst_element_query_duration(handle->pipeline, GST_FORMAT_TIME, &len)) + return FALSE; + + if (handle->timeline_label) + { + GString *str = g_string_new(NULL); + + guint pos_seconds = GST_TIME_AS_SECONDS(pos); + guint len_seconds = GST_TIME_AS_SECONDS(len); + + g_string_append_printf( + str, + "%u:%02u / %u:%02u", + pos_seconds / 60, + pos_seconds % 60, + len_seconds / 60, + len_seconds % 60 + ); + + ui_label_set_text(handle->timeline_label, str->str); + g_string_free(str, TRUE); + } + + if (handle->timeline_progress_bar) + gtk_progress_bar_set_fraction( + handle->timeline_progress_bar, + 1.0 * pos / len + ); + + return TRUE; +} + +static void +_adjust_playing_media_state(UI_PLAY_MEDIA_Handle *handle, + gboolean playing) +{ + handle->playing = playing; + + if (!(handle->play_symbol_stack)) + return; + + gtk_stack_set_visible_child_name( + handle->play_symbol_stack, + handle->playing? "pause_page" : "play_page" + ); + + if (handle->timeline) + g_source_remove(handle->timeline); + + if (handle->playing) + handle->timeline = g_timeout_add( + 1000, + G_SOURCE_FUNC(_adjust_playing_media_position), + handle + ); + else + handle->timeline = 0; + + gtk_widget_set_sensitive(GTK_WIDGET(handle->play_pause_button), TRUE); + + gtk_stack_set_visible_child( + handle->preview_stack, + handle->video_box + ); +} + +static void _pause_playing_media(UI_PLAY_MEDIA_Handle *handle) { - // + if (!(handle->pipeline)) + _adjust_playing_media_state(handle, FALSE); - handle->playing = FALSE; + GstStateChangeReturn ret = gst_element_set_state( + handle->pipeline, + GST_STATE_PAUSED + ); + + if (GST_STATE_CHANGE_FAILURE == ret) + { + _disable_video_processing(handle, TRUE); + return; + } } static void _continue_playing_media(UI_PLAY_MEDIA_Handle *handle) { - // + if (!(handle->pipeline)) + _adjust_playing_media_state(handle, TRUE); - handle->playing = TRUE; + GstStateChangeReturn ret = gst_element_set_state( + handle->pipeline, + GST_STATE_PLAYING + ); + + if (GST_STATE_CHANGE_FAILURE == ret) + { + _disable_video_processing(handle, TRUE); + return; + } } static void @@ -57,14 +171,36 @@ handle_play_pause_button_click(GtkButton *button, { UI_PLAY_MEDIA_Handle *handle = (UI_PLAY_MEDIA_Handle*) user_data; + gtk_widget_set_sensitive(GTK_WIDGET(button), FALSE); + if (handle->playing) _pause_playing_media(handle); else _continue_playing_media(handle); +} - gtk_stack_set_visible_child_name( - handle->play_symbol_stack, - handle->playing? "pause_page" : "play_page" +static void +handle_volume_button_value_changed(GtkScaleButton *button, + double value, + gpointer user_data) +{ + UI_PLAY_MEDIA_Handle *handle = (UI_PLAY_MEDIA_Handle*) user_data; + + if (!(handle->vol)) + return; + + g_object_set( + G_OBJECT(handle->vol), + "volume", + value, + NULL + ); + + g_object_set( + G_OBJECT(handle->vol), + "mute", + (value <= 0.0), + NULL ); } @@ -157,12 +293,155 @@ handle_window_destroy(UNUSED GtkWidget *window, ui_play_media_window_cleanup((UI_PLAY_MEDIA_Handle*) user_data); } +static void +msg_error_cb(UNUSED GstBus *bus, + GstMessage *msg, + gpointer *data) +{ + UI_PLAY_MEDIA_Handle *handle = (UI_PLAY_MEDIA_Handle*) data; + + GError* error; + gst_message_parse_error(msg, &error, NULL); + + if (!error) + fprintf(stderr, "ERROR: Unknown error\n"); + else if (error->message) + fprintf(stderr, "ERROR: %s (%d)\n", error->message, error->code); + else + fprintf(stderr, "ERROR: Unknown error (%d)\n", error->code); + + _disable_video_processing(handle, TRUE); +} + +static void +msg_eos_cb(UNUSED GstBus *bus, + UNUSED GstMessage *msg, + gpointer *data) +{ + UI_PLAY_MEDIA_Handle *handle = (UI_PLAY_MEDIA_Handle*) data; + + if (GST_MESSAGE_SRC(msg) == GST_OBJECT(handle->pipeline)) + _adjust_playing_media_state(handle, FALSE); +} + +static void +msg_state_changed_cb(UNUSED GstBus *bus, + GstMessage *msg, + gpointer *data) +{ + UI_PLAY_MEDIA_Handle *handle = (UI_PLAY_MEDIA_Handle*) data; + + GstState old_state, new_state, pending_state; + gst_message_parse_state_changed(msg, &old_state, &new_state, &pending_state); + + if ((GST_MESSAGE_SRC(msg) != GST_OBJECT(handle->pipeline)) || + (new_state == old_state) || (!(handle->preview_stack))) + return; + + if (!(handle->sink)) + _disable_video_processing(handle, FALSE); + else if (GST_STATE_PLAYING == new_state) + _adjust_playing_media_state(handle, TRUE); + else if (GST_STATE_PAUSED == new_state) + _adjust_playing_media_state(handle, FALSE); +} + +static void +_setup_gst_pipeline(UI_PLAY_MEDIA_Handle *handle) +{ + handle->pipeline = gst_parse_launch( + "filesrc name=source" + " ! decodebin name=decode" + " ! videoconvert ! video/x-raw,format=RGB" + " ! videoconvert ! gtksink name=vsink decode." + " ! audioconvert ! audioresample" + " ! volume name=vol ! autoaudiosink", + NULL + ); + + if (!(handle->pipeline)) + return; + + handle->source = gst_bin_get_by_name( + GST_BIN(handle->pipeline), "source" + ); + + g_object_set( + G_OBJECT(handle->source), + "location", + "", // absolute path to video file + NULL + ); + + handle->decode = gst_bin_get_by_name( + GST_BIN(handle->pipeline), "decode" + ); + + handle->sink = gst_bin_get_by_name( + GST_BIN(handle->pipeline), "vsink" + ); + + handle->vol = gst_bin_get_by_name( + GST_BIN(handle->pipeline), "vol" + ); + + GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(handle->pipeline)); + + if (!bus) + return; + + gst_bus_add_signal_watch(bus); + + g_signal_connect( + G_OBJECT(bus), + "message::error", + (GCallback) msg_error_cb, + handle + ); + + g_signal_connect( + G_OBJECT(bus), + "message::eos", + (GCallback) msg_eos_cb, + handle + ); + + g_signal_connect( + G_OBJECT(bus), + "message::state-changed", + (GCallback) msg_state_changed_cb, + handle + ); + + gst_object_unref(bus); +} + +static void* +_ui_play_media_video_thread(void *args) +{ + UI_PLAY_MEDIA_Handle *handle = (UI_PLAY_MEDIA_Handle*) args; + + if (!handle->playing) + _continue_playing_media(handle); + + return NULL; +} + void ui_play_media_window_init(MESSENGER_Application *app, UI_PLAY_MEDIA_Handle *handle) { GNUNET_assert((app) && (handle)); + _setup_gst_pipeline(handle); + + pthread_create( + &(handle->video_tid), + NULL, + _ui_play_media_video_thread, + handle + ); + handle->parent = GTK_WINDOW(app->ui.messenger.main_window); handle->builder = gtk_builder_new_from_resource( @@ -208,6 +487,40 @@ ui_play_media_window_init(MESSENGER_Application *app, gtk_builder_get_object(handle->builder, "controls_flap") ); + handle->preview_stack = GTK_STACK( + gtk_builder_get_object(handle->builder, "preview_stack") + ); + + handle->fail_box = GTK_WIDGET( + gtk_builder_get_object(handle->builder, "fail_box") + ); + + handle->video_box = GTK_WIDGET( + gtk_builder_get_object(handle->builder, "video_box") + ); + + GtkWidget *widget; + if (handle->sink) + g_object_get(handle->sink, "widget", &widget, NULL); + else + widget = NULL; + + if (widget) + { + gtk_box_pack_start( + GTK_BOX(handle->video_box), + widget, + true, + true, + 0 + ); + + g_object_unref(widget); + gtk_widget_realize(widget); + + gtk_widget_show_all(handle->video_box); + } + handle->play_pause_button = GTK_BUTTON( gtk_builder_get_object(handle->builder, "play_pause_button") ); @@ -227,10 +540,21 @@ ui_play_media_window_init(MESSENGER_Application *app, gtk_builder_get_object(handle->builder, "volume_button") ); + g_signal_connect( + handle->volume_button, + "value-changed", + G_CALLBACK(handle_volume_button_value_changed), + handle + ); + handle->timeline_label = GTK_LABEL( gtk_builder_get_object(handle->builder, "timeline_label") ); + handle->timeline_progress_bar = GTK_PROGRESS_BAR( + gtk_builder_get_object(handle->builder, "timeline_progress_bar") + ); + handle->settings_button = GTK_BUTTON( gtk_builder_get_object(handle->builder, "settings_button") ); @@ -270,6 +594,11 @@ ui_play_media_window_init(MESSENGER_Application *app, handle ); + gtk_scale_button_set_value( + GTK_SCALE_BUTTON(handle->volume_button), + 1.0 + ); + gtk_widget_show_all(GTK_WIDGET(handle->window)); } @@ -278,10 +607,21 @@ ui_play_media_window_cleanup(UI_PLAY_MEDIA_Handle *handle) { GNUNET_assert(handle); + pthread_join(handle->video_tid, NULL); + g_object_unref(handle->builder); + if (handle->timeline) + g_source_remove(handle->timeline); + if (handle->motion_lost) g_source_remove(handle->motion_lost); + if (handle->pipeline) + { + gst_element_set_state(handle->pipeline, GST_STATE_NULL); + gst_object_unref(GST_OBJECT(handle->pipeline)); + } + memset(handle, 0, sizeof(*handle)); } diff --git a/src/ui/play_media.h b/src/ui/play_media.h @@ -27,11 +27,20 @@ #include "messenger.h" +#include <gstreamer-1.0/gst/gst.h> +#include <pthread.h> + typedef struct UI_PLAY_MEDIA_Handle { gboolean playing; gboolean fullscreen; + GstElement *pipeline; + GstElement *source; + GstElement *decode; + GstElement *sink; + GstElement *vol; + GtkWindow *parent; GtkBuilder *builder; @@ -42,18 +51,27 @@ typedef struct UI_PLAY_MEDIA_Handle GtkButton *back_button; HdyFlap *controls_flap; + + GtkStack *preview_stack; + GtkWidget *fail_box; + GtkWidget *video_box; + GtkButton *play_pause_button; GtkStack *play_symbol_stack; GtkVolumeButton *volume_button; GtkLabel *timeline_label; + GtkProgressBar *timeline_progress_bar; GtkButton *settings_button; GtkButton *fullscreen_button; GtkStack *fullscreen_symbol_stack; + guint timeline; guint motion_lost; + + pthread_t video_tid; } UI_PLAY_MEDIA_Handle; /**