Event Management¶
Goal¶
This tutorial explains to you how to receive events emitted by the Fluendo SDK to build a more advanced, robust and complete player. This time, you will learn to:
Subscribe to events
Show any error that may arise
Close the player when the video finishes or the window is closed
Prerequisites¶
Knowledge of the Observer Pattern. Essentially, it is a pattern commonly used to receive information asynchronously when some events are triggered to avoid actively polling.
Walkthrough¶
We start initializing the SDK, opening the URI of the video stream and playing it.
flu_initialize();
FluPlayer *player = flu_player_new();
/* Set the URI to the first argument if given */
const char *uri = "http://ftp.halifax.rwth-aachen.de/blender/demo/movies/ToS/tears_of_steel_720p.mov";
if (argc > 1)
{
uri = argv[1];
g_print("Setting video stream URI: %s\n", uri);
}
/* Ask the player to open the video and start playing it */
flu_player_uri_open(player, uri);
flu_player_play(player);
Now, we are going to subscribe to a handful set of interesting events that the player emits. Each event gives us different information that we are going to use to make our application more user-friendly. To register the observers (also known as listeners) to the events, we use the flu_player_event_listener method. It takes a FluPlayerListener along with an opaque pointer meant to pass user data to the listener. This pointer is a common way of getting the context from within a callback to avoid relying on global variables.
Since we don’t want to have to implement our own loop in our main thread that waits until some event happens before exiting, we are going to make use of GMainLoop. This convenient utility is nothing else than a loop will be relied upon in the following tutorials and it’s often used in GLib and GTK+ applications. We create it using g_main_loop_new and start it through g_main_loop_run to have our main thread running until some listener asks to stop it via g_main_loop_quit.
GMainLoop *main_loop = g_main_loop_new(NULL, FALSE);
/* Subscribe to the desired events, passing main_loop to the ones that need it */
flu_player_event_listener_add(player, FLU_PLAYER_EVENT_ERROR, _player_on_error, main_loop);
flu_player_event_listener_add(player, FLU_PLAYER_EVENT_EOS, _player_on_eos, main_loop);
flu_player_event_listener_add(player, FLU_PLAYER_EVENT_STREAM_STATE_CHANGED, _player_on_stream_state_changed, main_loop);
/* Start running the main_loop. This will run the loop until
g_main_loop_quit is called on it. */
g_main_loop_run(main_loop);
We are setting listeners for several FluPlayerEventType. Let’s go through every kind of event along with the listener registered.
Attention
The context from which each listener is called depends on the emitter of the event. Since different listeners for different events can be called from different threads, you need to be extra cautious in what you do within these listeners as you may need to protect for data races and pay attention that the methods you call are thread-safe.
FLU_PLAYER_EVENT_ERROR¶
FLU_PLAYER_EVENT_ERROR
is emitted when an error happens in the player. This
allows us to provide to the user a human-readable error. As stated before, the
__player_on_error
callback (as well as any other listener) receives 3
arguments: the FluPlayer, the
FluPlayerEvent
and the user data provided when subscribing. The first thing
that we need to do in the event handler (yet another way of calling the
listener) is to retrieve the information from FluPlayerEvent
. It is a C
union that conveys all the kind of events that FluPlayer can emit. This way, we
can define generic functions that take FluPlayerEvent
as an argument,
without the need to have a different method for each of the events.
Hence, for each FluPlayerEventType we subscribe to
for a particular listener, we can collect the information it carries by
interpreting the correct type of event. Almost every FluPlayerEventType maps 1:1 to each
FluPlayerEvent
, with few exceptions of events that don’t need to provide
any extra information. For our FLU_PLAYER_EVENT_ERROR
, we need to interpret
it (a cast in the C world) as a FluPlayerEventError. In this listener
are going to show the short (GError->message
) and long (dbg
)
description of the error. We also want to ask the main loop to quit because we
consider for this tutorial all errors are unrecoverable. To do this, we cast
the data
pointer to the actual type we passed when registering to the event
(GMainLoop) and
call g_main_loop_quit.
Lastly, we need to return either TRUE
or FALSE
from the listener. With
FALSE
, the listener will be unsubscribed from the observer.
static gboolean _player_on_error(FluPlayer *player, FluPlayerEvent *event, gpointer data)
{
FluPlayerEventError *ev = (FluPlayerEventError *)event;
GMainLoop *main_loop = (GMainLoop *)data;
g_print("Error: %s\nDetailed description: %s.\n", ev->error->message, ev->dbg);
g_main_loop_quit(main_loop);
return TRUE;
}
- Example of error reported
- Error: Not FoundDetailed description: gstsouphttpsrc.c(1195): gst_soup_http_src_parse_status ():/GstPipeline:pipeline0/GstURIDecodeBin:uridecodebin0/GstSoupHTTPSrc:source:Not Found (404), URL: http://nonexistingmedia.
FLU_PLAYER_EVENT_EOS¶
The second event we subscribed to is FLU_PLAYER_EVENT_EOS
, which is emitted
when the EOS
(End Of Stream) is reached. For this case, there is no
FluEventType
really associated since this is a self-explanatory event. All
we want to do when this happens is quitting the loop.
static gboolean _player_on_eos(FluPlayer *player, FluPlayerEvent *event, gpointer data)
{
GMainLoop *main_loop = (GMainLoop *)data;
g_print("EOS received\n");
g_main_loop_quit(main_loop);
return TRUE;
}
FLU_PLAYER_EVENT_STREAM_STATE_CHANGED¶
The last event is FLU_PLAYER_EVENT_STREAM_STATE_CHANGED
, that allow us
to detect when the different streams are activated or deactivated.
Note
A single video container can have several streams of different kinds: video, audio, text or data. For instance, a movie could have different video qualities and tracks of audio for different languages, along with subtitles. To learn more about this, check out the Stream Management tutorial.
We use this listener to detect that the window has been closed, because it
deactivates the video stream. The FluEventType
sent to this event handler
is the FluPlayerEventStreamStateChanged,
that contains the FluStream that we use to
query the state of the stream through the flu_stream_is_active method. We are also
going to show the user some log when any stream state changes. In order to
distinguish the type of stream we create a helper method called
_stream_type_name
that uses flu_stream_type_get.
static const gchar *_stream_type_name(FluStream *stream)
{
switch (flu_stream_type_get(stream))
{
case FLU_STREAM_TYPE_VIDEO:
return "video";
case FLU_STREAM_TYPE_AUDIO:
return "audio";
case FLU_STREAM_TYPE_TEXT:
return "text";
case FLU_STREAM_TYPE_DATA:
return "data";
default:
return "unknown";
}
}
static gboolean _player_on_stream_state_changed(FluPlayer *player, FluPlayerEvent *event, gpointer data)
{
FluPlayerEventStreamStateChanged *ev = (FluPlayerEventStreamStateChanged *)event;
GMainLoop *main_loop = (GMainLoop *)data;
if (ev->stream && !flu_stream_is_pending(ev->stream))
{
gboolean active = flu_stream_is_active(ev->stream);
g_print("Info: %s stream %p has become %s.\n",
_stream_type_name(ev->stream),
ev->stream,
active ? "active" : "inactive");
if (!active && g_main_loop_is_running(main_loop))
{
g_print("Window closed, exiting...\n");
g_main_loop_quit(main_loop);
}
}
return TRUE;
}
Pay attention to the g_main_loop_is_running call to check that we have not previously attempted to quit. After closing the player, we may still receive this same event telling us that a different stream (such as audio) has also become inactive.
- Output
- Info: video stream 0x7fa230007000 has become active.Info: audio stream 0x7fa230007180 has become active.Info: video stream 0x7fa230007000 has become inactive.Window closed, exiting…
Finally, we release the references to the objects we have worked with and shutdown the SDK.
flu_player_close(player);
flu_player_unref(player);
g_main_loop_unref(main_loop);
flu_shutdown();
Full source code¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | #include <fluendo-sdk.h>
static gboolean _player_on_error(FluPlayer *player, FluPlayerEvent *event, gpointer data)
{
FluPlayerEventError *ev = (FluPlayerEventError *)event;
GMainLoop *main_loop = (GMainLoop *)data;
g_print("Error: %s\nDetailed description: %s.\n", ev->error->message, ev->dbg);
g_main_loop_quit(main_loop);
return TRUE;
}
static gboolean _player_on_eos(FluPlayer *player, FluPlayerEvent *event, gpointer data)
{
GMainLoop *main_loop = (GMainLoop *)data;
g_print("EOS received\n");
g_main_loop_quit(main_loop);
return TRUE;
}
static const gchar *_stream_type_name(FluStream *stream)
{
switch (flu_stream_type_get(stream))
{
case FLU_STREAM_TYPE_VIDEO:
return "video";
case FLU_STREAM_TYPE_AUDIO:
return "audio";
case FLU_STREAM_TYPE_TEXT:
return "text";
case FLU_STREAM_TYPE_DATA:
return "data";
default:
return "unknown";
}
}
static gboolean _player_on_stream_state_changed(FluPlayer *player, FluPlayerEvent *event, gpointer data)
{
FluPlayerEventStreamStateChanged *ev = (FluPlayerEventStreamStateChanged *)event;
GMainLoop *main_loop = (GMainLoop *)data;
if (ev->stream && !flu_stream_is_pending(ev->stream))
{
gboolean active = flu_stream_is_active(ev->stream);
g_print("Info: %s stream %p has become %s.\n",
_stream_type_name(ev->stream),
ev->stream,
active ? "active" : "inactive");
if (!active && g_main_loop_is_running(main_loop))
{
g_print("Window closed, exiting...\n");
g_main_loop_quit(main_loop);
}
}
return TRUE;
}
int main(int argc, const char **argv)
{
/* Initialize the Fluendo SDK and create the player */
flu_initialize();
FluPlayer *player = flu_player_new();
/* Set the URI to the first argument if given */
const char *uri = "http://ftp.halifax.rwth-aachen.de/blender/demo/movies/ToS/tears_of_steel_720p.mov";
if (argc > 1)
{
uri = argv[1];
g_print("Setting video stream URI: %s\n", uri);
}
/* Ask the player to open the video and start playing it */
flu_player_uri_open(player, uri);
flu_player_play(player);
/* Create the main loop that will handle the events we subscribe to */
GMainLoop *main_loop = g_main_loop_new(NULL, FALSE);
/* Subscribe to the desired events, passing main_loop to the ones that need it */
flu_player_event_listener_add(player, FLU_PLAYER_EVENT_ERROR, _player_on_error, main_loop);
flu_player_event_listener_add(player, FLU_PLAYER_EVENT_EOS, _player_on_eos, main_loop);
flu_player_event_listener_add(player, FLU_PLAYER_EVENT_STREAM_STATE_CHANGED, _player_on_stream_state_changed, main_loop);
/* Start running the main_loop. This will run the loop until
g_main_loop_quit is called on it. */
g_main_loop_run(main_loop);
/* Close the player, unref it, unref the main loop and shutdown the Fluendo SDK */
flu_player_close(player);
flu_player_unref(player);
g_main_loop_unref(main_loop);
flu_shutdown();
return 0;
}
|
You can download it here.
Building¶
This source code along with the rest of tutorials can be compiled using the following commands.
On Linux:
mkdir fluendo-sdk-tutorials && cd fluendo-sdk-tutorials
meson /opt/fluendo-sdk/share/doc/fluendo-sdk/tutorials/src
ninja
On Windows:
mkdir fluendo-sdk-tutorials
cd fluendo-sdk-tutorials
meson C:\fluendo-sdk\<version>\<x86/x86_64>\share\doc\fluendo-sdk\tutorials\src
ninja
To generate a Visual Studio project, you can pass the --backend=vs
option
to meson.
Conclusions¶
Summing up, this tutorial explained:
How to subscribe to the Fluendo SDK events using flu_player_event_listener passing a callback that distinguishes the kind of event FluPlayerEventType
How to create, run and close a GMainLoop via g_main_loop_new, g_main_loop_run, and g_main_loop_quit
How
FLU_PLAYER_EVENT_ERROR
is emitted when an error happens and how we can inspect FluPlayerEventError to retrieve information about the error that ocurredHow
FLU_PLAYER_EVENT_EOS
is triggered when the end of stream is reachedHow
FLU_PLAYER_EVENT_STREAM_STATE_CHANGED
informs about the different FluStream activation and deactivation via FluPlayerEventStreamStateChangedHow we can know whether a stream is active or not calling flu_stream_is_active
How flu_stream_type_get reports the kind of stream
Continue to the next tutorial to learn how manage the video latency.