diff --git a/docs/src/libs/video_support/gstreamer.rst b/docs/src/libs/video_support/gstreamer.rst index 3f89f33484..f0a465eab3 100644 --- a/docs/src/libs/video_support/gstreamer.rst +++ b/docs/src/libs/video_support/gstreamer.rst @@ -168,6 +168,59 @@ Here's how to create a basic GStreamer player and load media: /* Start playback */ lv_gstreamer_play(streamer); + +Events +------ + +- :cpp:enumerator:`LV_EVENT_STATE_CHANGED` Sent when the stream state changes. The stream state can be retrieved via :cpp:expr:`lv_gstreamer_get_stream_state(e)`. + +Event Handling +-------------- + +Handle GStreamer events using LVGL's event system: + +.. code-block:: c + + static void gstreamer_event_cb(lv_event_t * e) + { + + lv_event_code_t code = lv_event_get_code(e); + lv_obj_t * streamer = lv_event_get_target_obj(e); + + if(code != LV_EVENT_STATE_CHANGED) { + return; + } + + lv_gstreamer_stream_state_t stream_state = lv_gstreamer_get_stream_state(e); + switch(stream_state) { + case LV_GSTREAMER_STREAM_STATE_START: + LV_LOG_USER("Stream ready - Duration: %" LV_PRIu32 " ms", + lv_gstreamer_get_duration(streamer)); + LV_LOG_USER("\tStream resolution %" LV_PRId32 "x%" LV_PRId32, lv_image_get_src_width(streamer), + lv_image_get_src_height(streamer)); + break; + case LV_GSTREAMER_STREAM_STATE_END: + LV_LOG_USER("Stream is over"); + break; + case LV_GSTREAMER_STREAM_STATE_PLAY: + LV_LOG_USER("Stream set to play"); + break; + case LV_GSTREAMER_STREAM_STATE_PAUSE: + LV_LOG_USER("Stream set to pause"); + break; + case LV_GSTREAMER_STREAM_STATE_STOP: + LV_LOG_USER("Stream set to stop"); + break; + } + } + + /* Add event callback */ + lv_obj_add_event_cb(streamer, gstreamer_event_cb, LV_EVENT_STATE_CHANGED, NULL); + + + + + Media Source Configuration -------------------------- @@ -242,29 +295,6 @@ Manage audio volume with built-in controls: /* Get current volume */ uint8_t volume = lv_gstreamer_get_volume(streamer); -Event Handling --------------- - -Handle GStreamer events using LVGL's event system: - -.. code-block:: c - - static void gstreamer_event_cb(lv_event_t * e) - { - lv_event_code_t code = lv_event_get_code(e); - lv_obj_t * streamer = lv_event_get_target_obj(e); - - if(code == LV_EVENT_READY) { - LV_LOG_USER("Stream ready - Duration: %" LV_PRIu32 " ms", - lv_gstreamer_get_duration(streamer)); - LV_LOG_USER("Resolution: %" LV_PRId32 "x%" LV_PRId32, - lv_image_get_src_width(streamer), - lv_image_get_src_height(streamer)); - } - } - - /* Add event callback */ - lv_obj_add_event_cb(streamer, gstreamer_event_cb, LV_EVENT_ALL, NULL); Widget Architecture ******************* diff --git a/examples/libs/gstreamer/lv_example_gstreamer_1.c b/examples/libs/gstreamer/lv_example_gstreamer_1.c index cf7c5df68c..1304bc3b98 100644 --- a/examples/libs/gstreamer/lv_example_gstreamer_1.c +++ b/examples/libs/gstreamer/lv_example_gstreamer_1.c @@ -18,7 +18,7 @@ static void update_duration_label(lv_obj_t * label, uint32_t duration); static void volume_observer_cb(lv_observer_t * observer, lv_subject_t * subject); static void update_position_slider(lv_timer_t * timer); static void play_pause_pressed(lv_event_t * e); -static void streamer_ready(lv_event_t * e); +static void stream_state_changed(lv_event_t * e); /** * Loads a video from the internet using the gstreamer widget @@ -46,13 +46,6 @@ void lv_example_gstreamer_1(void) lv_obj_center(event_data.streamer); - /* The LV_EVENT_READY will fire when the stream is ready at that point you can query the stream - * information like its resolution and duration. See `streamer_ready` */ - lv_obj_add_event_cb(event_data.streamer, streamer_ready, LV_EVENT_READY, &event_data); - - /* Play the stream immediately */ - lv_gstreamer_play(event_data.streamer); - /* Create a slider to modify the stream volume and a label to visualize the current value */ volume_setter_create(&event_data); @@ -61,6 +54,13 @@ void lv_example_gstreamer_1(void) * Also add a pause/play button*/ control_bar_create(&event_data); + /* The LV_EVENT_STATE_CHANGED will fire when the stream is ready at that point we can query the stream + * information like its resolution and duration. See `streamer_ready` */ + lv_obj_add_event_cb(event_data.streamer, stream_state_changed, LV_EVENT_STATE_CHANGED, &event_data); + + /* Play the stream immediately */ + lv_gstreamer_play(event_data.streamer); + /* Create a timer that will update the slider position based on the stream position * Make it 3 times faster than the refresh rate for a smoother effect */ lv_timer_create(update_position_slider, LV_DEF_REFR_PERIOD, &event_data); @@ -173,30 +173,50 @@ static void play_pause_pressed(lv_event_t * e) { event_data_t * event_data = (event_data_t *)lv_event_get_user_data(e); - if(lv_streq(lv_label_get_text(event_data->button_label), LV_SYMBOL_PLAY)) { - lv_label_set_text(event_data->button_label, LV_SYMBOL_PAUSE); + if(lv_streq(lv_label_get_text(event_data->button_label), LV_SYMBOL_REFRESH)) { + lv_gstreamer_set_position(event_data->streamer, 0); + lv_gstreamer_play(event_data->streamer); + } + else if(lv_streq(lv_label_get_text(event_data->button_label), LV_SYMBOL_PLAY)) { lv_gstreamer_play(event_data->streamer); } else { - lv_label_set_text(event_data->button_label, LV_SYMBOL_PLAY); lv_gstreamer_pause(event_data->streamer); } } -static void streamer_ready(lv_event_t * e) +static void stream_state_changed(lv_event_t * e) { lv_event_code_t code = lv_event_get_code(e); event_data_t * event_data = (event_data_t *)lv_event_get_user_data(e); - lv_obj_t * btn = event_data->pp_button; + lv_obj_t * streamer = event_data->streamer; - if(code == LV_EVENT_READY) { - lv_obj_align(btn, LV_ALIGN_BOTTOM_MID, 0, 0); - uint32_t duration = lv_gstreamer_get_duration(streamer); - LV_LOG_USER("Video is starting"); - LV_LOG_USER("\tStream resolution %" LV_PRId32 "x%" LV_PRId32, lv_image_get_src_width(streamer), - lv_image_get_src_height(streamer)); - LV_LOG_USER("\tStream duration %" LV_PRIu32, duration); - update_duration_label(event_data->duration_label, duration); + if(code != LV_EVENT_STATE_CHANGED) { + return; + } + + lv_gstreamer_stream_state_t stream_state = lv_gstreamer_get_stream_state(e); + switch(stream_state) { + case LV_GSTREAMER_STREAM_STATE_START: { + uint32_t duration = lv_gstreamer_get_duration(streamer); + LV_LOG_USER("Stream is starting"); + LV_LOG_USER("\tStream resolution %" LV_PRId32 "x%" LV_PRId32, lv_image_get_src_width(streamer), + lv_image_get_src_height(streamer)); + LV_LOG_USER("\tStream duration %" LV_PRIu32, duration); + update_duration_label(event_data->duration_label, duration); + break; + } + case LV_GSTREAMER_STREAM_STATE_END: + LV_LOG_USER("Stream is over"); + lv_label_set_text_static(event_data->button_label, LV_SYMBOL_REFRESH); + break; + case LV_GSTREAMER_STREAM_STATE_PLAY: + lv_label_set_text_static(event_data->button_label, LV_SYMBOL_PAUSE); + break; + case LV_GSTREAMER_STREAM_STATE_PAUSE: + case LV_GSTREAMER_STREAM_STATE_STOP: + lv_label_set_text_static(event_data->button_label, LV_SYMBOL_PLAY); + break; } } diff --git a/src/libs/gstreamer/lv_gstreamer.c b/src/libs/gstreamer/lv_gstreamer.c index 3ef97fec61..cc4249ba1b 100644 --- a/src/libs/gstreamer/lv_gstreamer.c +++ b/src/libs/gstreamer/lv_gstreamer.c @@ -14,6 +14,7 @@ #include #include #include "../../core/lv_obj_class_private.h" +#include "../../misc/lv_event_private.h" /********************* * DEFINES @@ -41,10 +42,11 @@ static void lv_gstreamer_destructor(const lv_obj_class_t * class_p, lv_obj_t * o static void on_decode_pad_added(GstElement * element, GstPad * pad, gpointer user_data); static GstFlowReturn on_new_sample(GstElement * sink, gpointer user_data); static void gstreamer_timer_cb(lv_timer_t * timer); -static void gstreamer_poll_bus(lv_gstreamer_t * streamer); +static lv_result_t gstreamer_poll_bus(lv_gstreamer_t * streamer); static void gstreamer_update_frame(lv_gstreamer_t * streamer); static lv_result_t gstreamer_make_and_add_to_pipeline(lv_gstreamer_t * streamer, const lv_gstreamer_pipeline_element_t * elements, size_t element_count); +static lv_result_t gstreamer_send_state_changed(lv_gstreamer_t * streamer, lv_gstreamer_stream_state_t state); /********************** * STATIC VARIABLES @@ -184,7 +186,9 @@ void lv_gstreamer_play(lv_obj_t * obj) GstStateChangeReturn ret = gst_element_set_state(streamer->pipeline, GST_STATE_PLAYING); if(ret == GST_STATE_CHANGE_FAILURE) { LV_LOG_ERROR("Unable to play pipeline"); + return; } + gstreamer_send_state_changed(streamer, LV_GSTREAMER_STREAM_STATE_PLAY); } void lv_gstreamer_pause(lv_obj_t * obj) @@ -202,7 +206,9 @@ void lv_gstreamer_pause(lv_obj_t * obj) if(ret == GST_STATE_CHANGE_FAILURE) { LV_LOG_ERROR("Unable to pause pipeline"); + return; } + gstreamer_send_state_changed(streamer, LV_GSTREAMER_STREAM_STATE_PAUSE); } void lv_gstreamer_stop(lv_obj_t * obj) @@ -219,7 +225,9 @@ void lv_gstreamer_stop(lv_obj_t * obj) GstStateChangeReturn ret = gst_element_set_state(streamer->pipeline, GST_STATE_READY); if(ret == GST_STATE_CHANGE_FAILURE) { LV_LOG_ERROR("Unable to stop pipeline"); + return; } + gstreamer_send_state_changed(streamer, LV_GSTREAMER_STREAM_STATE_STOP); } void lv_gstreamer_set_position(lv_obj_t * obj, uint32_t position) { @@ -362,6 +370,15 @@ void lv_gstreamer_set_rate(lv_obj_t * obj, uint32_t rate) } } +lv_gstreamer_stream_state_t lv_gstreamer_get_stream_state(lv_event_t * e) +{ + if(!e || e->code != LV_EVENT_STATE_CHANGED) { + LV_LOG_WARN("Invalid event"); + return -1; + } + return *(lv_gstreamer_stream_state_t *)lv_event_get_param(e); +} + /********************** * STATIC FUNCTIONS **********************/ @@ -383,7 +400,7 @@ static void lv_gstreamer_constructor(const lv_obj_class_t * class_p, lv_obj_t * LV_TRACE_OBJ_CREATE("finished"); } -static void gstreamer_poll_bus(lv_gstreamer_t * streamer) +static lv_result_t gstreamer_poll_bus(lv_gstreamer_t * streamer) { GstBus * bus = gst_element_get_bus(streamer->pipeline); GstMessage * msg; @@ -401,7 +418,12 @@ static void gstreamer_poll_bus(lv_gstreamer_t * streamer) break; } case GST_MESSAGE_EOS: - LV_LOG_INFO("End of stream"); + if(gstreamer_send_state_changed(streamer, LV_GSTREAMER_STREAM_STATE_END) == LV_RESULT_INVALID) { + /* Object deleted inside event handler */ + gst_object_unref(bus); + gst_message_unref(msg); + return LV_RESULT_INVALID; + } break; case GST_MESSAGE_STATE_CHANGED: { GstState old_state, new_state; @@ -417,6 +439,7 @@ static void gstreamer_poll_bus(lv_gstreamer_t * streamer) gst_message_unref(msg); } gst_object_unref(bus); + return LV_RESULT_OK; } static void gstreamer_update_frame(lv_gstreamer_t * streamer) @@ -469,6 +492,11 @@ static void gstreamer_update_frame(lv_gstreamer_t * streamer) /* We send the event AFTER setting the image source so that users can query the * resolution on this specific event callback */ if(first_frame) { + if(gstreamer_send_state_changed(streamer, LV_GSTREAMER_STREAM_STATE_START) == LV_RESULT_INVALID) { + /* Object deleted inside event handler */ + return; + } + /*Send READY event for backwards compatibility with v9.4*/ lv_obj_send_event((lv_obj_t *)streamer, LV_EVENT_READY, streamer); } @@ -481,7 +509,9 @@ static void gstreamer_timer_cb(lv_timer_t * timer) return; } - gstreamer_poll_bus(streamer); + if(gstreamer_poll_bus(streamer) == LV_RESULT_INVALID) { + return; + } gstreamer_update_frame(streamer); } @@ -646,4 +676,9 @@ static GstFlowReturn on_new_sample(GstElement * sink, gpointer user_data) g_async_queue_push(streamer->frame_queue, sample); return GST_FLOW_OK; } + +static lv_result_t gstreamer_send_state_changed(lv_gstreamer_t * streamer, lv_gstreamer_stream_state_t state) +{ + return lv_obj_send_event((lv_obj_t *)streamer, LV_EVENT_STATE_CHANGED, &state); +} #endif diff --git a/src/libs/gstreamer/lv_gstreamer.h b/src/libs/gstreamer/lv_gstreamer.h index 1b1761aac3..e7503ce70e 100644 --- a/src/libs/gstreamer/lv_gstreamer.h +++ b/src/libs/gstreamer/lv_gstreamer.h @@ -73,6 +73,14 @@ typedef enum { LV_GSTREAMER_STATE_PLAYING } lv_gstreamer_state_t; +typedef enum { + LV_GSTREAMER_STREAM_STATE_START, + LV_GSTREAMER_STREAM_STATE_PLAY, + LV_GSTREAMER_STREAM_STATE_PAUSE, + LV_GSTREAMER_STREAM_STATE_STOP, + LV_GSTREAMER_STREAM_STATE_END +} lv_gstreamer_stream_state_t; + /********************** * GLOBAL PROTOTYPES **********************/ @@ -170,6 +178,13 @@ uint8_t lv_gstreamer_get_volume(lv_obj_t * gstreamer); */ void lv_gstreamer_set_rate(lv_obj_t * gstreamer, uint32_t rate); +/** + * Retrieve the stream state from a STATE_CHANGED event callback + * @param e pointer to the event + * @return the stream state or -1 if `e` is invalid (i.e. NULL or does not match expected event) + */ +lv_gstreamer_stream_state_t lv_gstreamer_get_stream_state(lv_event_t * e); + /********************** * MACROS