feat(gstreamer): send event on end of stream (#9634)

This commit is contained in:
André Costa
2026-02-07 08:18:55 +01:00
committed by GitHub
parent d0c1bbff24
commit a3f8487127
4 changed files with 148 additions and 48 deletions
+53 -23
View File
@@ -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
*******************
@@ -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;
}
}
+39 -4
View File
@@ -14,6 +14,7 @@
#include <glib.h>
#include <gst/gstelementfactory.h>
#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
+15
View File
@@ -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