[vlc-commits] [Git][videolan/vlc][master] 2 commits: lib: media_player: add a detach_window callback
Steve Lhomme (@robUx4)
gitlab at videolan.org
Tue Jan 27 17:54:23 UTC 2026
Steve Lhomme pushed to branch master at VideoLAN / VLC
Commits:
19cae7e5 by Alexandre Janniaux at 2026-01-27T17:39:39+00:00
lib: media_player: add a detach_window callback
The callback allows setting back the original values before adding a new
configuration for the video output (either window provider functions or
output and video callbacks). There are some duplications between
variable set by the callback to reset back to the old values and
potential variable set by the new callback that would have overriden
those, but it allows having a centralized callback instead of ensuring
every configuration has been removed from everywhere and maintain this
kind of list at every setter location.
This effectively allows setting vout, dec-dev and OpenGL implementations
from the libvlc configuration.
- - - - -
0c81c9ae by Alexandre Janniaux at 2026-01-27T17:39:39+00:00
test: libvlc: cover vout detach restore scenario
Check that the internal state is correctly preserved and restored when
switching video callbacks.
- - - - -
5 changed files:
- lib/media_player.c
- lib/media_player_internal.h
- test/Makefile.am
- test/libvlc/meson.build
- + test/libvlc/video_callback.c
Changes:
=====================================
lib/media_player.c
=====================================
@@ -1,7 +1,9 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
/*****************************************************************************
* media_player.c: Libvlc API Media Instance management functions
*****************************************************************************
* Copyright (C) 2005-2015 VLC authors and VideoLAN
+ * Copyright (C) 2020-2026 Alexandre Janniaux <ajanni at videolabs.io>
*
* Authors: Clément Stenac <zorglub at videolan.org>
*
@@ -673,6 +675,12 @@ libvlc_media_player_new( libvlc_instance_t *instance )
var_Create( mp, "vout-cb-select-plane", VLC_VAR_ADDRESS );
var_Create (mp, "dec-dev", VLC_VAR_STRING);
+
+ mp->vout.default_gl = var_GetString(mp, "gl");
+ mp->vout.default_gles2 = var_GetString(mp, "gles2");
+ mp->vout.default_vout = var_GetString(mp, "vout");
+ mp->vout.default_dec_dev = var_GetString(mp, "dec-dev");
+
var_Create (mp, "drawable-xid", VLC_VAR_INTEGER);
#if defined (_WIN32) || defined (__OS2__)
var_Create (mp, "drawable-hwnd", VLC_VAR_INTEGER);
@@ -877,6 +885,11 @@ static void libvlc_media_player_destroy( libvlc_media_player_t *p_mi )
libvlc_event_manager_destroy(&p_mi->event_manager);
libvlc_media_release( p_mi->p_md );
+ free(p_mi->vout.default_dec_dev);
+ free(p_mi->vout.default_vout);
+ free(p_mi->vout.default_gl);
+ free(p_mi->vout.default_gles2);
+
libvlc_instance_t *instance = p_mi->p_libvlc_instance;
vlc_object_delete(p_mi);
libvlc_release(instance);
@@ -1070,12 +1083,32 @@ int libvlc_media_player_set_renderer( libvlc_media_player_t *p_mi,
return 0;
}
+static void libvlc_media_player_switch_vout(libvlc_media_player_t *player,
+ libvlc_media_player_vout_detach_cb vout_detach)
+{
+ if (player->vout.window_detach != NULL)
+ player->vout.window_detach(player);
+ player->vout.window_detach = vout_detach;
+}
+
+static void libvlc_media_player_detach_video_callbacks(libvlc_media_player_t *player)
+{
+ var_SetAddress(player, "vmem-lock", NULL);
+ var_SetAddress(player, "vmem-unlock", NULL);
+ var_SetAddress(player, "vmem-display", NULL);
+ var_SetAddress(player, "vmem-data", NULL);
+ var_SetString(player, "dec-dev", player->vout.default_dec_dev);
+ var_SetString(player, "vout", player->vout.default_vout);
+ var_SetString(player, "window", "any");
+}
+
void libvlc_video_set_callbacks( libvlc_media_player_t *mp,
void *(*lock_cb) (void *, void **),
void (*unlock_cb) (void *, void *, void *const *),
void (*display_cb) (void *, void *),
void *opaque )
{
+ libvlc_media_player_switch_vout(mp, libvlc_media_player_detach_video_callbacks);
var_SetAddress( mp, "vmem-lock", lock_cb );
var_SetAddress( mp, "vmem-unlock", unlock_cb );
var_SetAddress( mp, "vmem-display", display_cb );
@@ -1102,6 +1135,27 @@ void libvlc_video_set_format( libvlc_media_player_t *mp, const char *chroma,
var_SetInteger( mp, "vmem-pitch", pitch );
}
+static void libvlc_media_player_detach_output_callbacks(libvlc_media_player_t *player)
+{
+ var_SetString(player, "dec-dev", player->vout.default_dec_dev);
+ var_SetString(player, "vout", player->vout.default_vout);
+ var_SetString(player, "gl", player->vout.default_gl);
+ var_SetString(player, "gles", player->vout.default_gles2);
+ var_SetString(player, "window", "any");
+
+ var_SetInteger(player, "vout-cb-type", libvlc_video_engine_disable);
+ var_SetAddress(player, "vout-cb-opaque", NULL);
+ var_SetAddress(player, "vout-cb-setup", NULL);
+ var_SetAddress(player, "vout-cb-cleanup", NULL);
+ var_SetAddress(player, "vout-cb-window-cb", NULL);
+ var_SetAddress(player, "vout-cb-update-output", NULL);
+ var_SetAddress(player, "vout-cb-swap", NULL);
+ var_SetAddress(player, "vout-cb-get-proc-address", NULL);
+ var_SetAddress(player, "vout-cb-make-current", NULL);
+ var_SetAddress(player, "vout-cb-metadata", NULL);
+ var_SetAddress(player, "vout-cb-select-plane", NULL);
+}
+
bool libvlc_video_set_output_callbacks(libvlc_media_player_t *mp,
libvlc_video_engine_t engine,
libvlc_video_output_setup_cb setup_cb,
@@ -1116,41 +1170,47 @@ bool libvlc_video_set_output_callbacks(libvlc_media_player_t *mp,
void *opaque)
{
static_assert(libvlc_video_engine_disable == 0, "No engine set must default to 0");
- var_SetString( mp, "window", "wextern");
- if( engine == libvlc_video_engine_gles2 )
+
+ if (engine == libvlc_video_engine_disable)
{
- var_SetString ( mp, "vout", "gles2" );
- var_SetString ( mp, "gles2", "vgl" );
+ libvlc_media_player_switch_vout(mp, NULL);
+ return true;
}
- else if( engine == libvlc_video_engine_opengl )
+
+ var_SetString( mp, "window", "wextern");
+
+ if (engine == libvlc_video_engine_gles2)
{
- var_SetString ( mp, "vout", "gl" );
- var_SetString ( mp, "gl", "vgl");
+ var_SetString(mp, "vout", "gles2");
+ var_SetString(mp, "gles2", "vgl");
+ libvlc_media_player_switch_vout(mp, libvlc_media_player_detach_output_callbacks);
}
- else if ( engine == libvlc_video_engine_d3d11 )
+ else if (engine == libvlc_video_engine_opengl)
{
- var_SetString ( mp, "vout", "d3d11drawable" );
- var_SetString ( mp, "dec-dev", "d3d11" );
+ var_SetString(mp, "vout", "gl");
+ var_SetString(mp, "gl", "vgl");
+ libvlc_media_player_switch_vout(mp, libvlc_media_player_detach_output_callbacks);
}
- else if ( engine == libvlc_video_engine_d3d9 )
+ else if (engine == libvlc_video_engine_d3d11)
{
- var_SetString ( mp, "vout", "direct3d9" );
- var_SetString ( mp, "dec-dev", "d3d9" );
+ var_SetString(mp, "vout", "d3d11drawable");
+ var_SetString(mp, "dec-dev", "d3d11");
+ libvlc_media_player_switch_vout(mp, libvlc_media_player_detach_output_callbacks);
}
- else if ( engine == libvlc_video_engine_anw )
+ else if (engine == libvlc_video_engine_d3d9)
{
- /* Force android-display is using MediaCodec or fallback to GL (any) */
- var_SetString ( mp, "vout", "android-display,any" );
- var_SetString ( mp, "dec-dev", "android" );
- var_SetString( mp, "window", "android");
+ var_SetString(mp, "vout", "direct3d9");
+ var_SetString(mp, "dec-dev", "d3d9");
+ libvlc_media_player_switch_vout(mp, libvlc_media_player_detach_output_callbacks);
}
- else if ( engine == libvlc_video_engine_disable )
+ else if (engine == libvlc_video_engine_anw)
{
- // use the default display module
- var_SetString ( mp, "vout", "any" );
- // use the default window
- var_SetString( mp, "window", "any" );
+ /* Force android-display is using MediaCodec or fallback to GL (any) */
+ var_SetString(mp, "vout", "android-display,any");
+ var_SetString(mp, "dec-dev", "android");
+ var_SetString(mp, "window", "android");
+ libvlc_media_player_switch_vout(mp, libvlc_media_player_detach_output_callbacks);
}
else
return false;
@@ -1169,6 +1229,11 @@ bool libvlc_video_set_output_callbacks(libvlc_media_player_t *mp,
return true;
}
+static void libvlc_media_player_detach_nsobject(libvlc_media_player_t *player)
+{
+ var_SetAddress(player, "drawable-nsobject", NULL);
+}
+
/**************************************************************************
* set_nsobject
**************************************************************************/
@@ -1176,10 +1241,9 @@ void libvlc_media_player_set_nsobject( libvlc_media_player_t *p_mi,
void * drawable )
{
assert (p_mi != NULL);
+ libvlc_media_player_switch_vout(p_mi, libvlc_media_player_detach_nsobject);
+
#ifdef __APPLE__
- var_SetString (p_mi, "dec-dev", "any");
- var_SetString (p_mi, "vout", "any");
- var_SetString (p_mi, "window", "any");
var_SetAddress (p_mi, "drawable-nsobject", drawable);
#else
(void)drawable;
@@ -1203,6 +1267,13 @@ void * libvlc_media_player_get_nsobject( libvlc_media_player_t *p_mi )
#endif
}
+static void libvlc_media_player_detach_xwindow(libvlc_media_player_t *player)
+{
+ /* Window variable is not configurable from libvlc */
+ var_SetString(player, "window", "any");
+ var_SetInteger(player, "drawable-xid", 0);
+}
+
/**************************************************************************
* set_xwindow
**************************************************************************/
@@ -1210,9 +1281,8 @@ void libvlc_media_player_set_xwindow( libvlc_media_player_t *p_mi,
uint32_t drawable )
{
assert (p_mi != NULL);
+ libvlc_media_player_switch_vout(p_mi, libvlc_media_player_detach_xwindow);
- var_SetString (p_mi, "dec-dev", "any");
- var_SetString (p_mi, "vout", "any");
var_SetString (p_mi, "window", drawable ? "embed-xid,any" : "any");
var_SetInteger (p_mi, "drawable-xid", drawable);
}
@@ -1225,6 +1295,13 @@ uint32_t libvlc_media_player_get_xwindow( libvlc_media_player_t *p_mi )
return var_GetInteger (p_mi, "drawable-xid");
}
+static void libvlc_media_player_detach_hwnd(libvlc_media_player_t *player)
+{
+ /* Window variable is not configurable from libvlc */
+ var_SetString(player, "window", "any");
+ var_SetInteger(player, "drawable-hwnd", 0);
+}
+
/**************************************************************************
* set_hwnd
**************************************************************************/
@@ -1232,9 +1309,8 @@ void libvlc_media_player_set_hwnd( libvlc_media_player_t *p_mi,
void *drawable )
{
assert (p_mi != NULL);
+ libvlc_media_player_switch_vout(p_mi, libvlc_media_player_detach_hwnd);
#if defined (_WIN32) || defined (__OS2__)
- var_SetString (p_mi, "dec-dev", "any");
- var_SetString (p_mi, "vout", "any");
var_SetString (p_mi, "window",
(drawable != NULL) ? "embed-hwnd,any" : "any");
var_SetInteger (p_mi, "drawable-hwnd", (uintptr_t)drawable);
@@ -1260,6 +1336,12 @@ void *libvlc_media_player_get_hwnd( libvlc_media_player_t *p_mi )
#endif
}
+static void libvlc_media_player_detach_android_context(libvlc_media_player_t *player)
+{
+ var_SetAddress(player, "drawable-androidwindow", NULL);
+}
+
+
/**************************************************************************
* set_android_context
**************************************************************************/
@@ -1267,6 +1349,7 @@ void libvlc_media_player_set_android_context( libvlc_media_player_t *p_mi,
void *p_awindow_handler )
{
assert (p_mi != NULL);
+ libvlc_media_player_switch_vout(p_mi, libvlc_media_player_detach_android_context);
#ifdef __ANDROID__
var_SetAddress (p_mi, "drawable-androidwindow", p_awindow_handler);
#else
=====================================
lib/media_player_internal.h
=====================================
@@ -33,6 +33,7 @@
#include "../modules/audio_filter/equalizer_presets.h"
+typedef void (*libvlc_media_player_vout_detach_cb)(libvlc_media_player_t *player);
struct libvlc_media_player_t
{
struct vlc_object_t obj;
@@ -55,6 +56,23 @@ struct libvlc_media_player_t
void *cbs_data;
bool seeking;
} timer;
+
+ struct {
+ /*
+ * Tracks the active libvlc window/output integration and the default
+ * values to restore when switching integrations. Each setter installs
+ * a detach callback that resets the variables it touched (vout, gl,
+ * gles2, dec-dev, window, vmem-*, vout-cb-*). The next setter calls the
+ * current detach callback before configuring its own state, so callers
+ * can safely switch between window providers and output/video callbacks
+ * without leaving stale configuration behind.
+ */
+ libvlc_media_player_vout_detach_cb window_detach;
+ char *default_dec_dev;
+ char *default_vout;
+ char *default_gl;
+ char *default_gles2;
+ } vout;
};
/**
=====================================
test/Makefile.am
=====================================
@@ -42,6 +42,7 @@ check_PROGRAMS = \
test_libvlc_media_thumbnail_webp \
test_libvlc_media_list \
test_libvlc_media_player \
+ test_libvlc_video_callback \
test_libvlc_media_player_record \
test_libvlc_media_discoverer \
test_libvlc_renderer_discoverer \
@@ -180,6 +181,8 @@ test_libvlc_media_list_SOURCES = libvlc/media_list.c
test_libvlc_media_list_LDADD = $(LIBVLC)
test_libvlc_media_player_SOURCES = libvlc/media_player.c
test_libvlc_media_player_LDADD = $(LIBVLCCORE) $(LIBVLC)
+test_libvlc_video_callback_SOURCES = libvlc/video_callback.c
+test_libvlc_video_callback_LDADD = $(LIBVLCCORE) $(LIBVLC)
test_libvlc_media_player_record_SOURCES = libvlc/media_player_record.c
test_libvlc_media_player_record_LDADD = $(LIBVLCCORE) $(LIBVLC)
test_libvlc_media_discoverer_SOURCES = libvlc/media_discoverer.c
=====================================
test/libvlc/meson.build
=====================================
@@ -109,6 +109,13 @@ vlc_tests += {
'module_depends': ['demux_mock']
}
+vlc_tests += {
+ 'name' : 'test_libvlc_video_callback',
+ 'sources' : files('video_callback.c'),
+ 'suite' : ['libvlc'],
+ 'link_with' : [libvlc, libvlccore],
+}
+
vlc_tests += {
'name' : 'test_libvlc_media_player_record',
'sources' : files('media_player_record.c'),
=====================================
test/libvlc/video_callback.c
=====================================
@@ -0,0 +1,240 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*****************************************************************************
+ * video_callback.c: test for the video callback libvlc code
+ *****************************************************************************
+ * Copyright (C) 2026 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
+
+#include "test.h"
+#include <vlc_common.h>
+#include <vlc_variables.h>
+#include "../lib/libvlc_internal.h"
+#include "../lib/media_player_internal.h"
+
+static void *dummy_lock(void *opaque, void **planes)
+{
+ (void)opaque;
+ (void)planes;
+ return NULL;
+}
+
+static void dummy_unlock(void *opaque, void *picture, void *const *planes)
+{
+ (void)opaque;
+ (void)picture;
+ (void)planes;
+}
+
+static void dummy_display(void *opaque, void *picture)
+{
+ (void)opaque;
+ (void)picture;
+}
+
+static bool dummy_output_setup(void **opaque,
+ const libvlc_video_setup_device_cfg_t *cfg,
+ libvlc_video_setup_device_info_t *out)
+{
+ (void)cfg;
+ (void)out;
+ *opaque = (void *)0x1;
+ return true;
+}
+
+static void dummy_output_cleanup(void *opaque)
+{
+ (void)opaque;
+}
+
+static void dummy_output_set_window(void *opaque,
+ libvlc_video_output_resize_cb resize_cb,
+ libvlc_video_output_mouse_move_cb move_cb,
+ libvlc_video_output_mouse_press_cb press_cb,
+ libvlc_video_output_mouse_release_cb release_cb,
+ void *report_opaque)
+{
+ (void)opaque;
+ (void)resize_cb;
+ (void)move_cb;
+ (void)press_cb;
+ (void)release_cb;
+ (void)report_opaque;
+}
+
+static bool dummy_update_output(void *opaque,
+ const libvlc_video_render_cfg_t *cfg,
+ libvlc_video_output_cfg_t *output)
+{
+ (void)opaque;
+ (void)cfg;
+ (void)output;
+ return true;
+}
+
+static void dummy_swap(void *opaque)
+{
+ (void)opaque;
+}
+
+static bool dummy_make_current(void *opaque, bool enter)
+{
+ (void)opaque;
+ (void)enter;
+ return true;
+}
+
+static void *dummy_get_proc_address(void *opaque, const char *fct_name)
+{
+ (void)opaque;
+ (void)fct_name;
+ return NULL;
+}
+
+static void dummy_metadata(void *opaque, libvlc_video_metadata_type_t type,
+ const void *metadata)
+{
+ (void)opaque;
+ (void)type;
+ (void)metadata;
+}
+
+static bool dummy_select_plane(void *opaque, size_t plane, void *output)
+{
+ (void)opaque;
+ (void)plane;
+ (void)output;
+ return true;
+}
+
+static void assert_var_string_eq(vlc_object_t *obj, const char *name,
+ const char *expected)
+{
+ char *value = var_GetString(obj, name);
+ if (expected == NULL)
+ assert(value == NULL);
+ else
+ {
+ assert(value != NULL);
+ assert(strcmp(value, expected) == 0);
+ }
+ free(value);
+}
+
+static void test_media_player_detach_video_callbacks(const char** argv, int argc)
+{
+ test_log("Testing detach of video callbacks\n");
+ (void)argv;
+ (void)argc;
+
+ const char *test_args[] = {
+ "-v", "--vout=nonexistent-vout", "--aout=adummy", "--text-renderer=tdummy",
+ };
+ const int test_nargs = (int)(sizeof(test_args) / sizeof(test_args[0]));
+
+ libvlc_instance_t *instance = libvlc_new(test_nargs, test_args);
+ assert(instance != NULL);
+
+ libvlc_media_player_t *player = libvlc_media_player_new(instance);
+ assert(player != NULL);
+
+ char *expected_vout = var_GetString(instance->p_libvlc_int, "vout");
+ assert(expected_vout != NULL);
+ assert(strcmp(expected_vout, "nonexistent-vout") == 0);
+ assert_var_string_eq(VLC_OBJECT(player), "vout", expected_vout);
+
+ libvlc_video_set_callbacks(player, dummy_lock, dummy_unlock, dummy_display, NULL);
+
+ assert(var_GetAddress(player, "vmem-lock") != NULL);
+ assert_var_string_eq(VLC_OBJECT(player), "vout", "vmem");
+ assert_var_string_eq(VLC_OBJECT(player), "window", "dummy");
+
+#ifdef __APPLE__
+ libvlc_media_player_set_nsobject(player, (void *)1);
+#elif defined(_WIN32)
+ libvlc_media_player_set_hwnd(player, (void *)1);
+#else
+ libvlc_media_player_set_xwindow(player, 1);
+#endif
+
+ assert(var_GetAddress(player, "vmem-lock") == NULL);
+ assert_var_string_eq(VLC_OBJECT(player), "vout", expected_vout);
+
+ free(expected_vout);
+ libvlc_media_player_release(player);
+ libvlc_release(instance);
+}
+
+static void test_media_player_detach_output_callbacks(const char** argv, int argc)
+{
+ test_log("Testing detach of output callbacks\n");
+ (void)argv;
+ (void)argc;
+
+ const char *test_args[] = {
+ "-v", "--vout=nonexistent-vout", "--aout=adummy", "--text-renderer=tdummy",
+ };
+ const int test_nargs = (int)(sizeof(test_args) / sizeof(test_args[0]));
+
+ libvlc_instance_t *instance = libvlc_new(test_nargs, test_args);
+ assert(instance != NULL);
+
+ libvlc_media_player_t *player = libvlc_media_player_new(instance);
+ assert(player != NULL);
+
+ char *expected_vout = var_GetString(instance->p_libvlc_int, "vout");
+ assert(expected_vout != NULL);
+ assert(strcmp(expected_vout, "nonexistent-vout") == 0);
+ assert_var_string_eq(VLC_OBJECT(player), "vout", expected_vout);
+
+ bool ok = libvlc_video_set_output_callbacks(
+ player, libvlc_video_engine_opengl, dummy_output_setup,
+ dummy_output_cleanup, dummy_output_set_window, dummy_update_output,
+ dummy_swap, dummy_make_current, dummy_get_proc_address,
+ dummy_metadata, dummy_select_plane, NULL);
+ assert(ok);
+
+ assert(var_GetInteger(player, "vout-cb-type") == libvlc_video_engine_opengl);
+ assert(var_GetAddress(player, "vout-cb-window-cb") != NULL);
+
+ ok = libvlc_video_set_output_callbacks(
+ player, libvlc_video_engine_disable, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL);
+ assert(ok);
+
+ assert(var_GetInteger(player, "vout-cb-type") == libvlc_video_engine_disable);
+ assert(var_GetAddress(player, "vout-cb-window-cb") == NULL);
+ assert(var_GetAddress(player, "vout-cb-setup") == NULL);
+ assert_var_string_eq(VLC_OBJECT(player), "vout", expected_vout);
+
+ free(expected_vout);
+ libvlc_media_player_release(player);
+ libvlc_release(instance);
+}
+
+int main (void)
+{
+ test_init();
+
+ test_media_player_detach_video_callbacks(test_defaults_args, test_defaults_nargs);
+ test_media_player_detach_output_callbacks(test_defaults_args, test_defaults_nargs);
+
+ return 0;
+}
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/b343f6d7b313cad87bc055915f8e59adafedb648...0c81c9aece7d785efd793476871f97a86fa03bfe
--
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/b343f6d7b313cad87bc055915f8e59adafedb648...0c81c9aece7d785efd793476871f97a86fa03bfe
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