[vlc-commits] [Git][videolan/vlc][master] 13 commits: qt: mlbookmarkmodel: use lambda for player cbs

Steve Lhomme (@robUx4) gitlab at videolan.org
Tue Sep 26 12:15:05 UTC 2023



Steve Lhomme pushed to branch master at VideoLAN / VLC


Commits:
df4ec732 by Alexandre Janniaux at 2023-09-26T11:59:26+00:00
qt: mlbookmarkmodel: use lambda for player cbs

The lambda will allow to use a zero-initializer and only initialize the
required fields.

- - - - -
d286d255 by Alexandre Janniaux at 2023-09-26T11:59:26+00:00
vlc_player: fix typo in doc

- - - - -
5d502b0b by Alexandre Janniaux at 2023-09-26T11:59:26+00:00
VLCPlayerController: use designated initializers

Designated initializer will allow to add new fields in the
vlc_player_cbs structure while automatically initializing the new fields
to null.

- - - - -
2ae6636c by Alexandre Janniaux at 2023-09-26T11:59:26+00:00
vlc_player: add a new on_stopping_current_media event

Add a new event notifying when the current media is stopping.

The media can be stopping without the player being stopping, ie. when
the media is finished and the player prepares the next one, but also
when the player changes media while having the player in the
VLC_PLAYER_STATE_PLAYING state.

The event will also be fired when the player is simply stopping, in
which case we expect that VLC_PLAYER_STATE_STOPPING is signaled before
the input is actually stop, which is coherent with the current
behaviour.

- - - - -
4ac61be1 by Alexandre Janniaux at 2023-09-26T11:59:26+00:00
libvlc_events: add libvlc_MediaPlayerMediaStopping

The event forwards the vlc_player_t event `on_stopping_current_media`
which signals that the media actually playing inside the player is
stopping.

The event is essential to the `libvlc_media_new_callbacks` API since
there's no way to stop the \ref libvlc_media_read_cb callback without
triggering an EOF, and the EOF should be signalled only as soon as the
media is stopping.

- - - - -
9bb751cb by Alexandre Janniaux at 2023-09-26T11:59:26+00:00
test: player: add player state_to_string function

Most of the code is checking the player state, but we had no way to
signal in the test logs which state was actually there when it fails.

- - - - -
bcba4461 by Alexandre Janniaux at 2023-09-26T11:59:26+00:00
test: player: dump the state vec when asserting

Dumping the list of state helps finding the root cause of the issue when
a test fails, but also enable additional manual tests to check whether
an unexpected untested test transition has happened or not.

- - - - -
52a231b8 by Alexandre Janniaux at 2023-09-26T11:59:26+00:00
test: player: add a state_equal() function

The function improves the error reporting when the assertion is
triggered, and also logs what were the resulting state, while nicely
handling negative index like python arrays.

- - - - -
4a35193e by Alexandre Janniaux at 2023-09-26T11:59:26+00:00
lib: media_player: move vlc_assert_unreachable

By moving the vlc_assert_unreachable after the switch(), we can get
warnings when not every cases are handled since there is no default
case, and we can keep asserting when it fails to reach any return
statement before.

- - - - -
6d40b91c by Alexandre Janniaux at 2023-09-26T11:59:26+00:00
lib: media_player: split function

Splitting the function allows return statement, which removes the need
for break, and simplify the code. It also allows to use a designated
initializer for the event structure.

The code also move the vlc_assert_unreachable in the new function.

By moving the vlc_assert_unreachable after the switch(), we can get
warnings when not every cases are handled since there is no default
case, and we can keep asserting when it fails to reach any return
statement before.

- - - - -
e651ac80 by Alexandre Janniaux at 2023-09-26T11:59:26+00:00
player: signal on_current_media_stopping event

Signal that the media is stopping to the client. This is done after
input_Stop so that application can rely on the fact that the underlying
input_thread is already stopping, so that we can trigger actions like
EOS, closing input resources, etc.

- - - - -
3841df7a by Alexandre Janniaux at 2023-09-26T11:59:26+00:00
lib: media player: signal stopping media event

Signal that the media is stopping to the libvlc client. This is done
after notifying the media on the VLC side that we're going to stop it,
so that application can rely on the fact that the objects using
application resources are already stopping, so that we can trigger
actions like EOS, closing those resources, etc.

- - - - -
30d1237d by Alexandre Janniaux at 2023-09-26T11:59:26+00:00
test: libvlc_callback: check imem-access behaviour

The previous patches added an event from libvlc_media_player_t which
notify the application that the current media is stopping, regardless of
whether the player itself is stopping or that it will process the next
media.

Those patches solve a design problem on the imem-access and libvlc
callbacks where the application could not be notified that the input was
stopped when the read() callback was pending, which happens through
vlc_interrupt_t in other demux.

In that situation, an application wanting to trigger the stop of the
media player can only signal the media end-of-stream,
triggering a pipeline drain, before stopping the player.

```mermaid
sequenceDiagram

    box Orange VLC side
    participant input as Input thread
    participant imem as imem access
    end

    box Cyan Application size
    participant app as Application
    end

    input ->> imem: stream::read()

    activate imem
    imem ->> app: media callback read()
    app --x imem: return "EOS" (no data to send)
    imem --x input: Trigger pipeline drain (EOS)
    deactivate imem

    app ->> input: stop (change media)
```mermaid

This decoder and output drain adds latency to the change of a media. The
duration depends on condition but I've been able to reproduce a steady
400ms latency addition when changing media (meaning that it will take
400ms more to start reading the new media), up to a gigantic 1.6s in a
randomized manner (1.1s being taken by the drain of the audio decoder!).

With the new system, it's now possible to let the application respond to
the new event to handle the interruption and termination of what it has
been doing in the libvlc_media_read_cb function, without triggering the
end of the media when it would have triggered a drain.

```mermaid
sequenceDiagram

    box Orange VLC side
    participant destructor as Player input destructor
    participant input as Input
    participant imem as imem access
    end

    box Cyan Application side
    participant player as Player
    participant app as Application
    end

    par Input thread
    activate imem
    input ->> imem: stream::read()
    imem ->> app: media callback read_cb()
    app ->> app: read_cb() might be waiting for some data

    and Application thread
    app ->> player: Stop()
    player ->> destructor: Add current input to destructor

    and Player destructor thread
    destructor ->> input: input_Stop()
    destructor ->> app: libvlc_MediaPlayerMediaStopping event
    end

    app ->> app: Interrupt read_cb() and return EOF
    deactivate imem
```

This commit verifies this behaviour in a reliable way to ensure that the
problem is indeed solved by this approach, while allowing to document
the solution on the application side.

To do so, two different tests are made:

 - A sanity test which checks that we can stop a libvlc media which is
   not blocking, basically checking that the media_callback API works
   and running it when we want to check against sanitizers.

 - A specific test checking that we can block the read() and only
   unblock it when the libvlc_MediaPlayerMediaStopping event is emitted,
   checking that the documented behaviour for the media callbacks is
   working correctly.

---

The test needs:

 - No intermediate cache so that we clearly control the access from the
   demux.

 - Better deterministic behaviour, typically to check that we don't call
   AccessReadNonBlocking twice if we don't notify between them and try
   to check that read() is correctly terminating in this case.

---

On a side note, other solutions were investigated to solve this issue:

1/ Provide an `errno` variable as parameter to read:

By providing an errno parameter, we can provide a specific error code
when -1 is returned, and in particular use EAGAIN/EWOULDBLOCK to inform
the imem-access code that we don't have data yet. Then, the imem-access
can wait for a notification from the application to start reading again,
while listening to interruption coming from the core.

This solution was submitted but it felt like alienating the libVLC imem
API and libvlc media API to solve this problem, without considering it
as an application problem as a whole.

2/ Provide the same solution but use `errno` directly:

This solution would be the simplest but there's no way for the
application to realize that errno returning EAGAIN, ie. mapping the read
callback directly to read() on a non-blocking socket for instance, might
create silent regression where the access would not try to fetch more
data, since the application won't know it must notify the access.

3/ Provide a poll_cb() callback in the imem interface

The solution would require another callback, instead of another
parameter. The new callback would be called every time before the read()
callback, and would notify whether the read() is ready or not. This adds
a new way for the application to exit, differentiating the return code
for read() from the return code from poll(), but the access still cannot
be interrupted and the additional cost on function call is higher.

4/ Provide a `waker` assignment (like rust futures)

Provide a new callback to specify an object that can be used to notify
the access that new data is available. This is much like poll() but it
has the benefit that the application can immediately notify that no data
is available, and then provide the object to notify the arrival of data
asynchronously without exposing a public function on libvlc_media_t.
This is still quite complex and still has a heavy function call cost,
but it also fullfill every points from the suggestion here. However, the
rust async model is not really the idiomatic model in C compared to
POSIX errno.

5/ Solve the problem at the decoder and output level

One of the main symptoms that can be observed when changing media is the
increase of the time to close a media, and in particular, to drain the
decoder. This is also a non-interruptible task, because a decoder drain
is synchronous. Admittedly, the interruption problem is less problematic
if the drain itself can be handled smoothly afterwards, and I actually
think both problems need to be solved eventually. But the decoder
behaviour on drain depends on the decoder implementation, whereas the
imem-access suggests a solution on the application side to correctly
express the required intent, and it is also quite suitable to express an
injection workflow (data being written from the application to the
VLC pipeline) as well as a pull workflow (data being read from the
application by VLC).

- - - - -


11 changed files:

- include/vlc/libvlc_events.h
- include/vlc_player.h
- lib/media_player.c
- modules/gui/macosx/playlist/VLCPlayerController.m
- modules/gui/qt/medialibrary/mlbookmarkmodel.cpp
- modules/gui/qt/player/player_controller.cpp
- src/player/player.c
- test/Makefile.am
- + test/libvlc/media_callback.c
- test/libvlc/meson.build
- test/src/player/player.c


Changes:

=====================================
include/vlc/libvlc_events.h
=====================================
@@ -241,6 +241,18 @@ enum libvlc_event_e {
      * The renderer item is no longer valid.
      */
     libvlc_RendererDiscovererItemDeleted,
+
+    /**
+     * The current media set into the \ref libvlc_media_player_t is stopping.
+     *
+     * This event can be used to notify when the media callbacks, initialized
+     * from \ref libvlc_media_new_callbacks, should be interrupted, and in
+     * particular the \ref libvlc_media_read_cb. It can also be used to signal
+     * the application state that any input resource (webserver, file mounting,
+     * etc) can be discarded. Output resources still need to be active until
+     * the player switches to the \ref libvlc_Stopped state.
+     */
+    libvlc_MediaPlayerMediaStopping,
 };
 
 /**
@@ -371,6 +383,12 @@ typedef struct libvlc_event_t
             libvlc_media_t * new_media;
         } media_player_media_changed;
 
+        struct
+        {
+            libvlc_media_t * media;
+        } media_player_media_stopping;
+
+
         /* ESAdded, ESDeleted, ESUpdated */
         struct
         {


=====================================
include/vlc_player.h
=====================================
@@ -1146,7 +1146,7 @@ vlc_player_GetSelectedChapter(vlc_player_t *player)
  * Select a chapter index for the current media
  *
  * @note A successful call will trigger the
- * vlc_player_cbs.on_chaper_selection_changed event.
+ * vlc_player_cbs.on_chapter_selection_changed event.
  *
  * @see vlc_player_title.chapters
  *
@@ -3221,6 +3221,23 @@ struct vlc_player_cbs
      * @param data opaque pointer set by vlc_player_AddListener()
      */
     void (*on_playback_restore_queried)(vlc_player_t *player, void *data);
+
+    /**
+     * Called when the player will stop the current media.
+     *
+     * @note This can be called from the PLAYING state, before the
+     * player requests the next media, or from the STOPPING state, ie.
+     * when the player is stopping.
+     *
+     * @see vlc_player_SetCurrentMedia()
+     * @see vlc_player_Stop()
+     *
+     * @param player locked player instance
+     * @param prev_media media currently stopping
+     * @param data opaque pointer set by vlc_player_AddListener()
+     */
+    void (*on_stopping_current_media)(vlc_player_t *player,
+            input_item_t *current_media, void *data);
 };
 
 /**


=====================================
lib/media_player.c
=====================================
@@ -80,34 +80,53 @@ on_current_media_changed(vlc_player_t *player, input_item_t *new_media,
 }
 
 static void
-on_state_changed(vlc_player_t *player, enum vlc_player_state new_state,
-                 void *data)
+on_stopping_current_media(vlc_player_t *player, input_item_t *media,
+                         void *data)
 {
+    assert(media != NULL);
     (void) player;
 
     libvlc_media_player_t *mp = data;
 
+    libvlc_media_t *libmedia = media->libvlc_owner;
+    assert(libmedia != NULL);
+
     libvlc_event_t event;
+    event.type = libvlc_MediaPlayerMediaStopping;
+    event.u.media_player_media_stopping.media = libmedia;
+    libvlc_event_send(&mp->event_manager, &event);
+}
+
+
+static libvlc_event_type_t
+PlayerStateToLibvlcEventType(enum vlc_player_state new_state)
+{
     switch (new_state) {
         case VLC_PLAYER_STATE_STOPPED:
-            event.type = libvlc_MediaPlayerStopped;
-            break;
+            return libvlc_MediaPlayerStopped;
         case VLC_PLAYER_STATE_STOPPING:
-            event.type = libvlc_MediaPlayerStopping;
-            break;
+            return libvlc_MediaPlayerStopping;
         case VLC_PLAYER_STATE_STARTED:
-            event.type = libvlc_MediaPlayerOpening;
-            break;
+            return libvlc_MediaPlayerOpening;
         case VLC_PLAYER_STATE_PLAYING:
-            event.type = libvlc_MediaPlayerPlaying;
-            break;
+            return libvlc_MediaPlayerPlaying;
         case VLC_PLAYER_STATE_PAUSED:
-            event.type = libvlc_MediaPlayerPaused;
-            break;
-        default:
-            vlc_assert_unreachable();
+            return libvlc_MediaPlayerPaused;
     }
+    vlc_assert_unreachable();
+}
+
+static void
+on_state_changed(vlc_player_t *player, enum vlc_player_state new_state,
+                 void *data)
+{
+    (void) player;
+
+    libvlc_media_player_t *mp = data;
 
+    libvlc_event_t event = {
+        .type = PlayerStateToLibvlcEventType(new_state)
+    };
     libvlc_event_send(&mp->event_manager, &event);
 }
 
@@ -508,6 +527,7 @@ on_audio_device_changed(audio_output_t *aout, const char *device, void *data)
 
 static const struct vlc_player_cbs vlc_player_cbs = {
     .on_current_media_changed = on_current_media_changed,
+    .on_stopping_current_media = on_stopping_current_media,
     .on_state_changed = on_state_changed,
     .on_error_changed = on_error_changed,
     .on_buffering_changed = on_buffering_changed,
@@ -1659,9 +1679,8 @@ libvlc_state_t libvlc_media_player_get_state( libvlc_media_player_t *p_mi )
             return libvlc_Playing;
         case VLC_PLAYER_STATE_PAUSED:
             return libvlc_Paused;
-        default:
-            vlc_assert_unreachable();
     }
+    vlc_assert_unreachable();
 }
 
 bool libvlc_media_player_is_seekable(libvlc_media_player_t *p_mi)


=====================================
modules/gui/macosx/playlist/VLCPlayerController.m
=====================================
@@ -474,39 +474,39 @@ static void cb_player_vout_changed(vlc_player_t *p_player,
 }
 
 static const struct vlc_player_cbs player_callbacks = {
-    cb_player_current_media_changed,
-    cb_player_state_changed,
-    cb_player_error_changed,
-    cb_player_buffering,
-    cb_player_rate_changed,
-    cb_player_capabilities_changed,
-    cb_player_position_changed,
-    cb_player_length_changed,
-    cb_player_track_list_changed,
-    cb_player_track_selection_changed,
-    cb_player_track_delay_changed,
-    cb_player_program_list_changed,
-    cb_player_program_selection_changed,
-    cb_player_titles_changed,
-    cb_player_title_selection_changed,
-    cb_player_chapter_selection_changed,
-    cb_player_teletext_menu_availability_changed,
-    cb_player_teletext_enabled_changed,
-    cb_player_teletext_page_changed,
-    cb_player_teletext_transparency_changed,
-    cb_player_category_delay_changed,
-    cb_player_associated_subs_fps_changed,
-    cb_player_renderer_changed,
-    cb_player_record_changed,
-    NULL, //cb_player_signal_changed,
-    cb_player_stats_changed,
-    cb_player_atobloop_changed,
-    cb_player_media_stopped_action_changed,
-    cb_player_item_meta_changed,
-    NULL, //cb_player_item_epg_changed,
-    NULL, //cb_player_subitems_changed,
-    cb_player_vout_changed,
-    NULL, //on_cork_changed
+    .on_current_media_changed = cb_player_current_media_changed,
+    .on_state_changed = cb_player_state_changed,
+    .on_error_changed = cb_player_error_changed,
+    .on_buffering_changed = cb_player_buffering,
+    .on_rate_changed = cb_player_rate_changed,
+    .on_capabilities_changed = cb_player_capabilities_changed,
+    .on_position_changed = cb_player_position_changed,
+    .on_length_changed = cb_player_length_changed,
+    .on_track_list_changed = cb_player_track_list_changed,
+    .on_track_selection_changed = cb_player_track_selection_changed,
+    .on_track_delay_changed = cb_player_track_delay_changed,
+    .on_program_list_changed = cb_player_program_list_changed,
+    .on_program_selection_changed = cb_player_program_selection_changed,
+    .on_titles_changed = cb_player_titles_changed,
+    .on_title_selection_changed = cb_player_title_selection_changed,
+    .on_chapter_selection_changed = cb_player_chapter_selection_changed,
+    .on_teletext_menu_changed = cb_player_teletext_menu_availability_changed,
+    .on_teletext_enabled_changed = cb_player_teletext_enabled_changed,
+    .on_teletext_page_changed = cb_player_teletext_page_changed,
+    .on_teletext_transparency_changed = cb_player_teletext_transparency_changed,
+    .on_category_delay_changed = cb_player_category_delay_changed,
+    .on_associated_subs_fps_changed = cb_player_associated_subs_fps_changed,
+    .on_renderer_changed = cb_player_renderer_changed,
+    .on_recording_changed = cb_player_record_changed,
+    .on_signal_changed = NULL,
+    .on_statistics_changed = cb_player_stats_changed,
+    .on_atobloop_changed = cb_player_atobloop_changed,
+    .on_media_stopped_action_changed = cb_player_media_stopped_action_changed,
+    .on_media_meta_changed = cb_player_item_meta_changed,
+    .on_media_epg_changed = NULL,
+    .on_media_subitems_changed = NULL,
+    .on_vout_changed = cb_player_vout_changed,
+    .on_cork_changed = NULL,
 };
 
 #pragma mark - video specific callback implementations


=====================================
modules/gui/qt/medialibrary/mlbookmarkmodel.cpp
=====================================
@@ -530,42 +530,12 @@ void MLBookmarkModel::setMl(MediaLib* medialib)
 
 void MLBookmarkModel::initModel()
 {
-    static const vlc_player_cbs cbs {
-        &onCurrentMediaChanged,
-        &onPlaybackStateChanged,
-        nullptr,
-        nullptr,
-        nullptr,
-        nullptr,
-        nullptr,
-        nullptr,
-        nullptr,
-        nullptr,
-        nullptr,
-        nullptr,
-        nullptr,
-        nullptr,
-        nullptr,
-        nullptr,
-        nullptr,
-        nullptr,
-        nullptr,
-        nullptr,
-        nullptr,
-        nullptr,
-        nullptr,
-        nullptr,
-        nullptr,
-        nullptr,
-        nullptr,
-        nullptr,
-        nullptr,
-        nullptr,
-        nullptr,
-        nullptr,
-        nullptr,
-        nullptr,
-    };
+    static const vlc_player_cbs cbs = []{
+        vlc_player_cbs cbs {};
+        cbs.on_current_media_changed = &onCurrentMediaChanged;
+        cbs.on_state_changed = &onPlaybackStateChanged;
+        return cbs;
+    }();
     QString uri;
     {
         vlc_player_locker lock{ m_player };


=====================================
modules/gui/qt/player/player_controller.cpp
=====================================
@@ -1025,7 +1025,8 @@ static const struct vlc_player_cbs player_cbs = {
     on_player_subitems_changed,
     on_player_vout_changed,
     on_player_corks_changed,
-    on_player_playback_restore_queried
+    on_player_playback_restore_queried,
+    nullptr, // on_stopping_current_media: not used
 };
 
 static const vlc_player_vout_cbs player_vout_cbs = []{


=====================================
src/player/player.c
=====================================
@@ -223,7 +223,12 @@ vlc_player_destructor_Thread(void *data)
                                          VLC_TICK_INVALID);
             vlc_player_destructor_AddStoppingInput(player, input);
 
+            /* Note: no need to hold the media here, it will be valid
+             * until input_Close() and the event is sent from the thread
+             * that will call this function. */
+            input_item_t *media = input_GetItem(input->thread);
             input_Stop(input->thread);
+            vlc_player_SendEvent(player, on_stopping_current_media, media);
         }
 
         bool keep_sout = true;


=====================================
test/Makefile.am
=====================================
@@ -12,6 +12,7 @@ check_PROGRAMS = \
 	test_libvlc_core \
 	test_libvlc_equalizer \
 	test_libvlc_media \
+	test_libvlc_media_callback \
 	test_libvlc_media_thumbnail_argb \
 	test_libvlc_media_thumbnail_jpeg \
 	test_libvlc_media_thumbnail_png \
@@ -123,6 +124,8 @@ test_libvlc_equalizer_SOURCES = libvlc/equalizer.c
 test_libvlc_equalizer_LDADD = $(LIBVLC)
 test_libvlc_media_SOURCES = libvlc/media.c
 test_libvlc_media_LDADD = $(LIBVLCCORE) $(LIBVLC)
+test_libvlc_media_callback_SOURCES = libvlc/media_callback.c
+test_libvlc_media_callback_LDADD = $(LIBVLCCORE) $(LIBVLC)
 test_libvlc_media_thumbnail_argb_SOURCES = libvlc/media_thumbnail.c
 test_libvlc_media_thumbnail_argb_LDADD = $(LIBVLCCORE) $(LIBVLC)
 test_libvlc_media_thumbnail_argb_CPPFLAGS = $(AM_CPPFLAGS) -DTEST_THUMB_TYPE=libvlc_picture_Argb -DTEST_REQUIRED_MODULES=\"avcodec\"


=====================================
test/libvlc/media_callback.c
=====================================
@@ -0,0 +1,304 @@
+/*****************************************************************************
+ * imem.c: test for the imem libvlc code
+ *****************************************************************************
+ * Copyright (C) 2023 VideoLabs
+ *
+ * Authors: Alexandre Janniaux <ajanni at videolabs.io>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+/* Define a builtin module for mocked parts */
+#define MODULE_NAME test_imem
+#undef VLC_DYNAMIC_PLUGIN
+
+#include "./test.h"
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_stream.h>
+#include <vlc_threads.h>
+#include <vlc_interrupt.h>
+
+#include <vlc/libvlc_media.h>
+
+#include <limits.h>
+
+#include "media_player.h"
+
+const char vlc_module_name[] = MODULE_STRING;
+
+struct imem_root
+{
+    /* root controlled semaphores */
+    vlc_sem_t available;
+    vlc_sem_t done;
+    vlc_sem_t opened;
+    vlc_sem_t read_blocking;
+
+    vlc_sem_t wait;
+};
+
+struct imem_access
+{
+    struct imem_root *root;
+    vlc_sem_t wait;
+};
+
+static void AccessClose(void *opaque)
+{
+    struct imem_access *sys = opaque;
+    fprintf(stderr, "test: Access: Close\n");
+    vlc_sem_post(&sys->root->done);
+    vlc_sem_post(&sys->root->available);
+    free(sys);
+}
+
+static int AccessOpen(void *opaque, void **datap, uint64_t *sizep)
+{
+    (void)sizep;
+    struct imem_root *root = opaque;
+    vlc_sem_wait(&root->available);
+    fprintf(stderr, "test: Access: Opening new instance\n");
+
+    struct imem_access *sys = *datap = malloc(sizeof *sys);
+    if (sys == NULL)
+        return -ENOMEM;
+
+    sys->root = root;
+    vlc_sem_init(&sys->wait, 0);
+    vlc_sem_post(&sys->root->opened);
+    return VLC_SUCCESS;
+}
+
+static ssize_t AccessRead(void *opaque, unsigned char *buf, size_t len)
+{
+    (void)opaque;
+    assert(len < SSIZE_MAX);
+    memset(buf, 0xff, len);
+    return len;
+}
+
+static void SetFlag(void *opaque)
+{
+    bool *flag = opaque;
+    *flag = true;
+}
+
+static ssize_t AccessReadBlocking(void *opaque, unsigned char *buf, size_t len)
+{
+    (void)opaque; (void)buf;
+    struct imem_access *sys = opaque;
+    assert(len < SSIZE_MAX);
+
+    vlc_sem_post(&sys->root->read_blocking);
+    /* The interruption is used to detect when the input has been closed. */
+    bool was_interrupted = false;
+    vlc_interrupt_register(SetFlag, &was_interrupted);
+    fprintf(stderr, "test: Access: read() -> blocking\n");
+    vlc_sem_wait(&sys->root->wait);
+    fprintf(stderr, "test: Access: read() -> unblocked\n");
+    vlc_interrupt_unregister();
+    //assert(was_interrupted);
+
+    /* We notify that we don't have data to read right now. */
+    return 0;
+}
+
+static void UnblockRead(const libvlc_event_t *event, void *opaque)
+{
+    (void)event;
+    fprintf(stderr, "test: Player: Unblock read\n");
+    struct imem_root *imem = opaque;
+    vlc_sem_post(&imem->wait);
+}
+
+static struct imem_root *imem_root_New(void)
+{
+
+    struct imem_root *imem = malloc(sizeof *imem);
+    assert(imem != NULL);
+
+    vlc_sem_init(&imem->available, 1);
+    vlc_sem_init(&imem->done, 0);
+    vlc_sem_init(&imem->wait, 0);
+    vlc_sem_init(&imem->opened, 0);
+    vlc_sem_init(&imem->read_blocking, 0);
+
+    return imem;
+}
+
+static void test_media_callback(libvlc_instance_t *vlc)
+{
+    struct imem_root *imem = imem_root_New();
+
+    fprintf(stderr, "test: 1/ checking that media can terminate\n");
+    libvlc_media_t *media = libvlc_media_new_callbacks(
+            AccessOpen,
+            AccessRead,
+            NULL,//AccessSeek,
+            AccessClose,
+            imem);
+    assert(media != NULL);
+
+    libvlc_media_player_t *player = libvlc_media_player_new(vlc);
+    assert(player != NULL);
+
+    struct mp_event_ctx wait_play;
+    mp_event_ctx_init(&wait_play);
+    libvlc_event_manager_t *mgr = libvlc_media_player_event_manager(player);
+    libvlc_event_attach(mgr, libvlc_MediaPlayerOpening, mp_event_ctx_on_event, &wait_play);
+
+    struct mp_event_ctx wait_stopped;
+    mp_event_ctx_init(&wait_stopped);
+    libvlc_event_attach(mgr, libvlc_MediaPlayerStopped, mp_event_ctx_on_event, &wait_stopped);
+
+    libvlc_media_player_set_media(player, media);
+    libvlc_media_player_play(player);
+
+    mp_event_ctx_wait(&wait_play);
+    libvlc_event_detach(mgr, libvlc_MediaPlayerOpening, mp_event_ctx_on_event, &wait_play);
+
+    // TODO: Wait event Opening
+    libvlc_media_player_stop_async(player);
+
+    mp_event_ctx_wait(&wait_stopped);
+    libvlc_event_detach(mgr, libvlc_MediaPlayerStopped, mp_event_ctx_on_event, &wait_stopped);
+
+    vlc_sem_wait(&imem->done);
+    libvlc_media_release(media);
+    libvlc_media_player_release(player);
+
+    free(imem);
+}
+
+static void test_media_callback_interrupt(libvlc_instance_t *vlc)
+{
+    struct imem_root *imem = imem_root_New();
+
+    fprintf(stderr, "test: 2/ checking that we can terminate after the input\n");
+    libvlc_media_player_t *player = libvlc_media_player_new(vlc);
+    assert(player != NULL);
+    libvlc_event_manager_t *mgr = libvlc_media_player_event_manager(player);
+    assert(mgr != NULL);
+
+    libvlc_media_t *media = libvlc_media_new_callbacks(
+            AccessOpen,
+            AccessReadBlocking,
+            NULL,//AccessSeek,
+            AccessClose,
+            imem);
+    assert(media != NULL);
+
+    struct mp_event_ctx wait_play;
+    struct mp_event_ctx wait_stopped;
+    mp_event_ctx_init(&wait_play);
+    mp_event_ctx_init(&wait_stopped);
+    libvlc_event_attach(mgr, libvlc_MediaPlayerMediaStopping, UnblockRead, imem);
+
+    fprintf(stderr, "test: set initial media\n");
+    libvlc_media_player_set_media(player, media);
+    libvlc_media_player_play(player);
+
+    /* We want to be sure that the media has been opened. */
+    vlc_sem_wait(&imem->opened);
+    vlc_sem_wait(&imem->read_blocking);
+
+    libvlc_media_release(media);
+    media = libvlc_media_new_callbacks(
+            AccessOpen,
+            AccessReadBlocking,
+            NULL,
+            AccessClose,
+            imem);
+    assert(media != NULL);
+    fprintf(stderr, "test: changing to new media\n");
+    libvlc_media_player_set_media(player, media);
+    fprintf(stderr, "test: waiting for the new media\n");
+
+    /* Semaphore notifying that the first access has been closed. */
+    vlc_sem_wait(&imem->done);
+    assert(vlc_sem_trywait(&imem->read_blocking) == EAGAIN);
+
+    vlc_sem_wait(&imem->opened);
+    vlc_sem_wait(&imem->read_blocking);
+
+    fprintf(stderr, "test: checking that we get the media stopping event\n");
+    libvlc_media_player_stop_async(player);
+
+    vlc_sem_wait(&imem->done);
+
+    libvlc_media_release(media);
+    libvlc_media_player_release(player);
+
+    free(imem);
+}
+
+int main( int argc, char **argv )
+{
+    (void)argc; (void)argv;
+    test_init();
+
+    const char * const vlc_argv[] = {
+        "-vvv", "--vout=dummy", "--aout=dummy", "--text-renderer=dummy",
+        "--demux="MODULE_STRING,
+    };
+
+    libvlc_instance_t *vlc = libvlc_new(ARRAY_SIZE(vlc_argv), vlc_argv);
+    assert(vlc != NULL);
+
+    test_media_callback(vlc);
+    test_media_callback_interrupt(vlc);
+
+    libvlc_release(vlc);
+    return 0;
+}
+
+static int DemuxDemux(demux_t *demux)
+{
+    (void)demux;
+    return VLC_SUCCESS;
+}
+
+static void DemuxClose(vlc_object_t *obj)
+{
+    (void)obj;
+}
+
+static int DemuxOpen(vlc_object_t *obj)
+{
+    demux_t *demux = (demux_t *)obj;
+
+    static const struct vlc_stream_operations ops =
+    {
+        .demux.demux = DemuxDemux,
+    };
+    demux->ops = &ops;
+
+    return VLC_SUCCESS;
+}
+
+vlc_module_begin()
+    set_capability("demux", 0)
+    set_callbacks(DemuxOpen, DemuxClose)
+vlc_module_end()
+
+VLC_EXPORT const vlc_plugin_cb vlc_static_modules[] = {
+    VLC_SYMBOL(vlc_entry),
+    NULL
+};


=====================================
test/libvlc/meson.build
=====================================
@@ -20,6 +20,13 @@ vlc_tests += {
     'module_depends': vlc_plugins_targets.keys()
 }
 
+vlc_tests += {
+    'name' : 'test_libvlc_media_callback',
+    'sources' : files('media_callback.c'),
+    'link_with' : [libvlc, libvlccore],
+    'module_depends': ['imem'],
+}
+
 test_libvlc_media_thumbnail_modules = ['demux_mock']
 if avcodec_dep.found()
     test_libvlc_media_thumbnail_modules += ['avcodec']


=====================================
test/src/player/player.c
=====================================
@@ -36,6 +36,19 @@
 # define TELETEXT_DECODER ""
 #endif
 
+static const char *state_to_string(enum vlc_player_state state)
+{
+    switch (state)
+    {
+        case VLC_PLAYER_STATE_STOPPED:      return "stopped";
+        case VLC_PLAYER_STATE_STARTED:      return "started";
+        case VLC_PLAYER_STATE_PLAYING:      return "playing";
+        case VLC_PLAYER_STATE_PAUSED:       return "paused";
+        case VLC_PLAYER_STATE_STOPPING:     return "stopping";
+    }
+    vlc_assert_unreachable();
+};
+
 struct report_capabilities
 {
     int old_caps;
@@ -598,13 +611,38 @@ wait_state(struct ctx *ctx, enum vlc_player_state state)
     assert(VEC_LAST(vec) == state); \
 } while(0)
 
+static inline void state_list_dump(vec_on_state_changed *vec)
+{
+    fprintf(stderr, "Dumping state:\n");
+    for (size_t i = 0; i < vec->size; ++i)
+        fprintf(stderr, "state[%zu] = %s\n",
+                i, state_to_string(vec->data[i]));
+}
+
+static inline bool
+state_equal(vec_on_state_changed *vec, int location, enum vlc_player_state state)
+{
+    assert(location < (int)vec->size && -location < (int)(vec->size + 1));
+    size_t index = location < 0 ? vec->size + location : (size_t)location;
+    const char *str_state_vec = state_to_string(vec->data[index]);
+    const char *str_state_check = state_to_string(state);
+
+    fprintf(stderr, "Checking state[%d] == '%s', is '%s'\n",
+            location, str_state_vec, str_state_check);
+    return vec->data[index] == state;
+}
+
+#define state_equal(ctx, index, state) \
+    (state_equal)(&ctx->report.on_state_changed, index, state)
+
 #define assert_normal_state(ctx) do { \
     vec_on_state_changed *vec = &ctx->report.on_state_changed; \
+    state_list_dump(vec); \
     assert(vec->size >= 4); \
-    assert(vec->data[vec->size - 4] == VLC_PLAYER_STATE_STARTED); \
-    assert(vec->data[vec->size - 3] == VLC_PLAYER_STATE_PLAYING); \
-    assert(vec->data[vec->size - 2] == VLC_PLAYER_STATE_STOPPING); \
-    assert(vec->data[vec->size - 1] == VLC_PLAYER_STATE_STOPPED); \
+    assert(state_equal(ctx, -4, VLC_PLAYER_STATE_STARTED)); \
+    assert(state_equal(ctx, -3, VLC_PLAYER_STATE_PLAYING)); \
+    assert(state_equal(ctx, -2, VLC_PLAYER_STATE_STOPPING)); \
+    assert(state_equal(ctx, -1, VLC_PLAYER_STATE_STOPPED)); \
 } while(0)
 
 static void



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/764317d421e57469977c1d40147b481344b0bac3...30d1237d76398c98b59976f422de5ef5508d45d6

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/764317d421e57469977c1d40147b481344b0bac3...30d1237d76398c98b59976f422de5ef5508d45d6
You're receiving this email because of your account on code.videolan.org.


VideoLAN code repository instance


More information about the vlc-commits mailing list