[vlc-commits] [Git][videolan/libvlcpp][master] Parser: adapt to the libvlc_parser split

Alaric Senat (@asenat) gitlab at videolan.org
Thu Sep 24 08:26:41 UTC 2026



Alaric Senat pushed to branch master at VideoLAN / libvlcpp


Commits:
3095992b by Ayush Dey at 2026-09-23T18:18:44+05:30
Parser: adapt to the libvlc_parser split

Refs vlc!10169

Replace queue() and queueThumbnailing() with createParseTask(),
createThumbnailTask() and submit().

The create methods return a move-only Task object, which submit()
consumes on success. Submitting it twice or to another parser throws
to avoid undefined behavior. Task::id() returns a TaskIdentifier,
fetched before submission to identify it in callbacks, which now
exposes the same TaskIdentifier for that task.

Bump the libvlc wrap to 778ec071ff216602f2a6e4ddd522dfdb6b791472.

- - - - -


5 changed files:

- meson.build
- subprojects/libvlc.wrap
- test/Parser/parse.cpp
- test/Parser/thumbnail.cpp
- vlcpp/Parser.hpp


Changes:

=====================================
meson.build
=====================================
@@ -5,7 +5,7 @@ project(
     ['cpp'],
     license: 'LGPL-2.1-or-later',
     license_files: ['COPYING'],
-    version: '4.0.0+vlc.266f1fe08a',
+    version: '4.0.0+vlc.778ec071ff',
     meson_version: '>=1.1.0',
     default_options: ['cpp_std=c++11'],
 )


=====================================
subprojects/libvlc.wrap
=====================================
@@ -2,8 +2,8 @@
 url = https://code.videolan.org/videolan/vlc.git
 depth = 1
 
-# Jun 28 2026
-revision = 266f1fe08a8bce490c51d830262f0b965b12c698
+# Sep 16 2026
+revision = 778ec071ff216602f2a6e4ddd522dfdb6b791472
 
 [provide]
 libvlc = libvlc_dep


=====================================
test/Parser/parse.cpp
=====================================
@@ -1,5 +1,5 @@
 /*****************************************************************************
- * parse.cpp: Parser::queue regression test
+ * parse.cpp: Parser parse task regression test
  *****************************************************************************
  * Copyright © 2026 libvlcpp authors & VideoLAN
  *
@@ -27,6 +27,7 @@
 #include <memory>
 #include <mutex>
 #include <iostream>
+#include <utility>
 
 int main(int ac, char** av)
 {
@@ -51,25 +52,28 @@ int main(int ac, char** av)
     std::atomic<int64_t> reportedDuration{-1};
     std::mutex stateMutex;
     std::condition_variable stateCv;
-    std::unique_ptr<VLC::Parser::TaskIdentifier> queuedTaskId;
+    VLC::Parser::TaskIdentifier expectedId;
 
-    VLC::Parser::Callbacks cbs([&](VLC::Parser::Task&& task, VLC::Parser::Status status) {
+    VLC::Parser::Callbacks cbs([&](VLC::Parser::TaskIdentifier task, VLC::Parser::Status status) {
         std::lock_guard<std::mutex> lk(stateMutex);
-        assert(*queuedTaskId == task);
-        auto media = task.getMedia();
-        assert(media.isValid());
+        assert(task == expectedId);
         reportedDuration.store(media.duration().count());
         parserStatus = status;
         parsingFinished = true;
         stateCv.notify_all();
     });
 
-    /* block until the parsing is done. Hold stateMutex across queue() so the
-       callback (which also locks stateMutex) cannot observe queuedTaskId
-       before we assign it */
+    /* the task identifier is fetched before submission, so the callback can
+       safely compare against it even if it fires before submit() returns */
+    auto task = parser.createParseTask(req, cbs);
+    expectedId = task.id();
+    auto submitted = parser.submit(std::move(task));
+    assert(submitted.id() == expectedId);
+    assert(submitted.getMedia() == media);
+
+    /* block until the parsing is done */
     {
         std::unique_lock<std::mutex> lk(stateMutex);
-        queuedTaskId.reset(new VLC::Parser::TaskIdentifier(parser.queue(req, cbs)));
         assert(stateCv.wait_for(lk, std::chrono::seconds(5),
                                 [&] { return parsingFinished; }));
     }


=====================================
test/Parser/thumbnail.cpp
=====================================
@@ -1,5 +1,5 @@
 /*****************************************************************************
- * thumbnail.cpp: Parser::queueThumbnailing regression test
+ * thumbnail.cpp: Parser thumbnail task regression test
  *****************************************************************************
  * Copyright © 2026 libvlcpp authors & VideoLAN
  *
@@ -53,7 +53,7 @@ int main(int ac, char** av)
     bool pictureReceived = false;
 
     VLC::Parser::ThumbnailerCallbacks thumbCbs(
-        [&](VLC::Parser::Task&& task, const VLC::Picture& picture) {
+        [&](VLC::Parser::TaskIdentifier, const VLC::Picture& picture) {
             assert(picture.isValid());
             std::lock_guard<std::mutex> lk(mtx);
             captured = picture;
@@ -62,7 +62,7 @@ int main(int ac, char** av)
         }
     );
 
-    parser.queueThumbnailing(thumbReq, thumbCbs);
+    parser.submit(parser.createThumbnailTask(thumbReq, thumbCbs));
 
     /* block until the thumbnail is received and verify some of its properties,
        then save it to a file and verify the file is non-empty before cleaning up */


=====================================
vlcpp/Parser.hpp
=====================================
@@ -42,79 +42,150 @@ public:
         Done = libvlc_parser_status_done,
     };
 
-    class Task : public Internal<libvlc_parser_task>
+    class Task;
+    class SubmittedTask;
+
+    /**
+     * Identifier of a parsing/thumbnailing task.
+     *
+     * Returned by Task::id() and SubmittedTask::id(), and passed to the parser
+     * and thumbnailer callbacks, to identify which task a callback refers to.
+     * It is a plain value that can be copied and compared with other identifiers.
+     *
+     * \warning An identifier is only unique while the task it identifies is
+     * alive, i.e. while a Task or SubmittedTask referring to it exists, or while
+     * one of its callbacks runs. Once the task is released, a new task may get
+     * the same identifier.
+     *
+     * \note A default constructed identifier doesn't identify any task. It is also
+     * returned by a Task that was submitted or moved from.
+     */
+    class TaskIdentifier
     {
     private:
-        explicit Task( libvlc_parser_task *task ) : Internal( task, libvlc_parser_task_release )
+        std::uintptr_t m_id = 0;
+
+        explicit TaskIdentifier( libvlc_parser_task *task )
+            : m_id( reinterpret_cast<std::uintptr_t>( task ) )
         {
         }
 
     public:
-        /**
-         * Fetch the media associated with the task handle.
-         *
-         * \return the media associated with the task
-         */
-        Media getMedia() const
+        TaskIdentifier() = default;
+
+        bool operator==( const TaskIdentifier& another ) const
         {
-            auto media = libvlc_parser_task_get_media( *this );
-            return Media( media, true );
+            return m_id == another.m_id;
         }
 
+        friend class Task;
+        friend class SubmittedTask;
         template <size_t, typename ...>
         friend struct CallbackWrapper;
     };
 
-    class TaskIdentifier
+    /**
+     * Owning handle of a submitted parsing/thumbnailing task.
+     *
+     * Returned by Parser::submit(). It can be used to cancel the task and to
+     * fetch its media. The underlying task is released when the last copy of
+     * this object is destroyed. Releasing it while the task is running is
+     * safe, libVLC keeps the task alive until its completion callback returns.
+     */
+    class SubmittedTask : public Internal<libvlc_parser_task>
     {
     private:
-        libvlc_parser_task *m_task;
-
-        explicit TaskIdentifier( libvlc_parser_task *task )
-            : m_task( task )
+        explicit SubmittedTask( Pointer task )
         {
+            m_obj = std::move( task );
         }
 
     public:
-        friend bool operator==( const TaskIdentifier& id, const Task& task )
+        /**
+         * Get the identifier of the task.
+         *
+         * \return the identifier of the task, the same one returned by the
+         * Task it was submitted from and passed to its callbacks, or a
+         * default constructed identifier if this handle is empty
+         */
+        TaskIdentifier id() const
         {
-            return id.m_task == task.get();
+            return TaskIdentifier( get() );
         }
 
-        friend bool operator==( const Task& task, const TaskIdentifier& id )
+        /**
+         * Fetch the media associated with the task handle.
+         *
+         * \return the media associated with the task, or an empty Media if
+         * this handle is empty
+         */
+        Media getMedia() const
         {
-            return id == task;
+            if ( !isValid() )
+                return Media();
+            auto media = libvlc_parser_task_get_media( *this );
+            return Media( media, true );
         }
 
-        bool operator==(const TaskIdentifier& another) const
+        friend class Parser;
+    };
+
+    /**
+     * A created task that has not been submitted yet.
+     *
+     * Returned by Parser::createParseTask() and Parser::createThumbnailTask(),
+     * and started by passing it to Parser::submit(), which consumes it on
+     * success. It can't be copied, so a task that was submitted successfully
+     * can't be submitted again. Destroying it without submitting it releases
+     * the task, and no callback is invoked for it.
+     */
+    class Task
+    {
+    private:
+        /* Identifies the parser that created the task without owning it.
+           It doesn't keep the parser that created it alive. If that parser is
+           destroyed first, the task can no longer be submitted, only released. */
+        std::weak_ptr<libvlc_parser_t> m_parser;
+        std::shared_ptr<libvlc_parser_task> m_task;
+
+        Task( const std::shared_ptr<libvlc_parser_t>& parser, libvlc_parser_task *task )
+            : m_parser( parser )
+            , m_task( task, libvlc_parser_task_release )
         {
-            return m_task == another.m_task;
         }
 
+    public:
+        Task( Task&& ) = default;
+        Task& operator=( Task&& ) = default;
+        Task( const Task& ) = delete;
+        Task& operator=( const Task& ) = delete;
+
         /**
-         * Fetch the media associated with the task handle.
+         * Get the identifier of the task.
          *
-         * \return the media associated with the task
+         * The completion callback may run before Parser::submit() returns,
+         * so fetch the identifier before submitting the task to identify it
+         * in its callbacks.
+         *
+         * \return the identifier of the task, or a default constructed
+         * identifier if this object was submitted or moved from
          */
-        Media getMedia() const
+        TaskIdentifier id() const
         {
-            auto media = libvlc_parser_task_get_media( m_task );
-            return Media( media, true );
+            return TaskIdentifier( m_task.get() );
         }
 
         friend class Parser;
-        template <size_t, typename ...>
-        friend struct CallbackWrapper;
     };
 
     /**
      * Callback prototype that notifies when a parser request finishes
      *
-     * \param task opaque handle of the task that finished, can be compared to the TaskIdentifier
-     * returned by Parser::queue() using operator==, to identify which task finished
+     * \param task identifier of the task that finished, can be compared with the one returned by
+     * Task::id() and SubmittedTask::id(), to identify which task finished
      * \param status terminal parse outcome, \ref Parser::Status
      */
-    using ExpectedOnParsedCb = void(Parser::Task&& task, Parser::Status status);
+    using ExpectedOnParsedCb = void(Parser::TaskIdentifier task, Parser::Status status);
 
     /**
      * Callback prototype that notify when the parser add new attachments to
@@ -122,8 +193,8 @@ public:
      *
      * Called before onParsed, if there are valid attachments.
      *
-     * \param task opaque handle of the task, can be compared to the TaskIdentifier
-     * returned by Parser::queue() using operator==, to identify which task added attachments
+     * \param task identifier of the task, can be compared with the one returned by
+     * Task::id() and SubmittedTask::id(), to identify which task added attachments
      * \param list list of pictures, the list is only valid from this
      * callback, each pictures can be held separately with list.at(index) method
      */
@@ -132,14 +203,14 @@ public:
     /**
      * Callback prototype that notify when a thumbnailer request finishes
      *
-     * \param task opaque handle of the task that finished, can be compared to the TaskIdentifier
-     * returned by Parser::queueThumbnailing() using operator==, to identify which task finished
+     * \param task identifier of the task that finished, can be compared with the one returned by
+     * Task::id() and SubmittedTask::id(), to identify which task finished
      * \param picture generated thumbnail, the thumbnail is only valid for the duration
      * of the callback, but can be safely copied if needed. It is an empty Picture object in case
      * of an error, timeout or request was cancelled. User should check if the Picture is valid by
      * calling picture.isValid()
      */
-    using ExpectedOnThumbnailerEndedCb = void(Parser::Task&& task, const Picture& picture);
+    using ExpectedOnThumbnailerEndedCb = void(Parser::TaskIdentifier task, const Picture& picture);
 
     enum class ParseFlags
     {
@@ -239,7 +310,7 @@ public:
          *
          * \param media the media to parse
          *
-         * \warning The media object must remain valid until the parser request is queued.
+         * \warning The media object must remain valid until the parser task is created.
          */
         Request( Media& media )
         {
@@ -294,7 +365,8 @@ public:
             m_cbs = {};
             m_cbs.version = 0;
             m_cbs.on_parsed = CallbackWrapper<(unsigned int)CallbackIdx::OnParsed,
-                              decltype(libvlc_parser_cbs::on_parsed)>::wrap<Parser::Task, Parser::Status>(
+                              decltype(libvlc_parser_cbs::on_parsed)>::wrap<
+                              Parser::TaskIdentifier, Parser::Status>(
                               *m_callbacks, std::forward<OnParsedCb>( onParsedCb ) );
         }
 
@@ -329,7 +401,7 @@ public:
          *
          * \param media the media for which to generate thumbnails
          *
-         * \warning The media object must remain valid until the thumnailer request is queued.
+         * \warning The media object must remain valid until the thumbnailer task is created.
          */
         ThumbnailerRequest( Media& media )
         {
@@ -455,7 +527,8 @@ public:
             m_cbs = {};
             m_cbs.version = 0;
             m_cbs.on_ended = CallbackWrapper<(unsigned int)CallbackIdx::OnThumbnailerEnded,
-                             decltype(libvlc_thumbnailer_cbs::on_ended)>::wrap<Parser::Task, Picture>(
+                             decltype(libvlc_thumbnailer_cbs::on_ended)>::wrap<
+                             Parser::TaskIdentifier, Picture>(
                              *m_callbacks, std::forward<OnThumbnailerEnded>( onThumbnailerEnded ) );
         }
     };
@@ -476,74 +549,122 @@ public:
     }
 
     /**
-     * Queue a parsing request.
+     * Create a media parsing task.
+     *
+     * Nothing runs and no callback can fire until the returned Task is
+     * passed to submit().
      *
      * \param request the parsing request
      * \param cbs pre-built \ref Parser::Callbacks object
-     * \return the queued task \ref TaskIdentifier that can be used to identify the
-     * task in callbacks and to cancel the task if needed
+     * \return the created \ref Task, to pass to submit(). Its
+     * Task::id() identifies the task in callbacks.
      *
      * \warning The application must ensure that the Callbacks object supplied
-     * remains valid and unmodified until the parser request terminates and the onParsedCb callback is called
-     * on that Task (the returned TaskIdentifier can be used to identify the Task in the onParsedCb
-     * using ==).
-     *
-     * \warning The returned TaskIdentifier is only valid till the parser request finishes
-     * and the onParsedCb callback is called, after that it should not be used anymore
-     * as the underlying task object is released by libVLC and the TaskIdentifier will hold a
-     * dangling pointer.
+     * remains valid and unmodified until the onParsedCb callback is called for
+     * the submitted task.
      */
-    TaskIdentifier queue( const Request& request, const Callbacks& cbs )
+    Task createParseTask( const Request& request, const Callbacks& cbs )
     {
-        auto task = libvlc_parser_queue( *this, &request.m_req, &cbs.m_cbs, cbs.m_callbacks.get() );
+        auto task = libvlc_parser_task_new_parse( *this, &request.m_req, &cbs.m_cbs, cbs.m_callbacks.get() );
         if ( task == nullptr )
-            throw std::runtime_error( "Failed to queue parser task" );
-        return TaskIdentifier( task );
+            throw std::runtime_error( "Failed to create parser task" );
+        return Task( m_obj, task );
     }
 
     /**
-     * Queue a thumbnail generation request.
+     * Create a thumbnail generation task.
+     *
+     * Nothing runs and no callback can fire until the returned Task is
+     * passed to submit().
      *
      * \param request the thumbnail generation request
      * \param cbs pre-built \ref Parser::ThumbnailerCallbacks object
-     * \return the queued task \ref TaskIdentifier that can be used to identify the
-     * task in callbacks and to cancel the task if needed
+     * \return the created \ref Task, to pass to submit(). Its
+     * Task::id() identifies the task in callbacks.
      *
      * \warning The application must ensure that the ThumbnailerCallbacks object supplied
-     * remains valid and unmodified until the request terminates and the onThumbnailerEnded callback is called
-     * on that Task (the returned TaskIdentifier can be used to identify the Task in the onThumbnailerEnded
-     * callback using ==).
-     *
-     * \warning The returned TaskIdentifier is only valid till the thumbnailer request finishes
-     * and the onThumbnailerEnded callback is called, after that it should not be used anymore
-     * as the underlying task object is released by libVLC and the TaskIdentifier will hold a dangling pointer.
+     * remains valid and unmodified until the onThumbnailerEnded callback is called for
+     * the submitted task.
      */
-    TaskIdentifier queueThumbnailing( const ThumbnailerRequest& request, const ThumbnailerCallbacks& cbs )
+    Task createThumbnailTask( const ThumbnailerRequest& request,
+                                     const ThumbnailerCallbacks& cbs )
     {
-        auto task = libvlc_parser_queue_thumbnailing( *this, &request.m_req, &cbs.m_cbs, cbs.m_callbacks.get() );
+        auto task = libvlc_parser_task_new_thumbnail( *this, &request.m_req, &cbs.m_cbs,
+                                                      cbs.m_callbacks.get() );
         if ( task == nullptr )
-            throw std::runtime_error( "Failed to queue thumbnailer task" );
-        return TaskIdentifier( task );
+            throw std::runtime_error( "Failed to create thumbnailer task" );
+        return Task( m_obj, task );
+    }
+
+    /**
+     * Start a task created by createParseTask() or createThumbnailTask().
+     *
+     * On success the task is scheduled and its completion callback is guaranteed
+     * to be called exactly once, including when the task is cancelled. That
+     * callback may run, on another thread, even before this function returns:
+     * fetch Task::id() before submitting to identify the task in its
+     * callbacks.
+     *
+     * \param task a task created by this parser that has not been submitted yet
+     * \return the \ref SubmittedTask, which can be used to cancel the task
+     *
+     * \throws std::logic_error if the task was already submitted
+     * \throws std::invalid_argument if the task was created by another parser
+     * \throws std::runtime_error if the task could not be submitted
+     *
+     * \note On success \p task is consumed and left empty. On error no
+     * callback is invoked, \p task is left untouched and may be submitted again.
+     */
+    SubmittedTask submit( Task&& task )
+    {
+        if ( task.m_task == nullptr )
+            throw std::logic_error( "Parser task was already submitted" );
+
+        /* Ensures whether the task was created by this parser.
+           Comparing libvlc_parser_t addresses isn't enough. Once the task's parser
+           is destroyed, a new parser can reuse the same address, and the stale task
+           would be submitted to a parser that didn't create it (UB in libVLC).
+           owner_before() compares shared_ptr control blocks instead. It ensures that
+           the task was created by this parser and rejects every other parser,
+           even if it reuses the same address. */
+        if ( m_obj.owner_before( task.m_parser ) || task.m_parser.owner_before( m_obj ) )
+            throw std::invalid_argument( "Parser task was created by another parser" );
+        if ( libvlc_parser_submit( *this, task.m_task.get() ) != 0 )
+            throw std::runtime_error( "Failed to submit parser task" );
+
+        return SubmittedTask( std::move( task.m_task ) );
     }
 
     /**
      * Cancel a parser request.
      *
-     * \param task the task to cancel \ref TaskIdentifier
+     * If the task already terminated, this is a no-op and no callback is
+     * invoked. An empty Task cancels nothing.
+     *
+     * \param task the task to cancel \ref SubmittedTask
      * \return the number of cancelled tasks
      *
-     * \warning The TaskIdentifier supplied must be valid and not expired,
-     * otherwise the behavior is undefined.
+     * \warning The completion callback of the cancelled task may be invoked
+     * synchronously from this call, on the calling thread. Don't hold a lock
+     * that the callback also takes while calling this function.
      */
-    size_t cancelRequest( TaskIdentifier& task )
+    size_t cancelRequest( const SubmittedTask& task )
     {
-        return libvlc_parser_cancel_request( *this, task.m_task );
+        /* libvlc cancels all tasks when given a NULL, so check
+           it early to avoid passing nullptr from an empty Task */
+        if ( !task.isValid() )
+            return 0;
+        return libvlc_parser_cancel_request( *this, task );
     }
 
     /**
      * Cancel all parser requests.
      *
      * \return the number of cancelled tasks
+     *
+     * \warning The completion callbacks of the cancelled tasks may be invoked
+     * synchronously from this call, on the calling thread. Don't hold a lock
+     * that the callbacks also take while calling this function.
      */
     size_t cancelAll()
     {



View it on GitLab: https://code.videolan.org/videolan/libvlcpp/-/commit/3095992b779ef7ee4487f39f936f8ab3750cb23a

-- 
View it on GitLab: https://code.videolan.org/videolan/libvlcpp/-/commit/3095992b779ef7ee4487f39f936f8ab3750cb23a
You're receiving this email because of your account on code.videolan.org. Manage all notifications: https://code.videolan.org/-/profile/notifications | Help: https://code.videolan.org/help




More information about the vlc-commits mailing list