diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 5724f6c509e7813e0fa69de2e914ea3ddea04523..37d5e5345dc6039dcf028e3e536ef955be45c977 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,5 +1,5 @@
 image:
-  name: registry.videolan.org/vlc-debian-wasm-emscripten:20211104155236
+  name: registry.videolan.org/vlc-debian-wasm-emscripten:20220505193036
   entrypoint: [] # See issue : https://gitlab.com/gitlab-org/gitlab-runner/-/issues/1170
 
 stages:
@@ -20,5 +20,12 @@ compile-job:
       - experimental*       # worker.js, wasm file and js file, data...
       - assets/             # Assets for logos etc...
       - vlc.html            # HTML page
+      - vlc_media_list_poc.html
       - lib/*               # libvlc.js, module-loader.js, overlay.js, wasm-imports.js
+      - ./vlc/modules/gui/qt/pixmaps/toolbar/volume-muted.png
+      - ./vlc/modules/gui/qt/pixmaps/pause.png
+      - ./vlc/modules/gui/qt/pixmaps/toolbar/volume-medium.png
+      - ./vlc/modules/gui/qt/pixmaps/play.png
+      - ./vlc/modules/gui/qt/pixmaps/play_button.svg
+      - ./vlc/modules/audio_output/webaudio/audio-worklet-processor.js
     expire_in: 1 week       # Expiration date
diff --git a/compile.sh b/compile.sh
index 4e149418260851b1528fbdd1ddeea95d0520966e..7a6bd2546e22062c916181b744c38f1675a42330 100755
--- a/compile.sh
+++ b/compile.sh
@@ -19,7 +19,7 @@ checkfail()
 SLOW_MODE=${SLOW_MODE:=1}
 WORK_DIR=$PWD
 
-EMSDK_VERSION="3.0.0"
+EMSDK_VERSION="3.1.18"
 # Download the portable SDK and uncompress it
 if [ ! -d emsdk ]; then
     diagnostic "emsdk not found. Fetching it"
@@ -29,7 +29,7 @@ if [ ! -d emsdk ]; then
 fi
 
 cd $WORK_DIR
-TESTED_HASH="271d3552"
+TESTED_HASH="06e361b127e4609e429909756212ed5e30e7d032"
 # Go go go vlc
 if [ ! -d vlc ]; then
     diagnostic "VLC source not found, cloning"
@@ -40,15 +40,17 @@ if [ ! -d vlc ]; then
     # patching vlc
     if [ -d ../vlc_patches ] && [ "$(ls -A ../vlc_patches)" ]; then
 	# core patches
-	git am -3 ../vlc_patches/0001-configure-improve-testing-unsupported-GL-functions-f.patch
-	git am -3 ../vlc_patches/0001-modules-disable-libvlc_json-and-ytbdl-vlc.js-17.patch
-	git am -3 ../vlc_patches/nacl-wasm/00*.patch
-	git am -3 ../vlc_patches/audio_output/00*.patch
-	git am -3 ../vlc_patches/video_output/00*.patch
-	git am -3 ../vlc_patches/logger/00*.patch
-	git am -3 ../vlc_patches/0001-vlc.js-modules-remove-category.patch
-	git am -3 ../vlc_patches/audio_output/new_aout.patch
-	# git am -3 ../vlc_patches/filesystem/*.patch
+	#git am -3 ../vlc_patches/0001-configure-improve-testing-unsupported-GL-functions-f.patch
+	#git am -3 ../vlc_patches/0001-modules-disable-libvlc_json-and-ytbdl-vlc.js-17.patch
+	#git am -3 ../vlc_patches/nacl-wasm/00*.patch
+	#git am -3 ../vlc_patches/audio_output/00*.patch
+	#git am -3 ../vlc_patches/video_output/00*.patch
+	#git am -3 ../vlc_patches/logger/00*.patch
+	#git am -3 ../vlc_patches/0001-vlc.js-modules-remove-category.patch
+	#git am -3 ../vlc_patches/audio_output/new_aout.patch
+	#git am -3 ../vlc_patches/filesystem/*.patch
+	#git am -3 ../vlc_patches/demo_alpha/*
+	git am -3  ../vlc_patches/aug/*
     fi
     checkfail "vlc source: git clone failed"
 fi
@@ -60,14 +62,11 @@ diagnostic "Setting the environment"
 diagnostic "build libvlc"
 cd ./vlc/extras/package/wasm-emscripten/
 ./build.sh --mode=${SLOW_MODE}
+cd $WORK_DIR
+echo "_main" > libvlc_wasm.sym
+sed -e 's/^/_/' ./vlc/lib/libvlc.sym >> libvlc_wasm.sym
 
-url="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"
 
-diagnostic "getting video"
 cd $WORK_DIR
-mkdir -p samples/
-if [ ! -f "./samples/BigBuckBunny.mp4" ]; then
-    curl ${url} -o samples/BigBuckBunny.mp4
-fi
 diagnostic "Generating executable"
 ./create_main.sh
diff --git a/create_main.sh b/create_main.sh
index f997c6d1f62b9cae6b22a351a8cbda4c767f6041..3bbaf9afc696f74029162e0902678f5ad62050bf 100755
--- a/create_main.sh
+++ b/create_main.sh
@@ -29,12 +29,24 @@ SAMPLE_DIR=${SAMPLE_DIR:=./samples}
 # For release builds, remove '--profiling-funcs' and add '-Os'
 # Note that we use '-s MODULARIZE', but no '-s EXPORT_ES6', which would
 # conflict with pthreads on Firefox.
-emcc --bind -s USE_PTHREADS=1 -s TOTAL_MEMORY=1GB -s PTHREAD_POOL_SIZE=15 \
-    -s OFFSCREEN_FRAMEBUFFER=1 -s USE_WEBGL2=1 --profiling-funcs \
-    -s MODULARIZE=1 -s EXPORT_NAME="VlcModule" \
-    -s EXPORTED_RUNTIME_METHODS="allocateUTF8" \
+
+#    -s TRACE_WEBGL_CALLS=1 \
+
+# -s ASYNCIFY_IMPORTS="['init_js_file', 'getVoutMessagePort', 'bindVideoFrame', 'CopyFrameToBuffer', 'probeConfig', 'initDecoderWorkerMessagePort', 'flushAsync', 'initDecoderJS']"
+
+
+emcc --bind -s USE_PTHREADS=1 -s TOTAL_MEMORY=2GB -s PTHREAD_POOL_SIZE=25 \
+    -s OFFSCREEN_FRAMEBUFFER=1  \
+    -s USE_WEBGL2=1 \
+    --profiling-funcs \
+    -s OFFSCREENCANVAS_SUPPORT=1 \
+    -s MODULARIZE=1 -s EXPORT_NAME="initModule" \
+    -s EXTRA_EXPORTED_RUNTIME_METHODS="[allocateUTF8, writeAsciiToMemory]" \
+    -s ASYNCIFY=1 -O3 \
+    -s EXIT_RUNTIME=1 -s ASSERTIONS=1 \
     -I $PATH_VLC/include/ \
     main.c exports_media_player.c exports_media.c \
+    -s EXPORTED_FUNCTIONS=@libvlc_wasm.sym \
     $PATH_VLC/build-emscripten/lib/.libs/libvlc.a \
     $PATH_VLC/build-emscripten/vlc-modules.bc \
     $PATH_VLC/build-emscripten/modules/.libs/*.a \
@@ -42,5 +54,26 @@ emcc --bind -s USE_PTHREADS=1 -s TOTAL_MEMORY=1GB -s PTHREAD_POOL_SIZE=15 \
     $PATH_VLC/build-emscripten/src/.libs/libvlccore.a \
     $PATH_VLC/build-emscripten/compat/.libs/libcompat.a \
     --js-library lib/wasm-imports.js \
-    --js-library vlc/modules/audio_output/webaudio/webaudio.js \
-    -o experimental.js --preload-file ${SAMPLE_DIR}
+    -o experimental.js
+
+
+#em++ --bind -s USE_PTHREADS=1 -s TOTAL_MEMORY=2GB -s PTHREAD_POOL_SIZE=21 \
+#    -s OFFSCREEN_FRAMEBUFFER=1\
+#    -s USE_WEBGL2=1 \
+#    --profiling-funcs \
+#    -s OFFSCREENCANVAS_SUPPORT=1 \
+#    -s MODULARIZE=1 -s EXPORT_NAME="initModule" \
+#    -s EXTRA_EXPORTED_RUNTIME_METHODS="[allocateUTF8]" \
+#    -s ASYNCIFY=1 -O3 \
+#    -s GL_ASSERTIONS=1 \
+#    -s GL_TRACK_ERRORS=1 \
+#    -I $PATH_VLC/include/ \
+#    main_opengl.cpp \
+#    $PATH_VLC/build-emscripten/lib/.libs/libvlc.a \
+#    $PATH_VLC/build-emscripten/vlc-modules.bc \
+#    $PATH_VLC/build-emscripten/modules/.libs/*.a \
+#    $PATH_VLC/contrib/wasm32-unknown-emscripten/lib/*.a \
+#    $PATH_VLC/build-emscripten/src/.libs/libvlccore.a \
+#    $PATH_VLC/build-emscripten/compat/.libs/libcompat.a \
+#    --js-library lib/wasm-imports.js \
+#    -o opengl.js --preload-file ${SAMPLE_DIR}
diff --git a/exports_media.c b/exports_media.c
index 55d35e13faaf4050bd3b82f9b187c9336b6bf010..f7bd2fdd430a5b97a9aab5887622b8afa7be0134 100644
--- a/exports_media.c
+++ b/exports_media.c
@@ -11,7 +11,11 @@
 extern libvlc_instance_t *libvlc;
 
 libvlc_media_t* EMSCRIPTEN_KEEPALIVE wasm_media_new_path(const char *path) {
-  return libvlc_media_new_path(libvlc, path);
+    return libvlc_media_new_path(libvlc, path);
+}
+
+libvlc_media_t* EMSCRIPTEN_KEEPALIVE wasm_media_new_location(const char *path) {
+    return libvlc_media_new_location(libvlc, path);
 }
 
 void EMSCRIPTEN_KEEPALIVE wasm_media_retain( libvlc_media_t *media) {
diff --git a/exports_media_player.c b/exports_media_player.c
index af61c3777543f27ea51f4259ba763529e5ac4396..a9bc603f34917eb54e704808bf5e88091959ec4c 100644
--- a/exports_media_player.c
+++ b/exports_media_player.c
@@ -20,8 +20,12 @@ libvlc_media_player_t* EMSCRIPTEN_KEEPALIVE wasm_media_player_new() {
   return libvlc_media_player_new(libvlc);
 }
 
+libvlc_media_list_player_t* EMSCRIPTEN_KEEPALIVE wasm_media_list_player_new() {
+  return libvlc_media_list_player_new(libvlc);
+}
+
 libvlc_media_player_t* EMSCRIPTEN_KEEPALIVE wasm_media_player_new_from_media(libvlc_media_t* media) {
-  return libvlc_media_player_new_from_media(media);
+    return libvlc_media_player_new_from_media(media);
 }
 
 void EMSCRIPTEN_KEEPALIVE wasm_media_player_release(libvlc_media_player_t *media_player) {
@@ -59,6 +63,12 @@ void EMSCRIPTEN_KEEPALIVE wasm_media_player_pause(libvlc_media_player_t *media_p
   libvlc_media_player_pause(media_player);
 }
 
+void EMSCRIPTEN_KEEPALIVE wasm_media_player_stop(libvlc_media_player_t *media_play\
+er) {
+  libvlc_media_player_stop_async(media_player);
+}
+
+
 // TODO
 // LIBVLC_API int libvlc_media_player_stop_async ( libvlc_media_player_t *p_mi );
 
@@ -234,3 +244,24 @@ int EMSCRIPTEN_KEEPALIVE wasm_media_player_get_role(libvlc_media_player_t *media
 int EMSCRIPTEN_KEEPALIVE wasm_media_player_set_role(libvlc_media_player_t *media_player, unsigned role) {
   return libvlc_media_player_set_role(media_player, role);
 }
+
+int EMSCRIPTEN_KEEPALIVE wasm_libvlc_init(size_t size, char const *argv[]) {
+    /*
+    char const *vlc_argv[] = {
+        "-vvv",
+        "--no-spu",
+        "--no-osd",
+        "--aout=emworklet_audio",
+        "--codec=avcodec",
+        "-Idummy",
+        "--ignore-config",
+    };
+    */
+    libvlc = libvlc_new( size, argv );
+    if (libvlc == NULL)
+    {
+        fprintf( stderr, "unable to create libvlc instance" );
+        return -1;
+    }
+    return 0;
+}
diff --git a/lib/libvlc.js b/lib/libvlc.js
index 51a8e4d2bbd5a2d4813d400b3c765896a8505717..8aa8bff73f0d93e3411acc9b7e42e5214bef3159 100644
--- a/lib/libvlc.js
+++ b/lib/libvlc.js
@@ -44,6 +44,9 @@ export class MediaPlayer {
     return this.module._wasm_media_player_is_playing(this.media_player_ptr);
   }
 
+  stop() {
+    return this.module._wasm_media_player_stop(this.media_player_ptr);
+  }
   play() {
     return this.module._wasm_media_player_play(this.media_player_ptr);
   }
@@ -228,7 +231,7 @@ export class Media {
     this.module = module;
 
     let path_ptr = module.allocateUTF8(path)
-    this.media_ptr = module._wasm_media_new_path(path_ptr);
+    this.media_ptr = module._wasm_media_new_location(path_ptr);
     module._free(path_ptr);
 
     if (this.media_ptr == 0) {
diff --git a/lib/module-loader.js b/lib/module-loader.js
index 9507f15866e42dbac2857b00babef2e68ccf58ea..20b73642c1ebfc36a5810cd21ad38a9a8eff1c67 100644
--- a/lib/module-loader.js
+++ b/lib/module-loader.js
@@ -1,6 +1,10 @@
 var statusElement = document.getElementById('status');
 var progressElement = document.getElementById('progress');
 var spinnerElement = document.getElementById('spinner');
+var overlayElement = document.getElementById('canvas');
+
+// This should be set to true once the user clicks on the canvas for the first time
+var was_clicked = false;
 
 // This should be set to true once the user clicks on the canvas for the first time
 var was_clicked = false;
@@ -9,9 +13,22 @@ var VlcModuleExt = {
   preRun: [ function() {
     window.display_overlay = true
   }],
+  preRun: [ function() {
+    window.display_overlay = true
+  }],
+  vlc_access_file: {},
   onRuntimeInitialized: function() {
     // This should run after the wasm module is instantiated
     // before, the Pthread object won't be available
+    var img = new Image(50, 50);
+    img.src = "./assets/VLC_Icon.svg";
+    var context = overlayElement.getContext('2d');
+    context.drawImage(img,
+		      overlay.width/2 - img.width/2,
+		      overlay.height/2 - img.height/2,
+		      img.width,
+		      img.height,
+		     );
   },
 
   print: (function() {
diff --git a/lib/overlay.js b/lib/overlay.js
index 7e0d627386bc27abef1d9df4c113ddcfa533d13b..8f51b8ce0ef56f9af819787142323bebf3879292 100644
--- a/lib/overlay.js
+++ b/lib/overlay.js
@@ -59,6 +59,12 @@ volume_button.src = "vlc/modules/gui/qt/pixmaps/toolbar/volume-medium.png";
 const muted_button = new Image(BUTTON_SIZE, BUTTON_SIZE);
 muted_button.src = "vlc/modules/gui/qt/pixmaps/toolbar/volume-muted.png";
 
+
+const filepicker_button = document.createElement("input");
+filepicker_button.id = "fpicker_btn";
+filepicker_button.type = "file";
+filepicker_button.style = "display:none";
+document.body.appendChild(filepicker_button);
 // Apply underlying changes (eg video is paused) to the displayed UI
 export function update_overlay(overlay) {
   const ctx = overlay.getContext("2d");
@@ -224,7 +230,12 @@ export function on_overlay_click(overlay, mouse_event) {
   }
 
   // User clicked outside the menu bar
-  else {
-    media_player.toggle_play();
+    else {
+      if (filepicker_button.files.length === 0) {
+	  filepicker_button.click();
+      }
+      else {
+	  media_player.toggle_play();
+      }
   }
 }
diff --git a/main.c b/main.c
index d6431c5dd554c443a76a0d17dc160ed72ac77ae8..d2b57924370d48a92f10ec28e5509f2430864cf3 100644
--- a/main.c
+++ b/main.c
@@ -7,84 +7,85 @@
 #include <emscripten.h>
 #include <emscripten/html5.h>
 
-libvlc_media_player_t *mp;
+libvlc_media_player_t *mp = NULL;
 libvlc_instance_t *libvlc;
 libvlc_time_t t = -1;
 
 static void iter()
 {
-  if (!mp)
-    return;
-	if (libvlc_media_player_get_time(mp) == t) {
-		// when enable, the js does not respond.
-		//libvlc_media_player_release( mp );
-    	//libvlc_release( libvlc );
-		emscripten_cancel_main_loop();
-	}
-	t = libvlc_media_player_get_time(mp);
+    if (!mp)
+        return;
+    if (libvlc_media_player_get_time(mp) == t) {
+        // when enable, the js does not respond.
+        //libvlc_media_player_release( mp );
+        //libvlc_release( libvlc );
+        emscripten_cancel_main_loop();
+    }
+    t = libvlc_media_player_get_time(mp);
 }
 
 void EMSCRIPTEN_KEEPALIVE set_global_media_player(libvlc_media_player_t *media_player) {
-  mp = media_player;
+    mp = media_player;
 }
 
 extern void update_overlay();
 
-
 static void on_position_changed(const libvlc_event_t *p_event, void *p_data){
-  VLC_UNUSED(p_event);
-  VLC_UNUSED(p_data);
-  
-  MAIN_THREAD_ASYNC_EM_ASM({
-    const overlay = document.getElementById("overlay");
-    update_overlay(overlay);
-  });
+    VLC_UNUSED(p_event);
+    VLC_UNUSED(p_data);
+
+    MAIN_THREAD_ASYNC_EM_ASM({
+        const overlay = document.getElementById("overlay");
+        update_overlay(overlay);
+    });
 }
 
 int main() {
 
-	/* We don't want to the main thread stop even if the main function exit.
-	 * If this thread stop, all proxyfied functions wont be called.
-	 */
-  EM_ASM(Module['noExitRuntime']=true);
-  char const *vlc_argv[] = {
-			    "-vvv",
-			    "--no-spu",
-			    "--no-osd",
-			    "--aout=emworklet_audio",
-			    "-Idummy",
-			    "--ignore-config",
-  };
+    /* We don't want to the main thread stop even if the main function exit.
+     * If this thread stop, all proxyfied functions wont be called.
+     */
+    EM_ASM(Module['noExitRuntime']=true);
+    /*
+    char const *vlc_argv[] = {
+        "-vvv",
+        "--no-spu",
+        "--no-osd",
+        "--aout=emworklet_audio",
+        "--codec=avcodec",
+        "-Idummy",
+        "--ignore-config",
+    };
 
-  libvlc = libvlc_new( ARRAY_SIZE( vlc_argv ), vlc_argv );
-  if (libvlc == NULL)
+    libvlc = libvlc_new( ARRAY_SIZE( vlc_argv ), vlc_argv );
+    if (libvlc == NULL)
     {
-      fprintf( stderr, "unable to create libvlc instance" );
-      return -1;
+        fprintf( stderr, "unable to create libvlc instance" );
+        return -1;
     }
-
-  emscripten_set_main_loop(iter, 1, 1);
-
-  return 0;
+    */
+    emscripten_set_main_loop(iter, 1, 0);
+    emscripten_exit_with_live_runtime();
+    return 0;
 }
 
 // Used to make sure the UI (progress bar, play/pause button, etc) is
 // updated as the video is read.
 void EMSCRIPTEN_KEEPALIVE attach_update_events(libvlc_media_player_t *media_player) {
-  libvlc_event_manager_t* event_manager = libvlc_media_player_event_manager(media_player);
-  int res;
-  res = libvlc_event_attach(
-    event_manager,
-    libvlc_MediaPlayerPositionChanged,
-    on_position_changed,
-    NULL
-  );
-  assert(res == 0);
-  res = libvlc_event_attach(
-    event_manager,
-    libvlc_MediaPlayerPaused,
-    on_position_changed,
-    NULL
-  );
-  assert(res == 0);
+    libvlc_event_manager_t* event_manager = libvlc_media_player_event_manager(media_player);
+    int res;
+    res = libvlc_event_attach(
+        event_manager,
+        libvlc_MediaPlayerPositionChanged,
+        on_position_changed,
+        NULL
+    );
+    assert(res == 0);
+    res = libvlc_event_attach(
+        event_manager,
+        libvlc_MediaPlayerPaused,
+        on_position_changed,
+        NULL
+    );
+    assert(res == 0);
 }
diff --git a/main_opengl.cpp b/main_opengl.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ca92ef174bfd382b0aa3b514dd6cc177d6d8ca8c
--- /dev/null
+++ b/main_opengl.cpp
@@ -0,0 +1,413 @@
+#include <stdio.h>
+#include <vlc/vlc.h>
+#include <assert.h>
+#include <errno.h>
+
+#define restrict
+
+#include <emscripten.h>
+#include <emscripten/html5.h>
+#include <GLES3/gl3.h>
+
+#include <vlc_common.h>
+#include <vlc_vout_window.h>
+#include <vlc_opengl.h>
+#include <vlc_vout_display.h>
+#include <vlc_opengl_filter.h>
+#include <vlc_opengl_interop.h>
+#include <vlc_modules.h>
+#include <vlc_messages.h>
+
+#include "vlc/lib/libvlc_internal.h"
+
+struct test_scenario;
+static void scenario1_clear(struct test_scenario *scenario);
+static void scenario3_clearsmooth(struct test_scenario *scenario);
+static void scenario4_mock(struct test_scenario *scenario);
+static void scenario5_videoframe(struct test_scenario *scenario);
+static void scenario7_display(struct test_scenario *scenario);
+
+struct webcodec_context
+{
+    pthread_t gl_worker;
+};
+
+libvlc_instance_t *libvlc;
+
+struct test_scenario {
+    const char *canvas;
+    void (*setup)(struct test_scenario *scenario);
+    float color[3];
+
+    vout_window_t *window;
+    vlc_gl_t *gl;
+    vlc_thread_t thread;
+} tests[] =  {
+    {
+        .canvas = "canvas_step1",
+        .setup = scenario1_clear,
+        .color = { 1.f, 0.f, 0.f },
+    },
+    {
+        .canvas = "canvas_step2",
+        .setup = scenario1_clear,
+        .color = { 0.f, 1.f, 0.f },
+    },
+    {
+        .canvas = "canvas_step3",
+        .setup = scenario3_clearsmooth,
+    },
+    {
+        .canvas = "canvas_step4",
+        .setup = scenario4_mock,
+    },
+    {
+        .canvas = "canvas_step5",
+        .setup = scenario5_videoframe,
+    },
+    {
+        .canvas = "canvas_step7",
+        .setup = scenario7_display,
+    }
+
+};
+
+#define DECLARE_MEMBER(type, name) type name;
+#define LOAD_SYMBOL(type, name) \
+    gl.name = (type)vlc_gl_GetProcAddress(scenario->gl, "gl" #name); \
+    assert(gl.name);
+
+#define CREATE_VTABLE(VTABLE) \
+    struct { \
+        VTABLE(DECLARE_MEMBER) \
+    } gl; \
+    VTABLE(LOAD_SYMBOL)
+
+static void iter()
+{
+    return;
+}
+
+static void *test_run(void *opaque)
+{
+    auto *test = static_cast<struct test_scenario *>(opaque);
+    libvlc_int_t *root = libvlc->p_libvlc_int;
+
+    test->window = vlc_object_create<vout_window_t>(root);
+    test->window->handle.canvas = test->canvas;
+    test->window->type = VOUT_WINDOW_TYPE_EMSCRIPTEN_WEBGL;
+    struct vout_display_cfg cfg = {
+        .window = test->window,
+    };
+    test->setup(test);
+
+    return NULL;
+}
+
+static void init_gl(struct test_scenario *scenario)
+{
+    struct vout_display_cfg cfg = {
+        .window = scenario->window,
+    };
+
+    scenario->gl = vlc_gl_Create(&cfg, VLC_OPENGL_ES2, "any");
+    assert(scenario->gl);
+}
+
+int main() {
+    /* We don't want to the main thread stop even if the main function exit.
+     * If this thread stop, all proxyfied functions wont be called.
+     */
+    EM_ASM(Module['noExitRuntime']=true);
+    char const *vlc_argv[] = {
+        "-vvv",
+        "--no-spu",
+        "--no-osd",
+        "--aout=emworklet_audio",
+        "-Idummy",
+        "--ignore-config",
+    };
+
+    libvlc = libvlc_new( ARRAY_SIZE( vlc_argv ), vlc_argv );
+    if (libvlc == NULL)
+    {
+        fprintf( stderr, "unable to create libvlc instance" );
+        return -1;
+    }
+
+    emscripten_log(EM_LOG_INFO | EM_LOG_CONSOLE, "Creating tests...\n");
+    for (size_t i=0; i<ARRAY_SIZE(tests); ++i)
+    {
+        emscripten_log(EM_LOG_INFO | EM_LOG_CONSOLE, "- test %zu/%zu...\n",
+            i, ARRAY_SIZE(tests));
+        vlc_clone(&tests[i].thread, test_run, &tests[i], 0);
+    }
+
+    emscripten_set_main_loop(iter, 1, 1);
+
+    return 0;
+}
+
+static void scenario1_clear(struct test_scenario *scenario)
+{
+    init_gl(scenario);
+
+    vlc_gl_MakeCurrent(scenario->gl);
+
+#define OPENGL_VTABLE_F(F) \
+    F(PFNGLCLEARCOLORPROC, ClearColor) \
+    F(PFNGLCLEARPROC, Clear)
+    CREATE_VTABLE(OPENGL_VTABLE_F);
+#undef OPENGL_VTABLE_F
+
+    gl.ClearColor(scenario->color[0], scenario->color[1], scenario->color[2], 1.f);
+    gl.Clear(GL_COLOR_BUFFER_BIT);
+    vlc_gl_Swap(scenario->gl);
+    vlc_gl_ReleaseCurrent(scenario->gl);
+}
+
+static void ClearColorAtTime(void *opaque)
+{
+    auto *scenario = static_cast<struct test_scenario *>(opaque);
+    vlc_gl_MakeCurrent(scenario->gl);
+
+#define OPENGL_VTABLE_F(F) \
+    F(PFNGLCLEARCOLORPROC, ClearColor) \
+    F(PFNGLCLEARPROC, Clear)
+    CREATE_VTABLE(OPENGL_VTABLE_F);
+#undef OPENGL_VTABLE_F
+
+    vlc_tick_t now = vlc_tick_now();
+    float secf = secf_from_vlc_tick(now);
+    secf = fmod(secf / 5.f, 1.f);
+
+    gl.ClearColor(secf, 0.f, 0.f, 1.f);
+    gl.Clear(GL_COLOR_BUFFER_BIT);
+    vlc_gl_Swap(scenario->gl);
+    vlc_gl_ReleaseCurrent(scenario->gl);
+
+}
+
+static void scenario3_clearsmooth(struct test_scenario *scenario)
+{
+    init_gl(scenario);
+
+    emscripten_set_main_loop_arg(ClearColorAtTime, scenario, 0, false);
+}
+
+static void scenario4_mock(struct test_scenario *scenario)
+{
+    init_gl(scenario);
+
+    vlc_gl_MakeCurrent(scenario->gl);
+
+#define OPENGL_VTABLE_F(F) \
+    F(PFNGLCLEARCOLORPROC, ClearColor) \
+    F(PFNGLCLEARPROC, Clear)
+    CREATE_VTABLE(OPENGL_VTABLE_F);
+#undef OPENGL_VTABLE_F
+
+    auto end = [&]{
+        gl.ClearColor(0.f, 1.f, 0.f, 1.f);
+        gl.Clear(GL_COLOR_BUFFER_BIT);
+        vlc_gl_Swap(scenario->gl);
+        vlc_gl_ReleaseCurrent(scenario->gl);
+    };
+
+    size_t count_strict;
+    module_t **modules;
+    ssize_t count = vlc_module_match(
+            "opengl filter", "mock", false,
+            &modules, &count_strict);
+
+
+    if (count == 0)
+    {
+        msg_Err(scenario->gl, "No opengl filter module named mock found");
+        end();
+        return;
+    }
+
+    gl.ClearColor(0.f, 0.f, 0.f, 1.f);
+    gl.Clear(GL_COLOR_BUFFER_BIT);
+
+
+    struct vlc_gl_input_meta meta = {
+        .pts = VLC_TICK_0,
+    };
+    struct vlc_gl_tex_size texsize = { .width = 400, .height = 300 };
+    auto filter = vlc_object_create<struct vlc_gl_filter>(scenario->gl);
+    filter->gl = scenario->gl;
+
+    auto *activate = reinterpret_cast<vlc_gl_filter_open_fn*>(
+            vlc_module_map(vlc_object_logger(scenario->gl), modules[0]));
+
+    if (activate(filter, NULL, NULL, &texsize) != VLC_SUCCESS)
+    {
+        end();
+        return;
+    }
+
+    filter->ops->draw(filter, NULL, &meta);
+
+    vlc_gl_Swap(scenario->gl);
+    vlc_gl_ReleaseCurrent(scenario->gl);
+    return;
+}
+
+static int InteropInit(struct vlc_gl_interop *interop, uint32_t tex_target,
+        vlc_fourcc_t chroma, video_color_space_t yuv_space)
+{
+    return VLC_SUCCESS;
+}
+
+EM_JS(void, SendVideoFrame, (int worker_id), {
+    let buffer = new Uint8Array(4*32*32);
+    for (let i=0; i<32; ++i)
+    {
+        for (let j=0; j<32; ++j)
+        {
+            let r1 = i - 16;
+            let r2 = j - 16;
+            let value = 255;
+            /* Let's draw a disk to recognize shape and size*/
+            if (r1*r1 + r2*r2 > 10 * 10)
+                value = 0;
+            buffer[4*(i*32 + j) + 0] = value;
+            buffer[4*(i*32 + j) + 1] = value;
+            buffer[4*(i*32 + j) + 2] = value;
+            buffer[4*(i*32 + j) + 3] = 255;
+        }
+    }
+    let frame = new VideoFrame(buffer, {
+        format: 'RGBA',
+        codedWidth: 32,
+        codedHeight: 32,
+        timestamp: 0,
+    });
+    self.postMessage({
+        cmd: 'custom',
+        customCmd: 'displayFrame',
+        frame: frame,
+        targetThread: worker_id,
+        pictureId: 0,
+    }, [frame]);
+    frame.close();
+})
+
+static void scenario5_videoframe(struct test_scenario *scenario)
+{
+    init_gl(scenario);
+
+    vlc_gl_MakeCurrent(scenario->gl);
+
+#define OPENGL_VTABLE_F(F) \
+    F(PFNGLCLEARCOLORPROC, ClearColor) \
+    F(PFNGLCLEARPROC, Clear) \
+    F(PFNGLBINDTEXTUREPROC, BindTexture) \
+    F(PFNGLGENTEXTURESPROC, GenTextures) \
+    F(PFNGLGENFRAMEBUFFERSPROC, GenFramebuffers) \
+    F(PFNGLBINDFRAMEBUFFERPROC, BindFramebuffer) \
+    F(PFNGLFRAMEBUFFERTEXTURE2DPROC, FramebufferTexture2D) \
+    F(PFNGLBLITFRAMEBUFFERPROC, BlitFramebuffer)
+    CREATE_VTABLE(OPENGL_VTABLE_F);
+#undef OPENGL_VTABLE_F
+
+    auto end = [&]{
+        gl.ClearColor(0.f, 1.f, 0.f, 1.f);
+        gl.Clear(GL_COLOR_BUFFER_BIT);
+        vlc_gl_Swap(scenario->gl);
+        vlc_gl_ReleaseCurrent(scenario->gl);
+    };
+
+    struct vlc_video_context *vctx =
+        vlc_video_context_Create(NULL, VLC_VIDEO_CONTEXT_WEBCODEC,
+                                 sizeof(struct webcodec_context), NULL);
+    auto *vctxPrivate = static_cast<struct webcodec_context *>(
+            vlc_video_context_GetPrivate(vctx, VLC_VIDEO_CONTEXT_WEBCODEC));
+
+    auto interop = vlc_object_create<struct vlc_gl_interop>(scenario->gl);
+    interop->gl = scenario->gl;
+    interop->vctx = vctx;
+    interop->init = InteropInit;
+    video_format_Init(&interop->fmt_in, VLC_CODEC_WEBCODEC_OPAQUE);
+    interop->fmt_in.i_visible_width
+        = interop->fmt_in.i_width
+        = 32;
+    interop->fmt_in.i_visible_height
+        = interop->fmt_in.i_height
+        = 32;
+    interop->tex_target = GL_TEXTURE_2D;
+
+    interop->module = module_need(interop, "glinterop", "any", false);
+    if (interop->module == NULL)
+    {
+        end();
+        return;
+    }
+
+    gl.ClearColor(0.f, 0.f, 0.f, 1.f);
+    gl.Clear(GL_COLOR_BUFFER_BIT);
+
+    SendVideoFrame(vctxPrivate->gl_worker);
+
+    picture_t *pic = picture_NewFromFormat(&interop->fmt_in);
+    assert(pic);
+    GLsizei tex_width, tex_height;
+
+    GLuint texture;
+    gl.GenTextures(1, &texture);
+    interop->ops->update_textures(interop, &texture, &tex_width, &tex_height,
+                                  pic, NULL);
+
+    //GLuint defaultFb;
+    //gl.GetIntergerv(GL_FRAMEBUFFER_BINDING, &defaultFb);
+
+    GLuint fb;
+    gl.GenFramebuffers(1, &fb);
+    gl.BindFramebuffer(GL_READ_FRAMEBUFFER, fb);
+    gl.FramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
+
+    gl.BlitFramebuffer(0, 0, 32, 32,
+                       0, 0, 200, 200,
+                       GL_COLOR_BUFFER_BIT,
+                       GL_NEAREST);
+
+    vlc_gl_Swap(scenario->gl);
+    vlc_gl_ReleaseCurrent(scenario->gl);
+    return;
+}
+
+void scenario7_display(struct test_scenario *scenario)
+{
+    struct vout_display_cfg cfg = {
+        .window = scenario->window,
+        .display = { .width=400, .height=300, .sar = {1,1} },
+        .is_display_filled = true,
+    };
+
+    video_format_t format;
+    video_format_Init(&format, VLC_CODEC_WEBCODEC_OPAQUE);
+    format.i_visible_width
+        = format.i_width
+        = 32;
+    format.i_visible_height
+        = format.i_height
+        = 32;
+    format.i_sar_num = format.i_sar_den = 1;
+
+    struct vlc_video_context *vctx =
+        vlc_video_context_Create(NULL, VLC_VIDEO_CONTEXT_WEBCODEC,
+                                 sizeof(struct webcodec_context), NULL);
+    auto *vctxPrivate = static_cast<struct webcodec_context *>(
+            vlc_video_context_GetPrivate(vctx, VLC_VIDEO_CONTEXT_WEBCODEC));
+
+    vout_display_t *vd = vout_display_New(scenario->window, &format, vctx, &cfg, "gles2", NULL);
+
+    SendVideoFrame(vctxPrivate->gl_worker);
+    picture_t *pic = picture_NewFromFormat(&format);
+    assert(pic);
+
+    vout_display_Prepare(vd, pic, NULL, VLC_TICK_0);
+    vout_display_Display(vd, pic);
+}
diff --git a/vlc.html b/vlc.html
index 345f3f898b2ab474369d829610a681b59578094f..4b00798b297b41d78412a05b88ba32760f3e266d 100644
--- a/vlc.html
+++ b/vlc.html
@@ -70,20 +70,17 @@
 <body>
     <div class="emscripten_border">
     <div class="emscripten">
-        <div class="banner">
-            <img src="./assets/emscripten.svg" id ="em_logo">
-            <div class="vlc_head">
-                <img src="./assets/VLC_Icon.svg" id="logo">
-                <span id="point">.</span>
-                <span id="js">JS</span>
-            </div>
-        </div>
         <progress id="progress" value="0" max="100"></progress>
         <div class="spinner" id='spinner'></div>
         <div class="emscripten" id="status">Downloading...</div>
     </div>
     </div>
-
+    <br></br>
+    <label for="opts">Enter libvlc options here:</label>
+    <input type="text" size=60 id="opts" name="opts" value="--codec=webcodec --aout=emworklet_audio -vvv"></input>
+    <button id="vlc_init">Start libvlc</button>
+    <button id="vlc_stop">stop</button>
+    <br></br>
     <div id="stack">
         <canvas
           class="emscripten" id="canvas"
@@ -96,20 +93,21 @@
           width=1280 height=720
         ></canvas>
     </div>
-
     <script src="./lib/module-loader.js"></script>
     <script src="./experimental.js"></script>
-
     <script type="module">
       import { update_overlay, on_overlay_click } from "./lib/overlay.js";
       import { MediaPlayer } from "./lib/libvlc.js";
 
+      var vlc_options = "";
+
       // VlcModule is a function generated by emscripten in experimental.js,
       // that loads the wasm file and generates a module object from it.
       // VlcModuleExt is an object defined in 'lib/module-loader.js' with a
       // bunch of options; also, all the fields of VlcModuleExt are added to
       // the returned Module.
-      const Module = await VlcModule(VlcModuleExt);
+
+      globalThis.Module = await initModule(VlcModuleExt);
 
       window.onerror = function(event) {
         // TODO: do not warn on ok events like simulating an infinite loop or exitStatus
@@ -119,18 +117,64 @@
           if (text) Module.printErr('[post-exception status] ' + text);
         };
       };
-
+      
       const overlay = document.getElementById("overlay");
-      const media_player = new MediaPlayer(Module, "./samples/BigBuckBunny.mp4");
+      let inputElement = document.getElementById("fpicker_btn");
+      let init_elem = document.getElementById("vlc_init");
+      let stop_elem = document.getElementById("vlc_stop");
+      let options_input = document.getElementById("opts");
+      function handleFiles() {
+	  // iterator in case the user selected multiple files.
+	  for (var i=0; i < this.files.length; i++) {
+	      var name = this.files.item(i).name;
+	      console.log("opened file: ", name);
+	      Module['vlc_access_file'][1] = this.files.item(i);
+	  }
+      }
 
-      media_player.set_volume(80);
-      // FIXME
-      Module._set_global_media_player(media_player.media_player_ptr);
-      window.media_player = media_player;
-      window.on_overlay_click = on_overlay_click;
-      window.update_overlay = update_overlay;
+      inputElement.addEventListener("change", handleFiles, false);  
+      var media_player;
+      function handleStart() {
+	  vlc_options = options_input.value;
+	  console.log("vlc.js: starting vlc.js with : " + vlc_options);
+	  let vlc_opts_array = vlc_options.split(' ');
 
-      update_overlay(overlay);
+	  let vlc_opts_size = 0;
+	  for (let i in vlc_opts_array) {
+	      vlc_opts_size += vlc_opts_array[i].length + 1;
+	  }
+	  var buffer = Module._malloc(vlc_opts_size);
+	  var wrote_size = 0;
+	  for (let i in vlc_opts_array) {
+	      Module.writeAsciiToMemory(vlc_opts_array[i], buffer + wrote_size, true);
+	      wrote_size += vlc_opts_array[i].length + 1;
+	  }
+	  var vlc_argv = Module._malloc(vlc_opts_array.length * 4 + 4);
+	  var view_vlc_argv = new Uint32Array(
+	      Module.wasmMemory.buffer,
+	      vlc_argv,
+	      vlc_opts_array.length
+	  );
+	  wrote_size = 0;
+	  for (let i in vlc_opts_array) {
+	      view_vlc_argv[i] = buffer + wrote_size;
+	      wrote_size += vlc_opts_array[i].length + 1;
+	  }
+	  Module._wasm_libvlc_init(vlc_opts_array.length, vlc_argv);
+	  media_player = new MediaPlayer(Module, "emjsfile://1");
+	  media_player.set_volume(80);
+	  
+	  Module._set_global_media_player(media_player.media_player_ptr);
+	  window.media_player = media_player;
+	  window.on_overlay_click = on_overlay_click;
+	  window.update_overlay = update_overlay;      
+	  update_overlay(overlay);
+      }
+      function handleStop() {
+	  media_player.stop();
+      }
+      init_elem.addEventListener("click", handleStart, false);
+      stop_elem.addEventListener("click", handleStop, false);
     </script>
 
 
diff --git a/vlc_media_list_poc.html b/vlc_media_list_poc.html
new file mode 100644
index 0000000000000000000000000000000000000000..0149d162aa79f0236f128e48c370e08462fcf8bd
--- /dev/null
+++ b/vlc_media_list_poc.html
@@ -0,0 +1,259 @@
+<!doctype html>
+<html lang="en-us">
+<head>
+    <meta charset="utf-8">
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+    <title>VLC.js</title>
+    <style>
+        .emscripten {
+            text-align: center;
+        }
+        body {
+            background-color: #343a40;
+        }
+        #canvas {
+            border:0 !important;
+            display: flex;
+            margin: 0 auto;
+            background-color: #000;
+        }
+        #overlay {
+            border:0 !important;
+            display: flex;
+            margin: 0 auto;
+        }
+        #stack {
+            display: grid;
+        }
+        #stack > canvas {
+            grid-column: 1;
+            grid-row: 1;
+        }
+        #spinner {
+            display: none;
+        }
+        #status {
+            display: none;
+        }
+        #progress {
+            margin-left: auto;
+            margin-right: auto;
+        }
+        #logo {
+            width: 64px;
+            height: 64px;
+        }
+        #em_logo {
+            position: absolute;
+            width: 200px;
+            height: 80px;
+        }
+        #point {
+            color: #fff;
+            font-size: 60px;
+        }
+        #js {
+            color: #fff;
+            font-size: 60px;
+            font-weight: bold;
+        }
+        .banner {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+        }
+        .vlc_head {
+            flex: 1 1 0px;
+        }
+    </style>
+</head>
+<body>
+    <div class="emscripten_border">
+    <div class="emscripten">
+        <progress id="progress" value="0" max="100"></progress>
+        <div class="spinner" id='spinner'></div>
+        <div class="emscripten" id="status">Downloading...</div>
+    </div>
+    </div>
+    <br></br>
+    <label for="opts">Enter libvlc options here:</label>
+    <input type="text" size=60 id="opts" name="opts" value="--codec=webcodec --aout=emworklet_audio -vvv"></input>
+    <button id="vlc_init">Start libvlc</button>
+    <button id="vlc_select">Select Files</button>
+    <button id="vlc_play">Play</button>
+    <button id="vlc_next">Next</button>
+    <button id="vlc_prev">Previous</button>
+    <button id="vlc_stop">Stop</button>
+    <br></br>
+    <div id="stack">
+        <canvas
+          class="emscripten" id="canvas"
+          oncontextmenu="event.preventDefault()" tabindex=-1
+          width=1280 height=720
+        ></canvas>
+        <canvas
+          class="emscripten hidden" id="overlay"
+          oncontextmenu="event.preventDefault()" tabindex=-2
+          width=1280 height=720
+        ></canvas>
+    </div>
+    <script src="./lib/module-loader.js"></script>
+    <script src="./experimental.js"></script>
+    <script type="module">
+      import { update_overlay, on_overlay_click } from "./lib/overlay.js";
+      import { MediaPlayer } from "./lib/libvlc.js";
+
+      var vlc_options = "";
+
+      // VlcModule is a function generated by emscripten in experimental.js,
+      // that loads the wasm file and generates a module object from it.
+      // VlcModuleExt is an object defined in 'lib/module-loader.js' with a
+      // bunch of options; also, all the fields of VlcModuleExt are added to
+      // the returned Module.
+
+      globalThis.Module = await initModule(VlcModuleExt);
+
+      window.onerror = function(event) {
+        // TODO: do not warn on ok events like simulating an infinite loop or exitStatus
+        Module.setStatus('Exception thrown, see JavaScript console');
+        spinnerElement.style.display = 'none';
+        Module.setStatus = function(text) {
+          if (text) Module.printErr('[post-exception status] ' + text);
+        };
+      };
+      
+      const overlay = document.getElementById("overlay");
+      let inputElement = document.getElementById("fpicker_btn");
+      let init_elem = document.getElementById("vlc_init");
+      let select_elem = document.getElementById("vlc_select");
+      let play_elem = document.getElementById("vlc_play");
+      let next_elem = document.getElementById("vlc_next");
+      let prev_elem = document.getElementById("vlc_prev");
+      let stop_elem = document.getElementById("vlc_stop");
+      let options_input = document.getElementById("opts");
+
+      var ml_ptr = undefined;
+      var mlp_ptr = undefined;
+      var flag = 0;
+      var counter = 0;
+
+      select_elem.addEventListener("click", function () {
+	  inputElement.click();
+      });
+
+      var playing = -1;
+      play_elem.addEventListener("click", function () {
+	  if (mlp_ptr === undefined) {
+	      mlp_ptr = Module._wasm_media_list_player_new();
+	  }
+	  if (ml_ptr === undefined) {
+	      console.error("bad !");
+	      return ;
+	  }
+	  if (Module._libvlc_media_list_count(ml_ptr) === 0) {
+	      console.error("nope, media list is empty... come back later!");
+	  }
+	  if (playing === -1) {
+	      Module._libvlc_media_list_player_set_media_list(mlp_ptr, ml_ptr);
+	      Module._libvlc_media_list_player_play(mlp_ptr);
+	      playing = 1
+	  }
+	  else if (playing === 1) {
+	      Module._libvlc_media_list_player_pause(mlp_ptr);
+	  }
+	  else {
+	      Module._libvlc_media_list_player_play(mlp_ptr);
+	  }
+      });
+
+      stop_elem.addEventListener("click", function () {
+	  Module._libvlc_media_list_player_stop_async(mlp_ptr);
+      });
+
+      next_elem.addEventListener("click", function () {
+	  Module._libvlc_media_list_player_next(mlp_ptr);
+      });
+      prev_elem.addEventListener("click", function () {
+	  Module._libvlc_media_list_player_previous(mlp_ptr);	  
+      });
+      // iterator in case the user selected multiple files.
+      let id = 1;
+      
+      function handleFiles() {
+	  if (ml_ptr === undefined) {
+	      console.abort("bad !");
+	      return;
+	  }
+	  for (var i=0; i < this.files.length; i++) {
+	      var name = this.files.item(i).name;
+	      console.log("opened file: ", name);
+	      Module['vlc_access_file'][id] = this.files.item(i);
+	      // acquire lock
+	      Module._libvlc_media_list_lock(ml_ptr);
+	      // add_media, check if return value is 0
+	      var path_ptr = Module.allocateUTF8("emjsfile://"+ id);
+	      var media_ptr = Module._wasm_media_new_location(path_ptr);
+	      var ret = Module._libvlc_media_list_add_media(ml_ptr, media_ptr);
+	      console.assert(ret == 0);
+	      // release lock
+	      Module._libvlc_media_list_unlock(ml_ptr);
+	      Module._free(path_ptr);
+	      counter += 1;
+	      id++;
+	  }
+      }
+
+      inputElement.addEventListener("change", handleFiles, false);
+            
+      function handleStart() {
+	  if (flag === 0) {
+	      vlc_options = options_input.value;
+	      console.log("vlc.js: starting vlc.js with : " + vlc_options);
+	      let vlc_opts_array = vlc_options.split(' ');	      
+	      let vlc_opts_size = 0;
+	      for (let i in vlc_opts_array) {
+		  vlc_opts_size += vlc_opts_array[i].length + 1;
+	      }
+	      var buffer = Module._malloc(vlc_opts_size);
+	      var wrote_size = 0;
+	      for (let i in vlc_opts_array) {
+		  Module.writeAsciiToMemory(vlc_opts_array[i], buffer + wrote_size, true);
+		  wrote_size += vlc_opts_array[i].length + 1;
+	      }
+	      var vlc_argv = Module._malloc(vlc_opts_array.length * 4 + 4);
+	      var view_vlc_argv = new Uint32Array(
+		  Module.wasmMemory.buffer,
+		  vlc_argv,
+		  vlc_opts_array.length
+	      );
+	      wrote_size = 0;
+	      for (let i in vlc_opts_array) {
+		  view_vlc_argv[i] = buffer + wrote_size;
+		  wrote_size += vlc_opts_array[i].length + 1;
+	      }
+	      Module._wasm_libvlc_init(vlc_opts_array.length, vlc_argv);
+	      ml_ptr = Module._libvlc_media_list_new();
+	      flag = 1;
+	  }
+	  // libvlc + media_list setup: OK
+	  else {
+	      console.error("multiple instances not supported yet!, please reload the page");
+	  }
+	  /*
+	  const media_player = new MediaPlayer(Module, "emjsfile:///mediafile");
+	  media_player.set_volume(80);
+	  
+	  Module._set_global_media_player(media_player.media_player_ptr);
+	  window.media_player = media_player;
+	  window.on_overlay_click = on_overlay_click;
+	  window.update_overlay = update_overlay;      
+	  update_overlay(overlay);
+	  */
+      }
+      init_elem.addEventListener("click", handleStart, false);
+
+    </script>
+
+
+</body>
+</html>
diff --git a/vlc_patches/aug/0001-configure-improve-testing-unsupported-GL-functions-f.patch b/vlc_patches/aug/0001-configure-improve-testing-unsupported-GL-functions-f.patch
new file mode 100644
index 0000000000000000000000000000000000000000..832087cd8ab1003b58d419486859eead4e79c139
--- /dev/null
+++ b/vlc_patches/aug/0001-configure-improve-testing-unsupported-GL-functions-f.patch
@@ -0,0 +1,43 @@
+From cdd64a1c6c680245cd89a66c957347318be707ec Mon Sep 17 00:00:00 2001
+From: Mehdi Sabwat <mehdi@videolabs.io>
+Date: Tue, 27 Apr 2021 15:34:23 +0200
+Subject: [PATCH 01/77] configure: improve testing unsupported GL functions for
+ emscripten
+
+The build system assumes OpenGL functions are implemented if the headers are defined.
+Which is wrong, so we need to disable the HAVE_GL marker, if linking fails.
+
+we use AM_LINK_IFELSE() because AC_COMPILE will add the "-c" option, and thus
+in wasm-emscripten this function will appear supported even if it is not.
+---
+ configure.ac | 10 +++++++---
+ 1 file changed, 7 insertions(+), 3 deletions(-)
+
+diff --git a/configure.ac b/configure.ac
+index a5771e36f4..5bde060fb7 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -3140,13 +3140,17 @@ have_gl="no"
+ PKG_CHECK_MODULES([GL], [gl], [
+   have_gl="yes"
+ ], [
+-  AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
++  AC_MSG_CHECKING([for OpenGL])
++  AC_LINK_IFELSE([AC_LANG_PROGRAM([[
+ #ifdef _WIN32
+ # include <GL/glew.h>
+ #endif
+ #include <GL/gl.h>
+-]], [
+-    [int t0 = GL_TEXTURE0;]])
++]], [[
++	int t0 = GL_TEXTURE0;
++	// glColorMaterial is unavailable in webgl, and emscripten
++	glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
++	]])
+   ], [
+     GL_CFLAGS=""
+     AS_IF([test "${SYS}" != "mingw32"], [
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0002-modules-disable-libvlc_json-and-ytbdl-vlc.js-17.patch b/vlc_patches/aug/0002-modules-disable-libvlc_json-and-ytbdl-vlc.js-17.patch
new file mode 100644
index 0000000000000000000000000000000000000000..93b1def20b2644c51925b7c4d06153548eaf2ed7
--- /dev/null
+++ b/vlc_patches/aug/0002-modules-disable-libvlc_json-and-ytbdl-vlc.js-17.patch
@@ -0,0 +1,25 @@
+From bcc3fabcc3dadb03cd0768daa137d019fc45d4c5 Mon Sep 17 00:00:00 2001
+From: Mehdi Sabwat <mehdi@videolabs.io>
+Date: Fri, 16 Apr 2021 11:32:33 +0200
+Subject: [PATCH 02/77] modules: disable libvlc_json and ytbdl vlc.js#17
+
+The libjson library is not linkable with emsdk v2.0.17,
+this commit should be reverted when the ticket is resolved.
+---
+ modules/demux/Makefile.am | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/modules/demux/Makefile.am b/modules/demux/Makefile.am
+index 86dfd8abee..3e64246e4d 100644
+--- a/modules/demux/Makefile.am
++++ b/modules/demux/Makefile.am
+@@ -543,4 +543,6 @@ libvlc_json_la_SOURCES = \
+ libvlc_json_la_CPPFLAGS = $(AM_CPPFLAGS) -I$(srcdir)/demux/json
+ libvlc_json_la_LIBADD = $(LTLIBVLCCORE) ../compat/libcompat.la $(LIBM)
+ libvlc_json_la_LDFLAGS = -static
++if !HAVE_EMSCRIPTEN
+ noinst_LTLIBRARIES += libvlc_json.la
++endif
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0003-contrib-ass-add-support-for-wasm-emscripten.patch b/vlc_patches/aug/0003-contrib-ass-add-support-for-wasm-emscripten.patch
new file mode 100644
index 0000000000000000000000000000000000000000..f96d2c845a32321e700f303d4630afcca25db1c0
--- /dev/null
+++ b/vlc_patches/aug/0003-contrib-ass-add-support-for-wasm-emscripten.patch
@@ -0,0 +1,157 @@
+From 035b067f845042fc0c87d80565783b21bfa50e6c Mon Sep 17 00:00:00 2001
+From: Mehdi Sabwat <mehdi@videolabs.io>
+Date: Sun, 30 May 2021 19:22:31 +0200
+Subject: [PATCH 03/77] contrib: ass: add support for wasm-emscripten
+
+---
+ contrib/src/ass/rules.mak                     |   5 +
+ ...nitial-support-for-wasm32-emscripten.patch | 105 ++++++++++++++++++
+ contrib/src/fontconfig/rules.mak              |   4 +
+ 3 files changed, 114 insertions(+)
+ create mode 100644 contrib/src/fontconfig/add-initial-support-for-wasm32-emscripten.patch
+
+diff --git a/contrib/src/ass/rules.mak b/contrib/src/ass/rules.mak
+index 05c7679896..be465bf409 100644
+--- a/contrib/src/ass/rules.mak
++++ b/contrib/src/ass/rules.mak
+@@ -25,6 +25,11 @@ WITH_DWRITE = 1
+ else
+ WITH_FONTCONFIG = 1
+ WITH_HARFBUZZ = 1
++ifdef HAVE_EMSCRIPTEN
++WITH_FONTCONFIG = 1
++WITH_HARFBUZZ = 1
++WITH_ASS_ASM = 0
++endif
+ endif
+ endif
+ endif
+diff --git a/contrib/src/fontconfig/add-initial-support-for-wasm32-emscripten.patch b/contrib/src/fontconfig/add-initial-support-for-wasm32-emscripten.patch
+new file mode 100644
+index 0000000000..b1308fb19e
+--- /dev/null
++++ b/contrib/src/fontconfig/add-initial-support-for-wasm32-emscripten.patch
+@@ -0,0 +1,105 @@
++From b7f21ca85efd78c8034223c63786a0c01b8378fe Mon Sep 17 00:00:00 2001
++From: Mehdi Sabwat <mehdi@videolabs.io>
++Date: Wed, 9 Jun 2021 03:42:51 +0200
++Subject: [PATCH 1/1] add initial support for wasm32-emscripten
++
++This commit adds a check for uuid_generate_random which is not supported for now, and fixes a failing test.
++It also handles a case where F_FSTYPENAME field is not present in statfs struct.
++---
++ configure.ac               | 1 +
++ src/Makefile.am            | 1 +
++ src/fcint.h                | 5 +++++
++ src/fcstat.c               | 2 +-
++ src/uuid_generate_random.c | 9 +++++++++
++ test/test-hash.c           | 5 ++++-
++ 6 files changed, 21 insertions(+), 2 deletions(-)
++ create mode 100644 src/uuid_generate_random.c
++
++diff --git a/configure.ac b/configure.ac
++index fb8af46..018cfc1 100644
++--- a/configure.ac
+++++ b/configure.ac
++@@ -171,2 +171,3 @@ AC_FUNC_VPRINTF
++ AC_FUNC_MMAP
++ AC_CHECK_FUNCS([link mkstemp mkostemp _mktemp_s mkdtemp getopt getopt_long getprogname getexecname rand random lrand48 random_r rand_r readlink fstatvfs fstatfs lstat strerror strerror_r]) 
+++AC_REPLACE_FUNCS([uuid_generate_random])
++
++ dnl AC_CHECK_FUNCS doesn't check for header files.
++ dnl posix_fadvise() may be not available in older libc.
++ AC_CHECK_SYMBOL([posix_fadvise], [fcntl.h], [fc_func_posix_fadvise=1], [fc_func_posix_fadvise=0])
++diff --git a/src/Makefile.am b/src/Makefile.am
++index 7b414df..de1d785 100644
++--- a/src/Makefile.am
+++++ b/src/Makefile.am
++@@ -127,6 +127,7 @@ EXTRA_DIST += \
++ 	fcobjshash.gperf.h
++ 
++ libfontconfig_la_SOURCES = \
+++	uuid_generate_random.c \
++ 	fcarch.h \
++ 	fcatomic.c \
++ 	fcatomic.h \
++diff --git a/src/fcint.h b/src/fcint.h
++index a9d075a..d8fdbfd 100644
++--- a/src/fcint.h
+++++ b/src/fcint.h
++@@ -598,6 +598,11 @@ struct _FcValuePromotionBuffer {
++ FcPrivate FcCache *
++ FcDirCacheScan (const FcChar8 *dir, FcConfig *config);
++ 
+++#ifndef HAVE_UUID_GENERATE_RANDOM
+++#include <uuid/uuid.h>
+++void uuid_generate_random(uuid_t out);
+++#endif
+++
++ FcPrivate FcCache *
++ FcDirCacheBuild (FcFontSet *set, const FcChar8 *dir, struct stat *dir_stat, FcStrSet *dirs);
++ 
++diff --git a/src/fcstat.c b/src/fcstat.c
++index 5aa1643..d1240c5 100644
++--- a/src/fcstat.c
+++++ b/src/fcstat.c
++@@ -384,7 +384,7 @@ FcFStatFs (int fd, FcStatFS *statb)
++ #  endif
++ #  if defined(HAVE_STRUCT_STATFS_F_FSTYPENAME)
++ 	p = buf.f_fstypename;
++-#  elif defined(__linux__)
+++#  elif defined(__linux__) || defined(__EMSCRIPTEN__)
++ 	switch (buf.f_type)
++ 	{
++ 	case 0x6969: /* nfs */
++diff --git a/src/uuid_generate_random.c b/src/uuid_generate_random.c
++new file mode 100644
++index 0000000..c17a58d
++--- /dev/null
+++++ b/src/uuid_generate_random.c
++@@ -0,0 +1,9 @@
+++// compat function for uuid_generate_random
+++#include "fcint.h"
+++
+++#ifndef HAVE_UUID_GENERATE_RANDOM
+++void uuid_generate_random(uuid_t out)
+++{
+++    uuid_generate(out);
+++}
+++#endif
++diff --git a/test/test-hash.c b/test/test-hash.c
++index 7530e82..221029d 100644
++--- a/test/test-hash.c
+++++ b/test/test-hash.c
++@@ -51,8 +51,11 @@ test_add (Test *test, FcChar8 *key, FcBool replace)
++     void *u;
++     FcBool (*hash_add) (FcHashTable *, void *, void *);
++     FcBool ret = FcFalse;
++-
+++#ifdef HAVE_UUID_GENERATE_RANDOM
++     uuid_generate_random (uuid);
+++#else
+++    uuid_generate(uuid);
+++#endif
++     if (replace)
++ 	hash_add = FcHashTableReplace;
++     else
++-- 
++2.32.0
++
+diff --git a/contrib/src/fontconfig/rules.mak b/contrib/src/fontconfig/rules.mak
+index ae4b218fd2..5b4b15ca34 100644
+--- a/contrib/src/fontconfig/rules.mak
++++ b/contrib/src/fontconfig/rules.mak
+@@ -23,6 +23,10 @@ ifdef HAVE_WIN32
+ endif
+ 	$(RM) $(UNPACK_DIR)/src/fcobjshash.gperf
+ 	$(call pkg_static, "fontconfig.pc.in")
++ifdef HAVE_EMSCRIPTEN
++	$(APPLY) $(SRC)/fontconfig/add-initial-support-for-wasm32-emscripten.patch
++	$(UPDATE_AUTOCONFIG)
++endif
+ 	$(MOVE)
+ 
+ FONTCONFIG_CONF := $(HOSTCONF) \
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0004-contrib-gcrypt-add-support-for-wasm-emscripten.patch b/vlc_patches/aug/0004-contrib-gcrypt-add-support-for-wasm-emscripten.patch
new file mode 100644
index 0000000000000000000000000000000000000000..f66cb6f7be287a1c3f7c14077f7e1591c4ba2fce
--- /dev/null
+++ b/vlc_patches/aug/0004-contrib-gcrypt-add-support-for-wasm-emscripten.patch
@@ -0,0 +1,93 @@
+From 46e99994434a293d92b11bdf7b5089e9c38b5aa2 Mon Sep 17 00:00:00 2001
+From: Mehdi Sabwat <mehdi@videolabs.io>
+Date: Sun, 30 May 2021 20:38:46 +0200
+Subject: [PATCH 04/77] contrib: gcrypt: add support for wasm-emscripten
+
+---
+ contrib/src/gcrypt/rules.mak           |  6 ++++
+ contrib/src/gpg-error/emscripten.patch | 43 ++++++++++++++++++++++++++
+ contrib/src/gpg-error/rules.mak        |  1 +
+ 3 files changed, 50 insertions(+)
+ create mode 100644 contrib/src/gpg-error/emscripten.patch
+
+diff --git a/contrib/src/gcrypt/rules.mak b/contrib/src/gcrypt/rules.mak
+index c9c9be3efa..0275e21fe8 100644
+--- a/contrib/src/gcrypt/rules.mak
++++ b/contrib/src/gcrypt/rules.mak
+@@ -72,6 +72,12 @@ ifeq ($(ARCH),aarch64)
+ GCRYPT_CONF += --disable-arm-crypto-support
+ endif
+ endif
++ifdef HAVE_EMSCRIPTEN
++GCRYPT_CONF += --disable-asm --disable-aesni-support ac_cv_func_syslog=no --disable-sse41-support
++GCRYPT_CONF += --disable-avx-support --disable-avx2-support --disable-padlock-support
++GCRYPT_CONF += --disable-amd64-as-feature-detection --disable-drng-support
++GCRYPT_CONF += --disable-pclmul-support
++endif
+ 
+ .gcrypt: gcrypt
+ 	# Reconfiguring this requires a git repo to be available, to
+diff --git a/contrib/src/gpg-error/emscripten.patch b/contrib/src/gpg-error/emscripten.patch
+new file mode 100644
+index 0000000000..f60695c513
+--- /dev/null
++++ b/contrib/src/gpg-error/emscripten.patch
+@@ -0,0 +1,43 @@
++From 63aa1523659914acd6c84229fb31ff9b712fbf8b Mon Sep 17 00:00:00 2001
++From: Mehdi Sabwat <mehdi@videolabs.io>
++Date: Wed, 2 Jun 2021 11:42:46 +0200
++Subject: [PATCH 1/1] emscripten
++
++---
++ .../lock-obj-pub.wasm32-unknown-emscripten.h  | 24 +++++++++++++++++++
++ 1 file changed, 24 insertions(+)
++ create mode 100644 src/syscfg/lock-obj-pub.wasm32-unknown-emscripten.h
++
++diff --git a/src/syscfg/lock-obj-pub.wasm32-unknown-emscripten.h b/src/syscfg/lock-obj-pub.wasm32-unknown-emscripten.h
++new file mode 100644
++index 0000000..1651518
++--- /dev/null
+++++ b/src/syscfg/lock-obj-pub.wasm32-unknown-emscripten.h
++@@ -0,0 +1,24 @@
+++## lock-obj-pub.wasm32-unknown-emscripten.h
+++## File created by gen-posix-lock-obj - DO NOT EDIT
+++## To be included by mkheader into gpg-error.h
+++
+++typedef struct
+++{
+++  long _vers;
+++  union {
+++    volatile char _priv[28];
+++    long _x_align;
+++    long *_xp_align;
+++  } u;
+++} gpgrt_lock_t;
+++
+++#define GPGRT_LOCK_INITIALIZER {1,{{0,0,0,0,0,0,0,0, \
+++                                    0,0,0,0,0,0,0,0, \
+++                                    0,0,0,0,0,0,0,0, \
+++                                    0,0,0,0}}}
+++##
+++## Local Variables:
+++## mode: c
+++## buffer-read-only: t
+++## End:
+++##
++-- 
++2.31.1
++
+diff --git a/contrib/src/gpg-error/rules.mak b/contrib/src/gpg-error/rules.mak
+index 0ceb75d7a3..5d4236b77f 100644
+--- a/contrib/src/gpg-error/rules.mak
++++ b/contrib/src/gpg-error/rules.mak
+@@ -25,6 +25,7 @@ endif
+ 	$(APPLY) $(SRC)/gpg-error/version-bump-gawk-5.patch
+ 	$(APPLY) $(SRC)/gpg-error/win32-extern-struct.patch
+ 	$(APPLY) $(SRC)/gpg-error/darwin-triplet.patch
++	$(APPLY) $(SRC)/gpg-error/emscripten.patch
+ ifndef HAVE_WIN32
+ 	cp -f -- "$(SRC)/gpg-error/lock-obj-pub.posix.h" \
+ 		"$(UNPACK_DIR)/src/lock-obj-pub.native.h"
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0005-contrib-gmp-add-support-for-wasm-emscripten.patch b/vlc_patches/aug/0005-contrib-gmp-add-support-for-wasm-emscripten.patch
new file mode 100644
index 0000000000000000000000000000000000000000..d9c252d772408dab74064597c096a9ca8251fe30
--- /dev/null
+++ b/vlc_patches/aug/0005-contrib-gmp-add-support-for-wasm-emscripten.patch
@@ -0,0 +1,26 @@
+From 540a5ac3eb4d987805e74a8b3d2d585186c2a0c4 Mon Sep 17 00:00:00 2001
+From: Mehdi Sabwat <mehdi@videolabs.io>
+Date: Sun, 30 May 2021 21:22:40 +0200
+Subject: [PATCH 05/77] contrib: gmp: add support for wasm-emscripten
+
+---
+ contrib/src/gmp/rules.mak | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/contrib/src/gmp/rules.mak b/contrib/src/gmp/rules.mak
+index 11fe8acdfa..d6cd702ce0 100644
+--- a/contrib/src/gmp/rules.mak
++++ b/contrib/src/gmp/rules.mak
+@@ -13,6 +13,9 @@ ifeq ($(ARCH),mips64el)
+ GMP_CONF += --disable-assembly
+ endif
+ endif
++ifdef HAVE_EMSCRIPTEN
++GMP_CONF += --disable-assembly
++endif
+ 
+ ifdef HAVE_WIN32
+ ifeq ($(ARCH),arm)
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0006-contrib-gnutls-add-support-for-wasm-emscripten.patch b/vlc_patches/aug/0006-contrib-gnutls-add-support-for-wasm-emscripten.patch
new file mode 100644
index 0000000000000000000000000000000000000000..df5621136f4fedee87199f228b97234d6c08ef04
--- /dev/null
+++ b/vlc_patches/aug/0006-contrib-gnutls-add-support-for-wasm-emscripten.patch
@@ -0,0 +1,29 @@
+From 907f99dd996a0a34c1f50ba34171da80c64ee0ba Mon Sep 17 00:00:00 2001
+From: Mehdi Sabwat <mehdi@videolabs.io>
+Date: Mon, 31 May 2021 00:18:18 +0200
+Subject: [PATCH 06/77] contrib: gnutls: add support for wasm-emscripten
+
+the contrib is temporarily disabled until this pr is merged:
+https://github.com/emscripten-core/emscripten/pull/14352
+---
+ contrib/src/gnutls/rules.mak | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/contrib/src/gnutls/rules.mak b/contrib/src/gnutls/rules.mak
+index edec0ad9ee..fb372f9da1 100644
+--- a/contrib/src/gnutls/rules.mak
++++ b/contrib/src/gnutls/rules.mak
+@@ -82,6 +82,10 @@ ifeq ($(ARCH),aarch64)
+ endif
+ endif
+ 
++ifdef HAVE_EMSCRIPTEN
++	GNUTLS_CONF += --disable-hardware-acceleration
++endif
++
+ .gnutls: gnutls
+ 	cd $< && $(GNUTLS_ENV) ./configure $(GNUTLS_CONF)
+ 	$(call pkg_static,"lib/gnutls.pc")
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0007-aout-add-emscripten-audio-worklet-module.patch b/vlc_patches/aug/0007-aout-add-emscripten-audio-worklet-module.patch
new file mode 100644
index 0000000000000000000000000000000000000000..fca81e83eab396a9b9d4755ccdd22ce8fe04dfb5
--- /dev/null
+++ b/vlc_patches/aug/0007-aout-add-emscripten-audio-worklet-module.patch
@@ -0,0 +1,438 @@
+From ea0d8db56cedf16328c1f88e9bd2406ef3bdcd29 Mon Sep 17 00:00:00 2001
+From: Mehdi Sabwat <mehdi@videolabs.io>
+Date: Fri, 30 Apr 2021 01:15:17 +0200
+Subject: [PATCH 07/77] aout: add emscripten audio worklet module
+
+---
+ modules/audio_output/Makefile.am    |   5 +
+ modules/audio_output/emscripten.cpp | 405 ++++++++++++++++++++++++++++
+ 2 files changed, 410 insertions(+)
+ create mode 100644 modules/audio_output/emscripten.cpp
+
+diff --git a/modules/audio_output/Makefile.am b/modules/audio_output/Makefile.am
+index 24c05429d0..dc91f3cd7e 100644
+--- a/modules/audio_output/Makefile.am
++++ b/modules/audio_output/Makefile.am
+@@ -117,3 +117,8 @@ endif
+ if HAVE_TVOS
+ aout_LTLIBRARIES += libaudiounit_ios_plugin.la
+ endif
++
++libemworklet_audio_plugin_la_SOURCES = audio_output/emscripten.cpp
++if HAVE_EMSCRIPTEN
++aout_LTLIBRARIES += libemworklet_audio_plugin.la
++endif
+diff --git a/modules/audio_output/emscripten.cpp b/modules/audio_output/emscripten.cpp
+new file mode 100644
+index 0000000000..1c890b3f6d
+--- /dev/null
++++ b/modules/audio_output/emscripten.cpp
+@@ -0,0 +1,405 @@
++/*****************************************************************************
++ * emscripten.c: audio output module using audio worklets
++ *****************************************************************************
++ * Copyright © 2020 VLC authors and VideoLAN
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 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 General Public License for more details.
++ *
++ * You should have received a copy of the GNU 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 <assert.h>
++#include <vlc_common.h>
++#include <vlc_plugin.h>
++#include <vlc_aout.h>
++
++#include <emscripten.h>
++#include <emscripten/val.h>
++#include <emscripten/bind.h>
++#include <emscripten/html5.h>
++
++#include <cstdint>
++#include <stdlib.h>
++
++#define STORAGE_SIZE 1024 * 1024
++// Sample rate might change, and it would be good to be able to change it during playback.
++#define AUDIO_WORKLET_SAMPLE_RATE 44100
++// Don't know any way to get the browser's supported number of channels.
++#define AUDIO_WORKLET_NB_CHANNELS 2
++
++using namespace emscripten;
++namespace {	   
++	EM_BOOL requestAnimationFrame_cb( double time, void *userData );
++
++	class AWNodeWrapper {
++	public:
++		val context = val::undefined();
++		val getCtx() const { return context; };
++		void setCtx(val v_context) { context = v_context; };
++		
++		uintptr_t sab_ptr;
++		uintptr_t getSabPtr() const { return sab_ptr; };
++		void setSabPtr(uintptr_t p_sab) { sab_ptr = p_sab; };
++		
++		size_t sab_size;
++		size_t getSabSize() const { return sab_size; };
++		void setSabSize(size_t s_size) { sab_size = s_size; };
++		
++		int8_t channels;
++		int8_t getChannels() const { return channels; };
++		void setChannels(int8_t chan) { channels = chan; };
++		
++		AWNodeWrapper(int sample_rate) {
++			// Prepare audio context options
++			val audio_ctx_options = val::object();
++			audio_ctx_options.set("sampleRate", sample_rate);
++			
++			context = val::global("AudioContext").new_(audio_ctx_options);
++			context.call<void>("suspend");
++		}
++		
++		val operator()( val undefined_promise_argument ) {
++			(val)undefined_promise_argument;
++			
++			// Prepare AWN Options
++			val awn_options = val::object();
++			val awn_opt_outputChannelCount = val::array();
++			awn_opt_outputChannelCount.call<val>("push", channels);
++			awn_options.set("outputChannelCount", awn_opt_outputChannelCount);
++			awn_options.set("numberOfInputs", 0);
++			awn_options.set("numberOfOutputs", 1);
++			
++			val AudioNode = val::global("AudioWorkletNode").new_(context, std::string("worklet-processor"), awn_options);
++			AudioNode.set("channelCount", channels);
++			
++			//Prepare postMessage message
++			val msg = val::object();
++			msg.set("type", std::string("recv-audio-queue"));
++			msg.set("data", val::module_property("wasmMemory")["buffer"]);
++			msg.set("sab_ptr", sab_ptr);
++			msg.set("sab_size", sab_size);
++			
++			AudioNode["port"].call<val>("postMessage", msg);
++			AudioNode.call<val>("connect", context["destination"]);
++			
++			emscripten_request_animation_frame_loop(requestAnimationFrame_cb, this);
++			
++			return val::undefined();
++		}
++	};
++	
++	EMSCRIPTEN_BINDINGS(AWWSCOPE) {
++		class_<AWNodeWrapper>("awn_cb_wrapper")
++			.constructor<int>()
++			.property("context", &AWNodeWrapper::getCtx, &AWNodeWrapper::setCtx)
++			.property("sab_ptr", &AWNodeWrapper::getSabPtr, &AWNodeWrapper::setSabPtr)
++			.property("sab_size", &AWNodeWrapper::getSabSize, &AWNodeWrapper::setSabSize)
++			.property("channels", &AWNodeWrapper::getChannels, &AWNodeWrapper::setChannels)
++			.function("awn_call", &AWNodeWrapper::operator());
++	};
++	
++	typedef struct aout_sys_t
++	{
++		int8_t *sab;
++		size_t sab_size;
++		AWNodeWrapper *awn_inst;
++		float volume;
++		
++	} aout_sys_t;
++
++	EM_BOOL requestAnimationFrame_cb( double time, void *userData ) {
++		(double) time;
++		AWNodeWrapper *inst = reinterpret_cast<AWNodeWrapper *>(userData);
++		uint32_t *sab = reinterpret_cast<uint32_t *>(inst->getSabPtr());
++		val view = val(typed_memory_view(inst->getSabSize(), sab));
++		val context = inst->getCtx();
++		if ( view[0].as<int>() == 1 ) {
++			context.call<val>("resume");
++			sab[0] = 0;
++			return EM_FALSE;
++		}
++		return EM_TRUE;
++	}
++	
++	// For Atomics.store() and .load() only integer types are supported
++	unsigned int js_index_load(int8_t *sab_ptr, int8_t index, size_t sab_size){
++		uint32_t *buffer_view = reinterpret_cast<uint32_t *>(sab_ptr); 
++		val buffer = val(typed_memory_view(sab_size, buffer_view));
++		
++		return val::global("Atomics").call<unsigned int>("load", buffer, index);
++	}
++	
++	void js_index_store(int8_t *sab_ptr, int8_t index, unsigned int value, size_t sab_size) {
++		uint32_t *buffer_view = reinterpret_cast<uint32_t *>(sab_ptr); 
++		val buffer = val(typed_memory_view(sab_size, buffer_view));
++		
++		return val::global("Atomics").call<void>("store", buffer, index, value);
++	}
++
++	// careful when calling this, you cannot wait on any index
++	// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait
++	unsigned int js_index_wait(int8_t *sab_ptr, int8_t index, size_t sab_size) {
++		int32_t *buffer_view = reinterpret_cast<int32_t *>(sab_ptr); 
++		val buffer = val(typed_memory_view(sab_size, buffer_view));
++
++		return val::global("Atomics").call<unsigned int>("wait", buffer, index);
++	}
++
++	void Flush( audio_output_t *aout )
++	{
++		aout_sys_t * sys = reinterpret_cast<aout_sys_t *>(aout->sys);
++		bzero(sys->sab, sys->sab_size);
++	}
++	
++	int Start( audio_output_t *aout, audio_sample_format_t *restrict fmt )
++	{
++		aout_sys_t *sys = reinterpret_cast<aout_sys_t *>(aout->sys);
++		unsigned nbChannels = aout_FormatNbChannels(fmt);
++
++		if (( nbChannels == 0 ) || !AOUT_FMT_LINEAR(fmt))
++			return VLC_EGENERIC;
++		fmt->i_format = VLC_CODEC_FL32;
++		fmt->i_channels = AUDIO_WORKLET_NB_CHANNELS;
++		fmt->i_rate = AUDIO_WORKLET_SAMPLE_RATE;
++
++		// resume audio context (first start, it is paused when initialized)
++		js_index_store(sys->sab, 4, (int)sys->volume * 100, sys->sab_size);
++		js_index_store(sys->sab, 0, 1, sys->sab_size);
++
++		return VLC_SUCCESS;
++	}
++
++	void Stop (audio_output_t *aout)
++	{
++		Flush(aout);
++	}
++	
++	int audio_worklet_push (audio_output_t *aout, const int8_t *data, unsigned data_size) {
++		aout_sys_t *sys = reinterpret_cast<aout_sys_t *>(aout->sys);
++		int8_t *sab_view = sys->sab + 5 * sizeof(int32_t);
++		unsigned head = js_index_load(sys->sab, 1, sys->sab_size);
++
++		// TODO: check that we do not write on unconsumed data.
++		if (head + data_size > STORAGE_SIZE) {
++			// Copy the part of the data at the buffer end
++			unsigned data_size_copy_end = STORAGE_SIZE - head;
++			memcpy(sab_view + head, data, data_size_copy_end);
++			head = 0;
++			
++			// Copy the part of the data at the buffer start
++			unsigned data_size_copy_start = data_size - data_size_copy_end;
++			memcpy(sab_view + head, data, data_size_copy_start);
++			head = data_size_copy_start;
++		}
++		else {
++			memcpy(sab_view + head, data, data_size);
++			head += data_size;
++		}
++		js_index_store(sys->sab, 1, head, sys->sab_size);
++		return 0;  // return success to indicate successful push.
++	}
++
++	void Play( audio_output_t *aout, block_t *block, vlc_tick_t date)
++	{
++		VLC_UNUSED(date);
++		aout_sys_t *sys = reinterpret_cast<aout_sys_t *>(aout->sys);
++		const int8_t* data = (int8_t *)block->p_buffer;
++		size_t data_size = block->i_buffer;
++		
++		unsigned head = js_index_load(sys->sab, 1, sys->sab_size);
++		unsigned tail = js_index_load(sys->sab, 2, sys->sab_size);
++		unsigned new_head = (head + data_size) % STORAGE_SIZE;
++		if (new_head > tail)
++		{
++			// the worklet processor keeps rendering  until tail matches head
++			// it will be notified by an Atomics.notify() from the process() callback
++			js_index_wait(sys->sab, 3, sys->sab_size);
++		}
++		audio_worklet_push(aout, data, data_size);
++		block_Release(block);
++	}
++	
++	void Pause( audio_output_t *aout, bool paused, vlc_tick_t date )
++	{
++		aout_sys_t * sys = reinterpret_cast<aout_sys_t *>(aout->sys);
++		if (paused == false) {
++			js_index_store(sys->sab, 0, 0, sys->sab_size);
++		}
++		else {
++			js_index_store(sys->sab, 0, 1, sys->sab_size);
++		}
++		VLC_UNUSED(date);
++		Flush(aout);
++	}
++	
++	int Time_Get( audio_output_t *aout, vlc_tick_t *delay)
++	{
++		return aout_TimeGetDefault(aout, delay);
++	}
++
++	void Close( vlc_object_t *obj )
++	{
++		audio_output_t *aout = (audio_output_t *)obj;
++		struct aout_sys_t *sys = reinterpret_cast<struct aout_sys_t *>(aout->sys);
++
++		delete sys->awn_inst;
++		free(sys->sab);
++		free(sys);
++	}
++
++	int Volume_Set( audio_output_t *aout, float volume)
++	{
++		struct aout_sys_t *sys = reinterpret_cast<struct aout_sys_t *>(aout->sys);
++
++		if (volume > 1.0f)
++			volume = 1.0f;
++		else if (volume < 0.0f)
++			volume = 0.0f;
++		// TODO: implement gain
++		sys->volume = volume;
++		js_index_store(sys->sab, 4, (int)volume * 100, sys->sab_size);
++		aout_VolumeReport(aout, volume);
++
++		return 0;
++	}
++
++	int Mute_Set( audio_output_t *aout, bool mute)
++	{
++		struct aout_sys_t *sys = reinterpret_cast<struct aout_sys_t *>(aout->sys);
++
++		if (mute == 0)
++			js_index_store(sys->sab, 4, 0, sys->sab_size);
++		else
++			js_index_store(sys->sab, 4, (int)sys->volume * 100, sys->sab_size);
++		aout_MuteReport(aout, mute);
++		return 0;
++	}
++
++	
++	int Open( vlc_object_t *obj )
++	{
++		audio_output_t * aout = (audio_output_t *) obj;
++		
++		/* Allocate structures */
++		aout_sys_t *sys = reinterpret_cast<aout_sys_t *>(malloc( sizeof( *sys ) ));
++		if( unlikely(sys == NULL) )
++			return VLC_ENOMEM;
++		
++		aout->sys = sys;
++		aout->start = Start;
++		aout->stop = Stop;
++		aout->play = Play;
++		aout->pause = Pause;
++		aout->flush = Flush;
++		aout->time_get = Time_Get;
++		aout->volume_set = Volume_Set;
++		aout->mute_set = Mute_Set;
++		
++		sys->awn_inst = new AWNodeWrapper(AUDIO_WORKLET_SAMPLE_RATE);
++		sys->sab_size = 5 * sizeof(int32_t) + STORAGE_SIZE;
++		sys->sab = reinterpret_cast<int8_t *>(malloc( sys->sab_size ));
++		sys->volume = 1.0f;
++
++		if ( unlikely(sys->sab == NULL) )
++			return VLC_ENOMEM;
++		bzero(sys->sab, sys->sab_size);
++		
++		val webaudio_context = sys->awn_inst->getCtx();
++		
++		// Prepare audioWorkletProcessor blob
++		val document = val::global("document");
++		val script = document.call<val>("createElement", std::string("SCRIPT"));
++		script.set("type", std::string("worklet"));
++		std::string processorStr = "class Processor extends AudioWorkletProcessor { \
++	constructor() { \
++		super(); \
++		this.port.onmessage = e => { \
++			if (e.data.type === 'recv-audio-queue') { \
++				this.buf = e.data.data; \
++				this.capacity = e.data.sab_size / 4; \
++				this.flag = new Uint32Array(this.buf, e.data.sab_ptr, 1); \
++				this.head = new Uint32Array(this.buf, e.data.sab_ptr + 4, 1); \
++				this.tail = new Uint32Array(this.buf, e.data.sab_ptr + 8, 1); \
++				this.can_write = new Int32Array(this.buf, e.data.sab_ptr + 12, 1); \
++				this.volume = new Int32Array(this.buf, e.data.sab_ptr + 16, 1); \
++				this.storage = new Float32Array(this.buf, e.data.sab_ptr + 20, this.capacity); \
++			} else { \
++				throw 'unexpected.'; \
++			} \
++		}; \
++	} \
++	process(inputs, outputs, parameters) { \
++		const output = outputs[0]; \
++		const nbChannels = output.length; \
++		const nbSamples = output[0].length; \
++		if (this.head.buffer.byteLength == 0) { \
++			throw new Error('wasmMemory grew'); \
++		} \
++		var head = Atomics.load(this.head, 0) / 4; \
++		var tail = Atomics.load(this.tail, 0) / 4; \
++		var i = 0; \
++		var volume = (Atomics.load(this.volume, 0) / 4) / 100; \
++		while (tail != head && i < nbSamples) \
++		{ \
++			for (let c = 0; c < nbChannels; ++c) { \
++				output[c][i] = this.storage[tail] * volume; \
++				tail++; \
++				if (tail == this.capacity) { \
++					tail = 0; \
++				} \
++			} \
++			i++; \
++		} \
++		Atomics.store(this.tail, 0, tail * 4); \
++		Atomics.notify(this.can_write, 0); \
++		return true; \
++	} \
++} \
++registerProcessor('worklet-processor', Processor);";
++		script.set("innerText", processorStr);
++		val ProcessorTextArray = val::array();
++		ProcessorTextArray.call<val>("push", script["innerText"]);
++		val BlobObject = val::object();
++		BlobObject.set("type", std::string("application/javascript"));
++		val WorkletModuleUrl = val::global("URL").call<val>("createObjectURL", val::global("Blob").new_(ProcessorTextArray, BlobObject));
++		
++		// Prepare audioWorkletProcessor callback
++		val cb_caller = val::module_property("awn_cb_wrapper").new_(AUDIO_WORKLET_SAMPLE_RATE);
++		cb_caller.set("context", val(webaudio_context));
++		cb_caller.set("sab_ptr", val(reinterpret_cast<uintptr_t>(sys->sab)));
++		cb_caller.set("sab_size", val(sys->sab_size));
++		cb_caller.set("channels", val(AUDIO_WORKLET_NB_CHANNELS));
++		val awn_caller = cb_caller["awn_call"];
++		val awn_cb = awn_caller.call<val>("bind", cb_caller);
++		
++		// start audio worklet (since the context is suspended, sound won't start now
++		// Since the WebAudio Context cannot be created in a worker, we create
++		// it in the main_thread and use the SAB to signal it when we want it to start
++		webaudio_context["audioWorklet"].call<val>("addModule", WorkletModuleUrl).call<val>("then", awn_cb);
++
++		return VLC_SUCCESS;
++	}
++}
++
++vlc_module_begin ()
++	set_description( N_("Emscripten Worklet audio output") )
++	set_shortname( "emworklet" )
++	set_capability( "audio output", 100 )
++	set_category( CAT_AUDIO )
++	set_subcategory( SUBCAT_AUDIO_AOUT )
++	set_callbacks( Open, Close )
++vlc_module_end ()
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0008-Normalize-formatting-in-audio_output-emscripten.cpp.patch b/vlc_patches/aug/0008-Normalize-formatting-in-audio_output-emscripten.cpp.patch
new file mode 100644
index 0000000000000000000000000000000000000000..8dc8721de3434a27b6c80cf09e3ef59bb2a4b396
--- /dev/null
+++ b/vlc_patches/aug/0008-Normalize-formatting-in-audio_output-emscripten.cpp.patch
@@ -0,0 +1,261 @@
+From 31ca0b2e4b1d26cbe867d2effd60bd1fae5325e5 Mon Sep 17 00:00:00 2001
+From: Olivier FAURE <couteaubleu@gmail.com>
+Date: Sun, 30 May 2021 12:01:49 +0200
+Subject: [PATCH 08/77] Normalize formatting in audio_output/emscripten.cpp
+
+---
+ modules/audio_output/emscripten.cpp | 74 ++++++++++++++---------------
+ 1 file changed, 37 insertions(+), 37 deletions(-)
+
+diff --git a/modules/audio_output/emscripten.cpp b/modules/audio_output/emscripten.cpp
+index 1c890b3f6d..b704b90699 100644
+--- a/modules/audio_output/emscripten.cpp
++++ b/modules/audio_output/emscripten.cpp
+@@ -42,7 +42,7 @@
+ #define AUDIO_WORKLET_NB_CHANNELS 2
+ 
+ using namespace emscripten;
+-namespace {	   
++namespace {
+ 	EM_BOOL requestAnimationFrame_cb( double time, void *userData );
+ 
+ 	class AWNodeWrapper {
+@@ -50,31 +50,31 @@ namespace {
+ 		val context = val::undefined();
+ 		val getCtx() const { return context; };
+ 		void setCtx(val v_context) { context = v_context; };
+-		
++
+ 		uintptr_t sab_ptr;
+ 		uintptr_t getSabPtr() const { return sab_ptr; };
+ 		void setSabPtr(uintptr_t p_sab) { sab_ptr = p_sab; };
+-		
++
+ 		size_t sab_size;
+ 		size_t getSabSize() const { return sab_size; };
+ 		void setSabSize(size_t s_size) { sab_size = s_size; };
+-		
++
+ 		int8_t channels;
+ 		int8_t getChannels() const { return channels; };
+ 		void setChannels(int8_t chan) { channels = chan; };
+-		
++
+ 		AWNodeWrapper(int sample_rate) {
+ 			// Prepare audio context options
+ 			val audio_ctx_options = val::object();
+ 			audio_ctx_options.set("sampleRate", sample_rate);
+-			
++
+ 			context = val::global("AudioContext").new_(audio_ctx_options);
+ 			context.call<void>("suspend");
+ 		}
+-		
++
+ 		val operator()( val undefined_promise_argument ) {
+ 			(val)undefined_promise_argument;
+-			
++
+ 			// Prepare AWN Options
+ 			val awn_options = val::object();
+ 			val awn_opt_outputChannelCount = val::array();
+@@ -82,26 +82,26 @@ namespace {
+ 			awn_options.set("outputChannelCount", awn_opt_outputChannelCount);
+ 			awn_options.set("numberOfInputs", 0);
+ 			awn_options.set("numberOfOutputs", 1);
+-			
++
+ 			val AudioNode = val::global("AudioWorkletNode").new_(context, std::string("worklet-processor"), awn_options);
+ 			AudioNode.set("channelCount", channels);
+-			
++
+ 			//Prepare postMessage message
+ 			val msg = val::object();
+ 			msg.set("type", std::string("recv-audio-queue"));
+ 			msg.set("data", val::module_property("wasmMemory")["buffer"]);
+ 			msg.set("sab_ptr", sab_ptr);
+ 			msg.set("sab_size", sab_size);
+-			
++
+ 			AudioNode["port"].call<val>("postMessage", msg);
+ 			AudioNode.call<val>("connect", context["destination"]);
+-			
++
+ 			emscripten_request_animation_frame_loop(requestAnimationFrame_cb, this);
+-			
++
+ 			return val::undefined();
+ 		}
+ 	};
+-	
++
+ 	EMSCRIPTEN_BINDINGS(AWWSCOPE) {
+ 		class_<AWNodeWrapper>("awn_cb_wrapper")
+ 			.constructor<int>()
+@@ -111,14 +111,14 @@ namespace {
+ 			.property("channels", &AWNodeWrapper::getChannels, &AWNodeWrapper::setChannels)
+ 			.function("awn_call", &AWNodeWrapper::operator());
+ 	};
+-	
++
+ 	typedef struct aout_sys_t
+ 	{
+ 		int8_t *sab;
+ 		size_t sab_size;
+ 		AWNodeWrapper *awn_inst;
+ 		float volume;
+-		
++
+ 	} aout_sys_t;
+ 
+ 	EM_BOOL requestAnimationFrame_cb( double time, void *userData ) {
+@@ -134,26 +134,26 @@ namespace {
+ 		}
+ 		return EM_TRUE;
+ 	}
+-	
++
+ 	// For Atomics.store() and .load() only integer types are supported
+ 	unsigned int js_index_load(int8_t *sab_ptr, int8_t index, size_t sab_size){
+-		uint32_t *buffer_view = reinterpret_cast<uint32_t *>(sab_ptr); 
++		uint32_t *buffer_view = reinterpret_cast<uint32_t *>(sab_ptr);
+ 		val buffer = val(typed_memory_view(sab_size, buffer_view));
+-		
++
+ 		return val::global("Atomics").call<unsigned int>("load", buffer, index);
+ 	}
+-	
++
+ 	void js_index_store(int8_t *sab_ptr, int8_t index, unsigned int value, size_t sab_size) {
+-		uint32_t *buffer_view = reinterpret_cast<uint32_t *>(sab_ptr); 
++		uint32_t *buffer_view = reinterpret_cast<uint32_t *>(sab_ptr);
+ 		val buffer = val(typed_memory_view(sab_size, buffer_view));
+-		
++
+ 		return val::global("Atomics").call<void>("store", buffer, index, value);
+ 	}
+ 
+ 	// careful when calling this, you cannot wait on any index
+ 	// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait
+ 	unsigned int js_index_wait(int8_t *sab_ptr, int8_t index, size_t sab_size) {
+-		int32_t *buffer_view = reinterpret_cast<int32_t *>(sab_ptr); 
++		int32_t *buffer_view = reinterpret_cast<int32_t *>(sab_ptr);
+ 		val buffer = val(typed_memory_view(sab_size, buffer_view));
+ 
+ 		return val::global("Atomics").call<unsigned int>("wait", buffer, index);
+@@ -164,7 +164,7 @@ namespace {
+ 		aout_sys_t * sys = reinterpret_cast<aout_sys_t *>(aout->sys);
+ 		bzero(sys->sab, sys->sab_size);
+ 	}
+-	
++
+ 	int Start( audio_output_t *aout, audio_sample_format_t *restrict fmt )
+ 	{
+ 		aout_sys_t *sys = reinterpret_cast<aout_sys_t *>(aout->sys);
+@@ -187,7 +187,7 @@ namespace {
+ 	{
+ 		Flush(aout);
+ 	}
+-	
++
+ 	int audio_worklet_push (audio_output_t *aout, const int8_t *data, unsigned data_size) {
+ 		aout_sys_t *sys = reinterpret_cast<aout_sys_t *>(aout->sys);
+ 		int8_t *sab_view = sys->sab + 5 * sizeof(int32_t);
+@@ -199,7 +199,7 @@ namespace {
+ 			unsigned data_size_copy_end = STORAGE_SIZE - head;
+ 			memcpy(sab_view + head, data, data_size_copy_end);
+ 			head = 0;
+-			
++
+ 			// Copy the part of the data at the buffer start
+ 			unsigned data_size_copy_start = data_size - data_size_copy_end;
+ 			memcpy(sab_view + head, data, data_size_copy_start);
+@@ -219,7 +219,7 @@ namespace {
+ 		aout_sys_t *sys = reinterpret_cast<aout_sys_t *>(aout->sys);
+ 		const int8_t* data = (int8_t *)block->p_buffer;
+ 		size_t data_size = block->i_buffer;
+-		
++
+ 		unsigned head = js_index_load(sys->sab, 1, sys->sab_size);
+ 		unsigned tail = js_index_load(sys->sab, 2, sys->sab_size);
+ 		unsigned new_head = (head + data_size) % STORAGE_SIZE;
+@@ -232,7 +232,7 @@ namespace {
+ 		audio_worklet_push(aout, data, data_size);
+ 		block_Release(block);
+ 	}
+-	
++
+ 	void Pause( audio_output_t *aout, bool paused, vlc_tick_t date )
+ 	{
+ 		aout_sys_t * sys = reinterpret_cast<aout_sys_t *>(aout->sys);
+@@ -245,7 +245,7 @@ namespace {
+ 		VLC_UNUSED(date);
+ 		Flush(aout);
+ 	}
+-	
++
+ 	int Time_Get( audio_output_t *aout, vlc_tick_t *delay)
+ 	{
+ 		return aout_TimeGetDefault(aout, delay);
+@@ -289,16 +289,16 @@ namespace {
+ 		return 0;
+ 	}
+ 
+-	
++
+ 	int Open( vlc_object_t *obj )
+ 	{
+ 		audio_output_t * aout = (audio_output_t *) obj;
+-		
++
+ 		/* Allocate structures */
+ 		aout_sys_t *sys = reinterpret_cast<aout_sys_t *>(malloc( sizeof( *sys ) ));
+ 		if( unlikely(sys == NULL) )
+ 			return VLC_ENOMEM;
+-		
++
+ 		aout->sys = sys;
+ 		aout->start = Start;
+ 		aout->stop = Stop;
+@@ -308,7 +308,7 @@ namespace {
+ 		aout->time_get = Time_Get;
+ 		aout->volume_set = Volume_Set;
+ 		aout->mute_set = Mute_Set;
+-		
++
+ 		sys->awn_inst = new AWNodeWrapper(AUDIO_WORKLET_SAMPLE_RATE);
+ 		sys->sab_size = 5 * sizeof(int32_t) + STORAGE_SIZE;
+ 		sys->sab = reinterpret_cast<int8_t *>(malloc( sys->sab_size ));
+@@ -317,9 +317,9 @@ namespace {
+ 		if ( unlikely(sys->sab == NULL) )
+ 			return VLC_ENOMEM;
+ 		bzero(sys->sab, sys->sab_size);
+-		
++
+ 		val webaudio_context = sys->awn_inst->getCtx();
+-		
++
+ 		// Prepare audioWorkletProcessor blob
+ 		val document = val::global("document");
+ 		val script = document.call<val>("createElement", std::string("SCRIPT"));
+@@ -376,7 +376,7 @@ registerProcessor('worklet-processor', Processor);";
+ 		val BlobObject = val::object();
+ 		BlobObject.set("type", std::string("application/javascript"));
+ 		val WorkletModuleUrl = val::global("URL").call<val>("createObjectURL", val::global("Blob").new_(ProcessorTextArray, BlobObject));
+-		
++
+ 		// Prepare audioWorkletProcessor callback
+ 		val cb_caller = val::module_property("awn_cb_wrapper").new_(AUDIO_WORKLET_SAMPLE_RATE);
+ 		cb_caller.set("context", val(webaudio_context));
+@@ -385,7 +385,7 @@ registerProcessor('worklet-processor', Processor);";
+ 		cb_caller.set("channels", val(AUDIO_WORKLET_NB_CHANNELS));
+ 		val awn_caller = cb_caller["awn_call"];
+ 		val awn_cb = awn_caller.call<val>("bind", cb_caller);
+-		
++
+ 		// start audio worklet (since the context is suspended, sound won't start now
+ 		// Since the WebAudio Context cannot be created in a worker, we create
+ 		// it in the main_thread and use the SAB to signal it when we want it to start
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0009-Fix-emscripten-API-to-get-set-volume-levels-in-audio.patch b/vlc_patches/aug/0009-Fix-emscripten-API-to-get-set-volume-levels-in-audio.patch
new file mode 100644
index 0000000000000000000000000000000000000000..7c9242e5a07a080349fbfa611982b887bdf5eef7
--- /dev/null
+++ b/vlc_patches/aug/0009-Fix-emscripten-API-to-get-set-volume-levels-in-audio.patch
@@ -0,0 +1,54 @@
+From 1741a092eae194f532b3e95400d90020be4517ab Mon Sep 17 00:00:00 2001
+From: Olivier FAURE <couteaubleu@gmail.com>
+Date: Sun, 30 May 2021 12:14:26 +0200
+Subject: [PATCH 09/77] Fix emscripten API to get/set volume levels in
+ audio_output module
+
+---
+ modules/audio_output/emscripten.cpp | 9 ++++++---
+ 1 file changed, 6 insertions(+), 3 deletions(-)
+
+diff --git a/modules/audio_output/emscripten.cpp b/modules/audio_output/emscripten.cpp
+index b704b90699..3fefa60065 100644
+--- a/modules/audio_output/emscripten.cpp
++++ b/modules/audio_output/emscripten.cpp
+@@ -162,7 +162,7 @@ namespace {
+ 	void Flush( audio_output_t *aout )
+ 	{
+ 		aout_sys_t * sys = reinterpret_cast<aout_sys_t *>(aout->sys);
+-		bzero(sys->sab, sys->sab_size);
++		bzero(sys->sab + (5 * sizeof(int32_t)), STORAGE_SIZE);
+ 	}
+ 
+ 	int Start( audio_output_t *aout, audio_sample_format_t *restrict fmt )
+@@ -177,7 +177,7 @@ namespace {
+ 		fmt->i_rate = AUDIO_WORKLET_SAMPLE_RATE;
+ 
+ 		// resume audio context (first start, it is paused when initialized)
+-		js_index_store(sys->sab, 4, (int)sys->volume * 100, sys->sab_size);
++		js_index_store(sys->sab, 4, (int)(sys->volume * 100), sys->sab_size);
+ 		js_index_store(sys->sab, 0, 1, sys->sab_size);
+ 
+ 		return VLC_SUCCESS;
+@@ -229,6 +229,10 @@ namespace {
+ 			// it will be notified by an Atomics.notify() from the process() callback
+ 			js_index_wait(sys->sab, 3, sys->sab_size);
+ 		}
++
++		// set volume
++		js_index_store(sys->sab, 4, (int)(sys->volume * 100), sys->sab_size);
++
+ 		audio_worklet_push(aout, data, data_size);
+ 		block_Release(block);
+ 	}
+@@ -271,7 +275,6 @@ namespace {
+ 			volume = 0.0f;
+ 		// TODO: implement gain
+ 		sys->volume = volume;
+-		js_index_store(sys->sab, 4, (int)volume * 100, sys->sab_size);
+ 		aout_VolumeReport(aout, volume);
+ 
+ 		return 0;
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0010-Move-some-audio-worklet-logic-out-of-JS-code.patch b/vlc_patches/aug/0010-Move-some-audio-worklet-logic-out-of-JS-code.patch
new file mode 100644
index 0000000000000000000000000000000000000000..0b970d19afc344a63d5f3efd10ccc74220a6fe38
--- /dev/null
+++ b/vlc_patches/aug/0010-Move-some-audio-worklet-logic-out-of-JS-code.patch
@@ -0,0 +1,84 @@
+From 44959e310fbf34b740b25010387fd08914fb5f3d Mon Sep 17 00:00:00 2001
+From: Olivier FAURE <couteaubleu@gmail.com>
+Date: Sun, 30 May 2021 14:18:32 +0200
+Subject: [PATCH 10/77] Move some audio worklet logic out of JS code.
+
+This isn't terribly useful on its own, but serves to prepare the next
+commit.
+---
+ modules/audio_output/emscripten.cpp | 35 ++++++++++++++++++-----------
+ 1 file changed, 22 insertions(+), 13 deletions(-)
+
+diff --git a/modules/audio_output/emscripten.cpp b/modules/audio_output/emscripten.cpp
+index 3fefa60065..625dda30c0 100644
+--- a/modules/audio_output/emscripten.cpp
++++ b/modules/audio_output/emscripten.cpp
+@@ -86,12 +86,23 @@ namespace {
+ 			val AudioNode = val::global("AudioWorkletNode").new_(context, std::string("worklet-processor"), awn_options);
+ 			AudioNode.set("channelCount", channels);
+ 
++      val Uint32Array = val::global("Uint32Array");
++      val Int32Array = val::global("Int32Array");
++      val Float32Array = val::global("Float32Array");
++
++      auto wasm_memory_buffer = val::module_property("wasmMemory")["buffer"];
++      uint32_t storage_capacity = sab_size / 4;
++
+ 			//Prepare postMessage message
+ 			val msg = val::object();
+ 			msg.set("type", std::string("recv-audio-queue"));
+-			msg.set("data", val::module_property("wasmMemory")["buffer"]);
+-			msg.set("sab_ptr", sab_ptr);
+-			msg.set("sab_size", sab_size);
++
++			msg.set("flag", Uint32Array.new_(wasm_memory_buffer, sab_ptr + 0, 1));
++			msg.set("head", Uint32Array.new_(wasm_memory_buffer, sab_ptr + 4, 1));
++			msg.set("tail", Uint32Array.new_(wasm_memory_buffer, sab_ptr + 8, 1));
++			msg.set("can_write", Int32Array.new_(wasm_memory_buffer, sab_ptr + 12, 1));
++			msg.set("volume", Int32Array.new_(wasm_memory_buffer, sab_ptr + 16, 1));
++			msg.set("storage", Float32Array.new_(wasm_memory_buffer, sab_ptr + 20, storage_capacity));
+ 
+ 			AudioNode["port"].call<val>("postMessage", msg);
+ 			AudioNode.call<val>("connect", context["destination"]);
+@@ -122,7 +133,7 @@ namespace {
+ 	} aout_sys_t;
+ 
+ 	EM_BOOL requestAnimationFrame_cb( double time, void *userData ) {
+-		(double) time;
++		(void) time;
+ 		AWNodeWrapper *inst = reinterpret_cast<AWNodeWrapper *>(userData);
+ 		uint32_t *sab = reinterpret_cast<uint32_t *>(inst->getSabPtr());
+ 		val view = val(typed_memory_view(inst->getSabSize(), sab));
+@@ -332,14 +343,12 @@ namespace {
+ 		super(); \
+ 		this.port.onmessage = e => { \
+ 			if (e.data.type === 'recv-audio-queue') { \
+-				this.buf = e.data.data; \
+-				this.capacity = e.data.sab_size / 4; \
+-				this.flag = new Uint32Array(this.buf, e.data.sab_ptr, 1); \
+-				this.head = new Uint32Array(this.buf, e.data.sab_ptr + 4, 1); \
+-				this.tail = new Uint32Array(this.buf, e.data.sab_ptr + 8, 1); \
+-				this.can_write = new Int32Array(this.buf, e.data.sab_ptr + 12, 1); \
+-				this.volume = new Int32Array(this.buf, e.data.sab_ptr + 16, 1); \
+-				this.storage = new Float32Array(this.buf, e.data.sab_ptr + 20, this.capacity); \
++				this.flag = e.data.flag; \
++				this.head = e.data.head; \
++				this.tail = e.data.tail; \
++				this.can_write = e.data.can_write; \
++				this.volume = e.data.volume; \
++				this.storage = e.data.storage; \
+ 			} else { \
+ 				throw 'unexpected.'; \
+ 			} \
+@@ -361,7 +370,7 @@ namespace {
+ 			for (let c = 0; c < nbChannels; ++c) { \
+ 				output[c][i] = this.storage[tail] * volume; \
+ 				tail++; \
+-				if (tail == this.capacity) { \
++				if (tail == this.storage.length) { \
+ 					tail = 0; \
+ 				} \
+ 			} \
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0011-Replace-untyped-js_index_load-store-functions-with-C.patch b/vlc_patches/aug/0011-Replace-untyped-js_index_load-store-functions-with-C.patch
new file mode 100644
index 0000000000000000000000000000000000000000..c91b608e0741a1a714c3cdb35b7388ae97b146e4
--- /dev/null
+++ b/vlc_patches/aug/0011-Replace-untyped-js_index_load-store-functions-with-C.patch
@@ -0,0 +1,291 @@
+From 79a00fe61cde93bd8416a88b41a4a5a68737befa Mon Sep 17 00:00:00 2001
+From: Olivier FAURE <couteaubleu@gmail.com>
+Date: Sun, 30 May 2021 15:15:22 +0200
+Subject: [PATCH 11/77] Replace untyped js_index_load/store functions with C++
+ atomics
+
+---
+ modules/audio_output/emscripten.cpp | 116 ++++++++++++++--------------
+ 1 file changed, 59 insertions(+), 57 deletions(-)
+
+diff --git a/modules/audio_output/emscripten.cpp b/modules/audio_output/emscripten.cpp
+index 625dda30c0..fa0f5bd3e4 100644
+--- a/modules/audio_output/emscripten.cpp
++++ b/modules/audio_output/emscripten.cpp
+@@ -22,6 +22,8 @@
+ # include "config.h"
+ #endif
+ 
++#include <atomic>
++
+ #include <assert.h>
+ #include <vlc_common.h>
+ #include <vlc_plugin.h>
+@@ -45,6 +47,18 @@ using namespace emscripten;
+ namespace {
+ 	EM_BOOL requestAnimationFrame_cb( double time, void *userData );
+ 
++	typedef struct sound_buffer_t
++	{
++    // TODO - should be bool?
++    std::atomic<uint32_t> is_paused;
++    std::atomic<uint32_t> head;
++    std::atomic<uint32_t> tail;
++    std::atomic<uint32_t> can_write;
++    std::atomic<uint32_t> volume;
++    int8_t storage[STORAGE_SIZE];
++
++	} sound_buffer_t;
++
+ 	class AWNodeWrapper {
+ 	public:
+ 		val context = val::undefined();
+@@ -55,10 +69,6 @@ namespace {
+ 		uintptr_t getSabPtr() const { return sab_ptr; };
+ 		void setSabPtr(uintptr_t p_sab) { sab_ptr = p_sab; };
+ 
+-		size_t sab_size;
+-		size_t getSabSize() const { return sab_size; };
+-		void setSabSize(size_t s_size) { sab_size = s_size; };
+-
+ 		int8_t channels;
+ 		int8_t getChannels() const { return channels; };
+ 		void setChannels(int8_t chan) { channels = chan; };
+@@ -90,19 +100,26 @@ namespace {
+       val Int32Array = val::global("Int32Array");
+       val Float32Array = val::global("Float32Array");
+ 
+-      auto wasm_memory_buffer = val::module_property("wasmMemory")["buffer"];
+-      uint32_t storage_capacity = sab_size / 4;
++      auto wasm_mem = val::module_property("wasmMemory")["buffer"];
+ 
+ 			//Prepare postMessage message
+ 			val msg = val::object();
+ 			msg.set("type", std::string("recv-audio-queue"));
+ 
+-			msg.set("flag", Uint32Array.new_(wasm_memory_buffer, sab_ptr + 0, 1));
+-			msg.set("head", Uint32Array.new_(wasm_memory_buffer, sab_ptr + 4, 1));
+-			msg.set("tail", Uint32Array.new_(wasm_memory_buffer, sab_ptr + 8, 1));
+-			msg.set("can_write", Int32Array.new_(wasm_memory_buffer, sab_ptr + 12, 1));
+-			msg.set("volume", Int32Array.new_(wasm_memory_buffer, sab_ptr + 16, 1));
+-			msg.set("storage", Float32Array.new_(wasm_memory_buffer, sab_ptr + 20, storage_capacity));
++      msg.set("is_paused",
++        Uint32Array.new_(wasm_mem, sab_ptr + offsetof(sound_buffer_t, is_paused), 1));
++      msg.set("head",
++        Uint32Array.new_(wasm_mem, sab_ptr + offsetof(sound_buffer_t, head), 1));
++      msg.set("tail",
++        Uint32Array.new_(wasm_mem, sab_ptr + offsetof(sound_buffer_t, tail), 1));
++      msg.set("can_write",
++        Int32Array.new_(wasm_mem, sab_ptr + offsetof(sound_buffer_t, can_write), 1));
++      msg.set("volume",
++        Int32Array.new_(wasm_mem, sab_ptr + offsetof(sound_buffer_t, volume), 1));
++
++      uint32_t storage_capacity = STORAGE_SIZE / 4;
++      msg.set("storage",
++        Float32Array.new_(wasm_mem, sab_ptr + offsetof(sound_buffer_t, storage), storage_capacity));
+ 
+ 			AudioNode["port"].call<val>("postMessage", msg);
+ 			AudioNode.call<val>("connect", context["destination"]);
+@@ -118,25 +135,25 @@ namespace {
+ 			.constructor<int>()
+ 			.property("context", &AWNodeWrapper::getCtx, &AWNodeWrapper::setCtx)
+ 			.property("sab_ptr", &AWNodeWrapper::getSabPtr, &AWNodeWrapper::setSabPtr)
+-			.property("sab_size", &AWNodeWrapper::getSabSize, &AWNodeWrapper::setSabSize)
+ 			.property("channels", &AWNodeWrapper::getChannels, &AWNodeWrapper::setChannels)
+ 			.function("awn_call", &AWNodeWrapper::operator());
+ 	};
+ 
+ 	typedef struct aout_sys_t
+ 	{
+-		int8_t *sab;
+-		size_t sab_size;
++		sound_buffer_t *sab; // TODO - rename to sound_buff
+ 		AWNodeWrapper *awn_inst;
+ 		float volume;
+ 
+ 	} aout_sys_t;
+ 
+ 	EM_BOOL requestAnimationFrame_cb( double time, void *userData ) {
+-		(void) time;
++		VLC_UNUSED(time);
++    // FIXME - this function seems to mix two different views on the
++    // same memory, not sure why
+ 		AWNodeWrapper *inst = reinterpret_cast<AWNodeWrapper *>(userData);
+ 		uint32_t *sab = reinterpret_cast<uint32_t *>(inst->getSabPtr());
+-		val view = val(typed_memory_view(inst->getSabSize(), sab));
++		val view = val(typed_memory_view(sizeof(sound_buffer_t), sab));
+ 		val context = inst->getCtx();
+ 		if ( view[0].as<int>() == 1 ) {
+ 			context.call<val>("resume");
+@@ -146,34 +163,19 @@ namespace {
+ 		return EM_TRUE;
+ 	}
+ 
+-	// For Atomics.store() and .load() only integer types are supported
+-	unsigned int js_index_load(int8_t *sab_ptr, int8_t index, size_t sab_size){
+-		uint32_t *buffer_view = reinterpret_cast<uint32_t *>(sab_ptr);
+-		val buffer = val(typed_memory_view(sab_size, buffer_view));
+-
+-		return val::global("Atomics").call<unsigned int>("load", buffer, index);
+-	}
+-
+-	void js_index_store(int8_t *sab_ptr, int8_t index, unsigned int value, size_t sab_size) {
+-		uint32_t *buffer_view = reinterpret_cast<uint32_t *>(sab_ptr);
+-		val buffer = val(typed_memory_view(sab_size, buffer_view));
+-
+-		return val::global("Atomics").call<void>("store", buffer, index, value);
+-	}
+-
+ 	// careful when calling this, you cannot wait on any index
+ 	// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait
+-	unsigned int js_index_wait(int8_t *sab_ptr, int8_t index, size_t sab_size) {
++	uint32_t js_index_wait(sound_buffer_t *sab_ptr, int8_t index) {
+ 		int32_t *buffer_view = reinterpret_cast<int32_t *>(sab_ptr);
+-		val buffer = val(typed_memory_view(sab_size, buffer_view));
++		val buffer = val(typed_memory_view(STORAGE_SIZE, buffer_view));
+ 
+-		return val::global("Atomics").call<unsigned int>("wait", buffer, index);
++		return val::global("Atomics").call<uint32_t>("wait", buffer, index);
+ 	}
+ 
+ 	void Flush( audio_output_t *aout )
+ 	{
+ 		aout_sys_t * sys = reinterpret_cast<aout_sys_t *>(aout->sys);
+-		bzero(sys->sab + (5 * sizeof(int32_t)), STORAGE_SIZE);
++		bzero(&sys->sab->storage, sizeof(sys->sab->storage));
+ 	}
+ 
+ 	int Start( audio_output_t *aout, audio_sample_format_t *restrict fmt )
+@@ -188,8 +190,8 @@ namespace {
+ 		fmt->i_rate = AUDIO_WORKLET_SAMPLE_RATE;
+ 
+ 		// resume audio context (first start, it is paused when initialized)
+-		js_index_store(sys->sab, 4, (int)(sys->volume * 100), sys->sab_size);
+-		js_index_store(sys->sab, 0, 1, sys->sab_size);
++		sys->sab->volume.store((int)(sys->volume * 100));
++		sys->sab->is_paused.store(1);
+ 
+ 		return VLC_SUCCESS;
+ 	}
+@@ -199,10 +201,10 @@ namespace {
+ 		Flush(aout);
+ 	}
+ 
+-	int audio_worklet_push (audio_output_t *aout, const int8_t *data, unsigned data_size) {
++	int audio_worklet_push (audio_output_t *aout, const int8_t *data, uint32_t data_size) {
+ 		aout_sys_t *sys = reinterpret_cast<aout_sys_t *>(aout->sys);
+-		int8_t *sab_view = sys->sab + 5 * sizeof(int32_t);
+-		unsigned head = js_index_load(sys->sab, 1, sys->sab_size);
++		int8_t *sab_view = sys->sab->storage;
++		uint32_t head = sys->sab->head.load();
+ 
+ 		// TODO: check that we do not write on unconsumed data.
+ 		if (head + data_size > STORAGE_SIZE) {
+@@ -220,7 +222,7 @@ namespace {
+ 			memcpy(sab_view + head, data, data_size);
+ 			head += data_size;
+ 		}
+-		js_index_store(sys->sab, 1, head, sys->sab_size);
++		sys->sab->head.store(head);
+ 		return 0;  // return success to indicate successful push.
+ 	}
+ 
+@@ -231,18 +233,18 @@ namespace {
+ 		const int8_t* data = (int8_t *)block->p_buffer;
+ 		size_t data_size = block->i_buffer;
+ 
+-		unsigned head = js_index_load(sys->sab, 1, sys->sab_size);
+-		unsigned tail = js_index_load(sys->sab, 2, sys->sab_size);
+-		unsigned new_head = (head + data_size) % STORAGE_SIZE;
++		uint32_t head = sys->sab->head.load();
++		uint32_t tail = sys->sab->tail.load();
++		uint32_t new_head = (head + data_size) % STORAGE_SIZE;
+ 		if (new_head > tail)
+ 		{
+ 			// the worklet processor keeps rendering  until tail matches head
+ 			// it will be notified by an Atomics.notify() from the process() callback
+-			js_index_wait(sys->sab, 3, sys->sab_size);
++			js_index_wait(sys->sab, 3);
+ 		}
+ 
+ 		// set volume
+-		js_index_store(sys->sab, 4, (int)(sys->volume * 100), sys->sab_size);
++		sys->sab->volume.store((int)(sys->volume * 100));
+ 
+ 		audio_worklet_push(aout, data, data_size);
+ 		block_Release(block);
+@@ -250,14 +252,14 @@ namespace {
+ 
+ 	void Pause( audio_output_t *aout, bool paused, vlc_tick_t date )
+ 	{
++		VLC_UNUSED(date);
+ 		aout_sys_t * sys = reinterpret_cast<aout_sys_t *>(aout->sys);
+ 		if (paused == false) {
+-			js_index_store(sys->sab, 0, 0, sys->sab_size);
++			sys->sab->is_paused.store(0);
+ 		}
+ 		else {
+-			js_index_store(sys->sab, 0, 1, sys->sab_size);
++			sys->sab->is_paused.store(1);
+ 		}
+-		VLC_UNUSED(date);
+ 		Flush(aout);
+ 	}
+ 
+@@ -271,6 +273,7 @@ namespace {
+ 		audio_output_t *aout = (audio_output_t *)obj;
+ 		struct aout_sys_t *sys = reinterpret_cast<struct aout_sys_t *>(aout->sys);
+ 
++    // FIXME
+ 		delete sys->awn_inst;
+ 		free(sys->sab);
+ 		free(sys);
+@@ -296,9 +299,10 @@ namespace {
+ 		struct aout_sys_t *sys = reinterpret_cast<struct aout_sys_t *>(aout->sys);
+ 
+ 		if (mute == 0)
+-			js_index_store(sys->sab, 4, 0, sys->sab_size);
++			sys->sab->volume.store(0);
+ 		else
+-			js_index_store(sys->sab, 4, (int)sys->volume * 100, sys->sab_size);
++			sys->sab->volume.store((int)(sys->volume * 100));
++
+ 		aout_MuteReport(aout, mute);
+ 		return 0;
+ 	}
+@@ -324,13 +328,12 @@ namespace {
+ 		aout->mute_set = Mute_Set;
+ 
+ 		sys->awn_inst = new AWNodeWrapper(AUDIO_WORKLET_SAMPLE_RATE);
+-		sys->sab_size = 5 * sizeof(int32_t) + STORAGE_SIZE;
+-		sys->sab = reinterpret_cast<int8_t *>(malloc( sys->sab_size ));
++		sys->sab = (sound_buffer_t*)malloc(sizeof(sound_buffer_t));
+ 		sys->volume = 1.0f;
+ 
+ 		if ( unlikely(sys->sab == NULL) )
+ 			return VLC_ENOMEM;
+-		bzero(sys->sab, sys->sab_size);
++		bzero(sys->sab, sizeof(sound_buffer_t));
+ 
+ 		val webaudio_context = sys->awn_inst->getCtx();
+ 
+@@ -343,7 +346,7 @@ namespace {
+ 		super(); \
+ 		this.port.onmessage = e => { \
+ 			if (e.data.type === 'recv-audio-queue') { \
+-				this.flag = e.data.flag; \
++				this.is_paused = e.data.is_paused; \
+ 				this.head = e.data.head; \
+ 				this.tail = e.data.tail; \
+ 				this.can_write = e.data.can_write; \
+@@ -393,7 +396,6 @@ registerProcessor('worklet-processor', Processor);";
+ 		val cb_caller = val::module_property("awn_cb_wrapper").new_(AUDIO_WORKLET_SAMPLE_RATE);
+ 		cb_caller.set("context", val(webaudio_context));
+ 		cb_caller.set("sab_ptr", val(reinterpret_cast<uintptr_t>(sys->sab)));
+-		cb_caller.set("sab_size", val(sys->sab_size));
+ 		cb_caller.set("channels", val(AUDIO_WORKLET_NB_CHANNELS));
+ 		val awn_caller = cb_caller["awn_call"];
+ 		val awn_cb = awn_caller.call<val>("bind", cb_caller);
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0012-Fix-volume-handling-in-emscripten-audio_output-modul.patch b/vlc_patches/aug/0012-Fix-volume-handling-in-emscripten-audio_output-modul.patch
new file mode 100644
index 0000000000000000000000000000000000000000..4cfd159570e553002499a1df37d22de0e28208b5
--- /dev/null
+++ b/vlc_patches/aug/0012-Fix-volume-handling-in-emscripten-audio_output-modul.patch
@@ -0,0 +1,124 @@
+From 57c7d65d059510b0a39567a0d69351780d8b44c0 Mon Sep 17 00:00:00 2001
+From: Olivier FAURE <couteaubleu@gmail.com>
+Date: Sun, 30 May 2021 17:18:23 +0200
+Subject: [PATCH 12/77] Fix volume handling in emscripten audio_output module
+
+Fix bug that had volume divided by 4
+Store mute state separately
+---
+ modules/audio_output/emscripten.cpp | 28 +++++++++++++++-------------
+ 1 file changed, 15 insertions(+), 13 deletions(-)
+
+diff --git a/modules/audio_output/emscripten.cpp b/modules/audio_output/emscripten.cpp
+index fa0f5bd3e4..c39ca884b6 100644
+--- a/modules/audio_output/emscripten.cpp
++++ b/modules/audio_output/emscripten.cpp
+@@ -55,6 +55,7 @@ namespace {
+     std::atomic<uint32_t> tail;
+     std::atomic<uint32_t> can_write;
+     std::atomic<uint32_t> volume;
++    std::atomic<uint32_t> is_muted;
+     int8_t storage[STORAGE_SIZE];
+ 
+ 	} sound_buffer_t;
+@@ -116,6 +117,8 @@ namespace {
+         Int32Array.new_(wasm_mem, sab_ptr + offsetof(sound_buffer_t, can_write), 1));
+       msg.set("volume",
+         Int32Array.new_(wasm_mem, sab_ptr + offsetof(sound_buffer_t, volume), 1));
++      msg.set("is_muted",
++        Uint32Array.new_(wasm_mem, sab_ptr + offsetof(sound_buffer_t, is_muted), 1));
+ 
+       uint32_t storage_capacity = STORAGE_SIZE / 4;
+       msg.set("storage",
+@@ -143,7 +146,6 @@ namespace {
+ 	{
+ 		sound_buffer_t *sab; // TODO - rename to sound_buff
+ 		AWNodeWrapper *awn_inst;
+-		float volume;
+ 
+ 	} aout_sys_t;
+ 
+@@ -190,7 +192,6 @@ namespace {
+ 		fmt->i_rate = AUDIO_WORKLET_SAMPLE_RATE;
+ 
+ 		// resume audio context (first start, it is paused when initialized)
+-		sys->sab->volume.store((int)(sys->volume * 100));
+ 		sys->sab->is_paused.store(1);
+ 
+ 		return VLC_SUCCESS;
+@@ -240,12 +241,10 @@ namespace {
+ 		{
+ 			// the worklet processor keeps rendering  until tail matches head
+ 			// it will be notified by an Atomics.notify() from the process() callback
++      // FIXME - This is layout-dependent, which isn't ideal
+ 			js_index_wait(sys->sab, 3);
+ 		}
+ 
+-		// set volume
+-		sys->sab->volume.store((int)(sys->volume * 100));
+-
+ 		audio_worklet_push(aout, data, data_size);
+ 		block_Release(block);
+ 	}
+@@ -288,7 +287,9 @@ namespace {
+ 		else if (volume < 0.0f)
+ 			volume = 0.0f;
+ 		// TODO: implement gain
+-		sys->volume = volume;
++		// Note: We store volume as an integer between 0..100 because
++		// for some reason Float32Array doesn't allow atomic operations
++		sys->sab->volume.store((int)(volume * 100));
+ 		aout_VolumeReport(aout, volume);
+ 
+ 		return 0;
+@@ -298,12 +299,9 @@ namespace {
+ 	{
+ 		struct aout_sys_t *sys = reinterpret_cast<struct aout_sys_t *>(aout->sys);
+ 
+-		if (mute == 0)
+-			sys->sab->volume.store(0);
+-		else
+-			sys->sab->volume.store((int)(sys->volume * 100));
+-
++		sys->sab->is_muted.store(mute);
+ 		aout_MuteReport(aout, mute);
++
+ 		return 0;
+ 	}
+ 
+@@ -329,11 +327,11 @@ namespace {
+ 
+ 		sys->awn_inst = new AWNodeWrapper(AUDIO_WORKLET_SAMPLE_RATE);
+ 		sys->sab = (sound_buffer_t*)malloc(sizeof(sound_buffer_t));
+-		sys->volume = 1.0f;
+ 
+ 		if ( unlikely(sys->sab == NULL) )
+ 			return VLC_ENOMEM;
+ 		bzero(sys->sab, sizeof(sound_buffer_t));
++		sys->sab->volume = 100;
+ 
+ 		val webaudio_context = sys->awn_inst->getCtx();
+ 
+@@ -351,6 +349,7 @@ namespace {
+ 				this.tail = e.data.tail; \
+ 				this.can_write = e.data.can_write; \
+ 				this.volume = e.data.volume; \
++				this.is_muted = e.data.is_muted; \
+ 				this.storage = e.data.storage; \
+ 			} else { \
+ 				throw 'unexpected.'; \
+@@ -367,7 +366,10 @@ namespace {
+ 		var head = Atomics.load(this.head, 0) / 4; \
+ 		var tail = Atomics.load(this.tail, 0) / 4; \
+ 		var i = 0; \
+-		var volume = (Atomics.load(this.volume, 0) / 4) / 100; \
++		var volume = Atomics.load(this.volume, 0) / 100; \
++		if (Atomics.load(this.is_paused, 0) != 0 || Atomics.load(this.is_muted, 0) != 0) { \
++			volume = 0; \
++		} \
+ 		while (tail != head && i < nbSamples) \
+ 		{ \
+ 			for (let c = 0; c < nbChannels; ++c) { \
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0013-aout-fix-js_index_wait.patch b/vlc_patches/aug/0013-aout-fix-js_index_wait.patch
new file mode 100644
index 0000000000000000000000000000000000000000..ea66d4fec0f661c80c9ad517a961269a03e6e669
--- /dev/null
+++ b/vlc_patches/aug/0013-aout-fix-js_index_wait.patch
@@ -0,0 +1,51 @@
+From 7a138898779f9bf1d4af79361e05b580f068461c Mon Sep 17 00:00:00 2001
+From: Mehdi Sabwat <mehdi@videolabs.io>
+Date: Thu, 1 Jul 2021 11:28:53 +0200
+Subject: [PATCH 13/77] aout: fix js_index_wait
+
+- fix return type
+- notifying without changing the value won't update the value
+---
+ modules/audio_output/emscripten.cpp | 9 +++++----
+ 1 file changed, 5 insertions(+), 4 deletions(-)
+
+diff --git a/modules/audio_output/emscripten.cpp b/modules/audio_output/emscripten.cpp
+index c39ca884b6..6acea387e3 100644
+--- a/modules/audio_output/emscripten.cpp
++++ b/modules/audio_output/emscripten.cpp
+@@ -167,11 +167,11 @@ namespace {
+ 
+ 	// careful when calling this, you cannot wait on any index
+ 	// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait
+-	uint32_t js_index_wait(sound_buffer_t *sab_ptr, int8_t index) {
++	void js_index_wait(sound_buffer_t *sab_ptr, int8_t index) {
+ 		int32_t *buffer_view = reinterpret_cast<int32_t *>(sab_ptr);
+ 		val buffer = val(typed_memory_view(STORAGE_SIZE, buffer_view));
+ 
+-		return val::global("Atomics").call<uint32_t>("wait", buffer, index);
++		val::global("Atomics").call<val>("wait", buffer, index, 0);
+ 	}
+ 
+ 	void Flush( audio_output_t *aout )
+@@ -241,7 +241,7 @@ namespace {
+ 		{
+ 			// the worklet processor keeps rendering  until tail matches head
+ 			// it will be notified by an Atomics.notify() from the process() callback
+-      // FIXME - This is layout-dependent, which isn't ideal
++			// FIXME - This is layout-dependent, which isn't ideal
+ 			js_index_wait(sys->sab, 3);
+ 		}
+ 
+@@ -382,7 +382,8 @@ namespace {
+ 			i++; \
+ 		} \
+ 		Atomics.store(this.tail, 0, tail * 4); \
+-		Atomics.notify(this.can_write, 0); \
++		Atomics.store(this.can_write, 0, 1); \
++		Atomics.notify(this.can_write, 0);   \
+ 		return true; \
+ 	} \
+ } \
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0014-opengl-set-egl-display.patch b/vlc_patches/aug/0014-opengl-set-egl-display.patch
new file mode 100644
index 0000000000000000000000000000000000000000..f45519a4f8830b03bf01661b08fe68ff6bfd996a
--- /dev/null
+++ b/vlc_patches/aug/0014-opengl-set-egl-display.patch
@@ -0,0 +1,29 @@
+From 1f56116d4a13943522a5e59d09b941adf4535a35 Mon Sep 17 00:00:00 2001
+From: Mehdi Sabwat <mehdi@videolabs.io>
+Date: Tue, 27 Apr 2021 16:27:19 +0200
+Subject: [PATCH 14/77] opengl: set egl display
+
+Emscripten does not implement EGL extensions.
+https://emscripten.org/docs/porting/multimedia_and_graphics/EGL-Support-in-Emscripten.html#egl-extensions
+---
+ modules/video_output/opengl/egl_display_generic.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/modules/video_output/opengl/egl_display_generic.c b/modules/video_output/opengl/egl_display_generic.c
+index dfb841feef..730fb381d2 100644
+--- a/modules/video_output/opengl/egl_display_generic.c
++++ b/modules/video_output/opengl/egl_display_generic.c
+@@ -52,8 +52,8 @@ static vlc_egl_display_open_fn Open;
+ static int
+ Open(struct vlc_egl_display *display)
+ {
+-#ifdef __ANDROID__
+-    /* The default display is refcounted on Android */
++#if defined(__ANDROID__) || defined(__EMSCRIPTEN__)
++    /* The default display is refcounted on Android and Emscripten */
+     display->display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ #elif defined(EGL_KHR_display_reference)
+     const char *extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0015-vout-add-emscripten-webgl-module.patch b/vlc_patches/aug/0015-vout-add-emscripten-webgl-module.patch
new file mode 100644
index 0000000000000000000000000000000000000000..9f73141f5b09e9f0ed6c1df904fd0449877709b5
--- /dev/null
+++ b/vlc_patches/aug/0015-vout-add-emscripten-webgl-module.patch
@@ -0,0 +1,221 @@
+From 4a8434bf69a9f4b6e684bc750b4cbe3aa3c49685 Mon Sep 17 00:00:00 2001
+From: Mehdi Sabwat <mehdi@videolabs.io>
+Date: Tue, 27 Apr 2021 16:45:02 +0200
+Subject: [PATCH 15/77] vout: add emscripten webgl module
+
+this module uses the OFFSCREEN_FRAMEBUFFER option which
+adds a backbuffer to the webgl context, that will be used
+for rendering.
+
+it works as a polyfill for offscreen canvas.
+
+Co-Authored-By: Etienne Brateau <etienne.brateau@gmail.com>
+---
+ modules/video_output/Makefile.am  |   7 ++
+ modules/video_output/emscripten.c | 176 ++++++++++++++++++++++++++++++
+ 2 files changed, 183 insertions(+)
+ create mode 100644 modules/video_output/emscripten.c
+
+diff --git a/modules/video_output/Makefile.am b/modules/video_output/Makefile.am
+index d4ada0394b..3134528bc1 100644
+--- a/modules/video_output/Makefile.am
++++ b/modules/video_output/Makefile.am
+@@ -345,6 +345,13 @@ libcaca_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(voutdir)'
+ EXTRA_LTLIBRARIES += libcaca_plugin.la
+ vout_LTLIBRARIES += $(LTLIBcaca)
+ 
++### Emscripten ###
++libemscripten_window_plugin_la_SOURCES = video_output/emscripten.c
++
++if HAVE_EMSCRIPTEN
++vout_LTLIBRARIES += libemscripten_window_plugin.la
++endif
++
+ ### Common ###
+ 
+ libflaschen_plugin_la_SOURCES = video_output/flaschen.c
+diff --git a/modules/video_output/emscripten.c b/modules/video_output/emscripten.c
+new file mode 100644
+index 0000000000..05c17d3179
+--- /dev/null
++++ b/modules/video_output/emscripten.c
+@@ -0,0 +1,176 @@
++/**
++ * @file emscripten.c
++ * @brief Emscripten webgl video output for VLC media player
++ */
++/*****************************************************************************
++ * Copyright © 2020 VLC authors and VideoLAN
++ *
++ *
++ * 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 <stdarg.h>
++
++#include <vlc_common.h>
++#include <vlc_plugin.h>
++#include <vlc_vout_window.h>
++#include <vlc_vout_display.h>
++#include <vlc_opengl.h>
++
++#include "./opengl/vout_helper.h"
++
++#include <emscripten.h>
++#include <emscripten/html5.h>
++#include <webgl/webgl2.h>
++// eglGetProcAddress
++#include <EGL/egl.h>
++
++extern emscripten_GetProcAddress(char *name);
++
++static const struct vout_window_operations ops = {
++	//TODO: Implement canvas operations
++	//vout_window_ReportSize() should be called from here
++};
++
++typedef struct gl_sys_t
++{
++	unsigned width;
++	unsigned height;
++
++	EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context;
++} gl_sys_t;
++
++static int OpenWindow(vout_window_t *wnd)
++{
++	wnd->type = VOUT_WINDOW_TYPE_EMSCRIPTEN_WEBGL;
++	wnd->ops = &ops;
++
++	return VLC_SUCCESS;
++}
++
++static void *GetProcAddress(vlc_gl_t *gl, const char *name)
++{
++	VLC_UNUSED(gl);
++
++	return eglGetProcAddress(name);
++}
++static int MakeCurrent(vlc_gl_t *gl)
++{
++	gl_sys_t *sys = gl->sys;
++
++	if (emscripten_webgl_make_context_current(sys->context) != EMSCRIPTEN_RESULT_SUCCESS)
++        return VLC_EGENERIC;
++	return VLC_SUCCESS;
++}
++
++static void ReleaseCurrent(vlc_gl_t *gl)
++{
++	VLC_UNUSED(gl);
++	emscripten_webgl_make_context_current(0);
++}
++
++static void Swap(vlc_gl_t *gl)
++{
++	VLC_UNUSED(gl);
++	emscripten_webgl_commit_frame();
++}
++
++static void Resize(vlc_gl_t *gl, unsigned w, unsigned h)
++{
++	VLC_UNUSED(gl);
++	VLC_UNUSED(w);
++	VLC_UNUSED(h);
++}
++
++static void Close (vlc_gl_t *gl)
++{
++	free(gl->sys);
++}
++
++static int Open (vlc_gl_t *gl, unsigned width, unsigned height)
++{  
++	VLC_UNUSED(width), VLC_UNUSED(height);
++  
++	EmscriptenWebGLContextAttributes attr;
++
++	emscripten_webgl_init_context_attributes(&attr);
++	attr.majorVersion=2;
++	attr.minorVersion=0;
++	attr.explicitSwapControl = 1;
++	
++	vout_window_t *wnd = gl->surface;
++  
++	if (wnd->type != VOUT_WINDOW_TYPE_EMSCRIPTEN_WEBGL)
++		goto error;
++
++	gl_sys_t *sys;
++	
++	gl->sys = sys = calloc(1, sizeof(*sys));
++	if (!sys)
++		return VLC_ENOMEM;
++  
++	sys->context = emscripten_webgl_create_context("#canvas", &attr);  
++	if (!sys->context) {
++		msg_Err(gl, "Failed to make context current");
++		goto error;
++	}
++
++	// Check that the WebGL context is valid
++	if (emscripten_webgl_make_context_current(sys->context) != EMSCRIPTEN_RESULT_SUCCESS) {
++		emscripten_log(EM_LOG_CONSOLE, "failed to make context current");
++		goto error;
++	}
++
++	// Release the context
++	emscripten_webgl_make_context_current(0);
++	wnd->handle.em_context = sys->context;
++
++	// Implement egl routines: 
++	gl->make_current = MakeCurrent;
++	gl->release_current = ReleaseCurrent;
++	gl->resize = Resize;
++	gl->swap = Swap;
++	gl->get_proc_address = GetProcAddress;
++	gl->destroy = Close;
++
++	return VLC_SUCCESS;
++error:
++	Close(gl);
++	return VLC_EGENERIC;
++}
++
++/*
++ * Module descriptor
++ */
++vlc_module_begin()
++	set_shortname(N_("Emscripten Window"))
++	set_description(N_("Emscripten drawing area"))
++	set_subcategory(SUBCAT_VIDEO_VOUT)
++	set_capability("vout window", 10)
++	set_callbacks(OpenWindow, NULL)
++
++	add_submodule ()
++	set_shortname("Emscripten GL")
++	set_description(N_("Emscripten extension for OpenGL"))
++	set_subcategory(SUBCAT_VIDEO_VOUT)
++	set_capability("opengl es2", 50)
++	set_callback(Open)
++	add_shortcut("em_webgl")
++vlc_module_end()
++
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0016-vout-add-emscripten-window-and-webgl-context.patch b/vlc_patches/aug/0016-vout-add-emscripten-window-and-webgl-context.patch
new file mode 100644
index 0000000000000000000000000000000000000000..9b9045a8828604e3b97c3e107497949a3d13d44a
--- /dev/null
+++ b/vlc_patches/aug/0016-vout-add-emscripten-window-and-webgl-context.patch
@@ -0,0 +1,32 @@
+From b28ea0ff21f0764f285f9094cd6f2359c91d9596 Mon Sep 17 00:00:00 2001
+From: Etienne Brateau <etienne.brateau@gmail.com>
+Date: Tue, 27 Apr 2021 16:19:40 +0200
+Subject: [PATCH 16/77] vout: add emscripten window and webgl context
+
+---
+ include/vlc_vout_window.h | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/include/vlc_vout_window.h b/include/vlc_vout_window.h
+index a3dfd1bfca..b5a01b33e4 100644
+--- a/include/vlc_vout_window.h
++++ b/include/vlc_vout_window.h
+@@ -65,6 +65,7 @@ enum vout_window_type {
+     VOUT_WINDOW_TYPE_WAYLAND /**< Wayland surface */,
+     VOUT_WINDOW_TYPE_DCOMP /**< Win32 DirectComposition */,
+     VOUT_WINDOW_TYPE_KMS /**< DRM KMS CRTC */,
++    VOUT_WINDOW_TYPE_EMSCRIPTEN_WEBGL /**< Emscripten surface */,
+ };
+ 
+ /**
+@@ -385,6 +386,7 @@ typedef struct vout_window_t {
+         struct wl_surface *wl;   /**< Wayland surface (client pointer) */
+         void     *dcomp_visual;  /**<  Win32 direct composition visual */
+         uint32_t crtc;           /**< KMS CRTC identifier */
++        uint32_t em_context;     /* Emscripten webgl context */
+     } handle;
+ 
+     /** Display server (mandatory)
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0017-module-vout-Allow-C-OpenGL-modules.patch b/vlc_patches/aug/0017-module-vout-Allow-C-OpenGL-modules.patch
new file mode 100644
index 0000000000000000000000000000000000000000..a767aabdc78b654dcf0f8bddd4dfc58f500c834e
--- /dev/null
+++ b/vlc_patches/aug/0017-module-vout-Allow-C-OpenGL-modules.patch
@@ -0,0 +1,37 @@
+From 839d192745f4d60f7d68e7b2eee30162c08f7e05 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Mon, 17 Jan 2022 15:56:42 +0100
+Subject: [PATCH 17/77] module: vout: Allow C++ OpenGL modules
+
+---
+ modules/video_output/opengl/sampler.h | 9 +++++++++
+ 1 file changed, 9 insertions(+)
+
+diff --git a/modules/video_output/opengl/sampler.h b/modules/video_output/opengl/sampler.h
+index c779f2a9ba..2a5f0ef063 100644
+--- a/modules/video_output/opengl/sampler.h
++++ b/modules/video_output/opengl/sampler.h
+@@ -38,6 +38,11 @@ extern "C"
+ {
+ #endif
+ 
++#ifdef __cplusplus
++extern "C"
++{
++#endif
++
+ /**
+  * The purpose of a sampler is to provide pixel values of a VLC input picture,
+  * stored in any format.
+@@ -199,4 +204,8 @@ vlc_gl_sampler_SelectPlane(struct vlc_gl_sampler *sampler, unsigned plane);
+ }
+ #endif
+ 
++#ifdef __cplusplus
++}
++#endif
++
+ #endif
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0018-TMP-Start-the-vout-from-the-vout-thread.patch b/vlc_patches/aug/0018-TMP-Start-the-vout-from-the-vout-thread.patch
new file mode 100644
index 0000000000000000000000000000000000000000..996969d22b476f8d35ecba1ab6006e7661d9dccc
--- /dev/null
+++ b/vlc_patches/aug/0018-TMP-Start-the-vout-from-the-vout-thread.patch
@@ -0,0 +1,82 @@
+From 5f9b0db3e274cbc2cd83414d3245bed255aa4b81 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Fri, 21 Jan 2022 09:59:35 +0100
+Subject: [PATCH 18/77] TMP: Start the vout from the vout thread
+
+---
+ src/video_output/video_output.c | 31 +++++++++++++++++++++++++------
+ 1 file changed, 25 insertions(+), 6 deletions(-)
+
+diff --git a/src/video_output/video_output.c b/src/video_output/video_output.c
+index 7b03fca641..8b8ce5f323 100644
+--- a/src/video_output/video_output.c
++++ b/src/video_output/video_output.c
+@@ -100,6 +100,13 @@ typedef struct vout_thread_sys_t
+     vout_control_t  control;
+     atomic_bool     control_is_terminated; // shutdown the vout thread
+     vlc_thread_t    thread;
++    vlc_sem_t       thread_ready_sem;
++    bool            thread_success;
++
++    // Begin lazy ass chouquette test:
++    vlc_video_context* vctx_vout_start;
++    const vout_configuration_t *cfg_vout_start;
++    //end of lazy ass chouquette test. don't use this for anything else than vout_start
+ 
+     struct {
+         vlc_tick_t  date;
+@@ -1705,6 +1712,8 @@ error:
+     return VLC_EGENERIC;
+ }
+ 
++
++static void vout_DisableWindow(vout_thread_sys_t *sys);
+ /*****************************************************************************
+  * Thread: video output thread
+  *****************************************************************************
+@@ -1717,6 +1726,17 @@ static void *Thread(void *object)
+     vout_thread_sys_t *vout = object;
+     vout_thread_sys_t *sys = vout;
+ 
++    if (vout_Start(vout, sys->vctx_vout_start, sys->cfg_vout_start))
++    {
++        sys->thread_success = false;
++        msg_Err(&vout->obj, "video output display creation failed");
++        vout_DisableWindow(vout);
++        vlc_sem_post(&sys->thread_ready_sem);
++        return NULL;
++    }
++    sys->thread_success = true;
++    vlc_sem_post(&sys->thread_ready_sem);
++
+     vlc_tick_t deadline = VLC_TICK_INVALID;
+ 
+     for (;;) {
+@@ -2108,19 +2128,18 @@ int vout_Request(const vout_configuration_t *cfg, vlc_video_context *vctx, input
+     sys->rate = 1.f;
+     sys->clock = cfg->clock;
+     sys->delay = 0;
++    vlc_sem_init(&sys->thread_ready_sem, 0);
++
++    sys->cfg_vout_start = cfg;
++    sys->vctx_vout_start = vctx;
+ 
+-    if (vout_Start(vout, vctx, cfg))
+-    {
+-        msg_Err(cfg->vout, "video output display creation failed");
+-        vout_DisableWindow(vout);
+-        return -1;
+-    }
+     atomic_store(&sys->control_is_terminated, false);
+     if (vlc_clone(&sys->thread, Thread, vout, VLC_THREAD_PRIORITY_OUTPUT)) {
+         vout_ReleaseDisplay(vout);
+         vout_DisableWindow(vout);
+         return -1;
+     }
++    vlc_sem_wait(&sys->thread_ready_sem);
+ 
+     if (input != NULL && sys->spu)
+         spu_Attach(sys->spu, input);
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0019-build.sh-Forcefully-disable-accept4.patch b/vlc_patches/aug/0019-build.sh-Forcefully-disable-accept4.patch
new file mode 100644
index 0000000000000000000000000000000000000000..30309ba6d55d94493bc140b15874ad150986ff17
--- /dev/null
+++ b/vlc_patches/aug/0019-build.sh-Forcefully-disable-accept4.patch
@@ -0,0 +1,26 @@
+From dc3975b019fb46dfa37c6216703b4eb97ff4e608 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Thu, 3 Feb 2022 09:30:49 +0100
+Subject: [PATCH 19/77] build.sh: Forcefully disable accept4
+
+In order to declare vlc_cloexec
+---
+ extras/package/wasm-emscripten/build.sh | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/extras/package/wasm-emscripten/build.sh b/extras/package/wasm-emscripten/build.sh
+index 69d6b98c47..c1ea999806 100755
+--- a/extras/package/wasm-emscripten/build.sh
++++ b/extras/package/wasm-emscripten/build.sh
+@@ -164,6 +164,8 @@ if [ $BUILD_MODE -eq 1 ]; then
+                         --disable-sout --disable-vlm --disable-a52 --disable-xcb --disable-lua \
+                         --disable-addonmanagermodules --disable-ssp --disable-nls \
+                         --enable-gles2 \
++                        ac_cv_func_if_nameindex=yes ac_cv_header_sys_shm_h=no \
++                        ac_cv_func_accept4=no \
+                         --with-contrib="$VLC_SRCPATH"/contrib/wasm32-unknown-emscripten
+ fi
+ 
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0020-modules-Convert-emscripten-vout-to-C.patch b/vlc_patches/aug/0020-modules-Convert-emscripten-vout-to-C.patch
new file mode 100644
index 0000000000000000000000000000000000000000..9ea808ab7d1d4a4f32282c6d60a097dc55f6f8c9
--- /dev/null
+++ b/vlc_patches/aug/0020-modules-Convert-emscripten-vout-to-C.patch
@@ -0,0 +1,393 @@
+From de9a47b9c4bf6fb2d6ac20b7aecba3064943f4aa Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Thu, 3 Feb 2022 09:35:05 +0100
+Subject: [PATCH 20/77] modules: Convert emscripten vout to C++
+
+We'll need to be able to use various emscripten features that are only
+available through C++
+---
+ modules/video_output/Makefile.am    |   2 +-
+ modules/video_output/emscripten.c   | 176 ----------------------------
+ modules/video_output/emscripten.cpp | 174 +++++++++++++++++++++++++++
+ 3 files changed, 175 insertions(+), 177 deletions(-)
+ delete mode 100644 modules/video_output/emscripten.c
+ create mode 100644 modules/video_output/emscripten.cpp
+
+diff --git a/modules/video_output/Makefile.am b/modules/video_output/Makefile.am
+index 3134528bc1..191992d887 100644
+--- a/modules/video_output/Makefile.am
++++ b/modules/video_output/Makefile.am
+@@ -346,7 +346,7 @@ EXTRA_LTLIBRARIES += libcaca_plugin.la
+ vout_LTLIBRARIES += $(LTLIBcaca)
+ 
+ ### Emscripten ###
+-libemscripten_window_plugin_la_SOURCES = video_output/emscripten.c
++libemscripten_window_plugin_la_SOURCES = video_output/emscripten.cpp
+ 
+ if HAVE_EMSCRIPTEN
+ vout_LTLIBRARIES += libemscripten_window_plugin.la
+diff --git a/modules/video_output/emscripten.c b/modules/video_output/emscripten.c
+deleted file mode 100644
+index 05c17d3179..0000000000
+--- a/modules/video_output/emscripten.c
++++ /dev/null
+@@ -1,176 +0,0 @@
+-/**
+- * @file emscripten.c
+- * @brief Emscripten webgl video output for VLC media player
+- */
+-/*****************************************************************************
+- * Copyright © 2020 VLC authors and VideoLAN
+- *
+- *
+- * 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 <stdarg.h>
+-
+-#include <vlc_common.h>
+-#include <vlc_plugin.h>
+-#include <vlc_vout_window.h>
+-#include <vlc_vout_display.h>
+-#include <vlc_opengl.h>
+-
+-#include "./opengl/vout_helper.h"
+-
+-#include <emscripten.h>
+-#include <emscripten/html5.h>
+-#include <webgl/webgl2.h>
+-// eglGetProcAddress
+-#include <EGL/egl.h>
+-
+-extern emscripten_GetProcAddress(char *name);
+-
+-static const struct vout_window_operations ops = {
+-	//TODO: Implement canvas operations
+-	//vout_window_ReportSize() should be called from here
+-};
+-
+-typedef struct gl_sys_t
+-{
+-	unsigned width;
+-	unsigned height;
+-
+-	EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context;
+-} gl_sys_t;
+-
+-static int OpenWindow(vout_window_t *wnd)
+-{
+-	wnd->type = VOUT_WINDOW_TYPE_EMSCRIPTEN_WEBGL;
+-	wnd->ops = &ops;
+-
+-	return VLC_SUCCESS;
+-}
+-
+-static void *GetProcAddress(vlc_gl_t *gl, const char *name)
+-{
+-	VLC_UNUSED(gl);
+-
+-	return eglGetProcAddress(name);
+-}
+-static int MakeCurrent(vlc_gl_t *gl)
+-{
+-	gl_sys_t *sys = gl->sys;
+-
+-	if (emscripten_webgl_make_context_current(sys->context) != EMSCRIPTEN_RESULT_SUCCESS)
+-        return VLC_EGENERIC;
+-	return VLC_SUCCESS;
+-}
+-
+-static void ReleaseCurrent(vlc_gl_t *gl)
+-{
+-	VLC_UNUSED(gl);
+-	emscripten_webgl_make_context_current(0);
+-}
+-
+-static void Swap(vlc_gl_t *gl)
+-{
+-	VLC_UNUSED(gl);
+-	emscripten_webgl_commit_frame();
+-}
+-
+-static void Resize(vlc_gl_t *gl, unsigned w, unsigned h)
+-{
+-	VLC_UNUSED(gl);
+-	VLC_UNUSED(w);
+-	VLC_UNUSED(h);
+-}
+-
+-static void Close (vlc_gl_t *gl)
+-{
+-	free(gl->sys);
+-}
+-
+-static int Open (vlc_gl_t *gl, unsigned width, unsigned height)
+-{  
+-	VLC_UNUSED(width), VLC_UNUSED(height);
+-  
+-	EmscriptenWebGLContextAttributes attr;
+-
+-	emscripten_webgl_init_context_attributes(&attr);
+-	attr.majorVersion=2;
+-	attr.minorVersion=0;
+-	attr.explicitSwapControl = 1;
+-	
+-	vout_window_t *wnd = gl->surface;
+-  
+-	if (wnd->type != VOUT_WINDOW_TYPE_EMSCRIPTEN_WEBGL)
+-		goto error;
+-
+-	gl_sys_t *sys;
+-	
+-	gl->sys = sys = calloc(1, sizeof(*sys));
+-	if (!sys)
+-		return VLC_ENOMEM;
+-  
+-	sys->context = emscripten_webgl_create_context("#canvas", &attr);  
+-	if (!sys->context) {
+-		msg_Err(gl, "Failed to make context current");
+-		goto error;
+-	}
+-
+-	// Check that the WebGL context is valid
+-	if (emscripten_webgl_make_context_current(sys->context) != EMSCRIPTEN_RESULT_SUCCESS) {
+-		emscripten_log(EM_LOG_CONSOLE, "failed to make context current");
+-		goto error;
+-	}
+-
+-	// Release the context
+-	emscripten_webgl_make_context_current(0);
+-	wnd->handle.em_context = sys->context;
+-
+-	// Implement egl routines: 
+-	gl->make_current = MakeCurrent;
+-	gl->release_current = ReleaseCurrent;
+-	gl->resize = Resize;
+-	gl->swap = Swap;
+-	gl->get_proc_address = GetProcAddress;
+-	gl->destroy = Close;
+-
+-	return VLC_SUCCESS;
+-error:
+-	Close(gl);
+-	return VLC_EGENERIC;
+-}
+-
+-/*
+- * Module descriptor
+- */
+-vlc_module_begin()
+-	set_shortname(N_("Emscripten Window"))
+-	set_description(N_("Emscripten drawing area"))
+-	set_subcategory(SUBCAT_VIDEO_VOUT)
+-	set_capability("vout window", 10)
+-	set_callbacks(OpenWindow, NULL)
+-
+-	add_submodule ()
+-	set_shortname("Emscripten GL")
+-	set_description(N_("Emscripten extension for OpenGL"))
+-	set_subcategory(SUBCAT_VIDEO_VOUT)
+-	set_capability("opengl es2", 50)
+-	set_callback(Open)
+-	add_shortcut("em_webgl")
+-vlc_module_end()
+-
+diff --git a/modules/video_output/emscripten.cpp b/modules/video_output/emscripten.cpp
+new file mode 100644
+index 0000000000..857d921c27
+--- /dev/null
++++ b/modules/video_output/emscripten.cpp
+@@ -0,0 +1,174 @@
++/**
++ * @file emscripten.c
++ * @brief Emscripten webgl video output for VLC media player
++ */
++/*****************************************************************************
++ * Copyright © 2020 VLC authors and VideoLAN
++ *
++ *
++ * 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 <stdarg.h>
++
++#include <vlc_common.h>
++#include <vlc_plugin.h>
++#include <vlc_vout_window.h>
++#include <vlc_vout_display.h>
++#include <vlc_opengl.h>
++
++#include "./opengl/vout_helper.h"
++
++#include <emscripten.h>
++#include <emscripten/html5.h>
++#include <webgl/webgl2.h>
++// eglGetProcAddress
++#include <EGL/egl.h>
++
++static const struct vout_window_operations ops = {
++    //TODO: Implement canvas operations
++    //vout_window_ReportSize() should be called from here
++};
++
++typedef struct gl_sys_t
++{
++    unsigned width;
++    unsigned height;
++
++    EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context;
++} gl_sys_t;
++
++static int OpenWindow(vout_window_t *wnd)
++{
++    wnd->type = VOUT_WINDOW_TYPE_EMSCRIPTEN_WEBGL;
++    wnd->ops = &ops;
++
++    return VLC_SUCCESS;
++}
++
++static void *GetProcAddress(vlc_gl_t *gl, const char *name)
++{
++    VLC_UNUSED(gl);
++
++    return reinterpret_cast<void*>(eglGetProcAddress(name));
++}
++static int MakeCurrent(vlc_gl_t *gl)
++{
++    auto *sys = static_cast<gl_sys_t*>(gl->sys);
++
++    if (emscripten_webgl_make_context_current(sys->context) != EMSCRIPTEN_RESULT_SUCCESS)
++        return VLC_EGENERIC;
++    return VLC_SUCCESS;
++}
++
++static void ReleaseCurrent(vlc_gl_t *gl)
++{
++    VLC_UNUSED(gl);
++    emscripten_webgl_make_context_current(0);
++}
++
++static void Swap(vlc_gl_t *gl)
++{
++    VLC_UNUSED(gl);
++    emscripten_webgl_commit_frame();
++}
++
++static void Resize(vlc_gl_t *gl, unsigned w, unsigned h)
++{
++    VLC_UNUSED(gl);
++    VLC_UNUSED(w);
++    VLC_UNUSED(h);
++}
++
++static void Close (vlc_gl_t *gl)
++{
++    free(gl->sys);
++}
++
++static int Open (vlc_gl_t *gl, unsigned width, unsigned height)
++{
++    VLC_UNUSED(width), VLC_UNUSED(height);
++
++    EmscriptenWebGLContextAttributes attr;
++
++    emscripten_webgl_init_context_attributes(&attr);
++    attr.majorVersion=2;
++    attr.minorVersion=0;
++    attr.explicitSwapControl = 1;
++
++    vout_window_t *wnd = gl->surface;
++
++    if (wnd->type != VOUT_WINDOW_TYPE_EMSCRIPTEN_WEBGL)
++        goto error;
++
++    gl_sys_t *sys;
++
++    gl->sys = sys = static_cast<gl_sys_t*>(calloc(1, sizeof(*sys)));
++    if (!sys)
++        return VLC_ENOMEM;
++
++    sys->context = emscripten_webgl_create_context("#canvas", &attr);
++    if (!sys->context) {
++        msg_Err(gl, "Failed to make context current");
++        goto error;
++    }
++
++    // Check that the WebGL context is valid
++    if (emscripten_webgl_make_context_current(sys->context) != EMSCRIPTEN_RESULT_SUCCESS) {
++        emscripten_log(EM_LOG_CONSOLE, "failed to make context current");
++        goto error;
++    }
++
++    // Release the context
++    emscripten_webgl_make_context_current(0);
++    wnd->handle.em_context = sys->context;
++
++    // Implement egl routines:
++    gl->make_current = MakeCurrent;
++    gl->release_current = ReleaseCurrent;
++    gl->resize = Resize;
++    gl->swap = Swap;
++    gl->get_proc_address = GetProcAddress;
++    gl->destroy = Close;
++
++    return VLC_SUCCESS;
++error:
++    Close(gl);
++    return VLC_EGENERIC;
++}
++
++/*
++ * Module descriptor
++ */
++vlc_module_begin()
++    set_shortname(N_("Emscripten Window"))
++    set_description(N_("Emscripten drawing area"))
++    set_subcategory(SUBCAT_VIDEO_VOUT)
++    set_capability("vout window", 10)
++    set_callbacks(OpenWindow, NULL)
++
++    add_submodule ()
++    set_shortname("Emscripten GL")
++    set_description(N_("Emscripten extension for OpenGL"))
++    set_subcategory(SUBCAT_VIDEO_VOUT)
++    set_capability("opengl es2", 50)
++    set_callback(Open)
++    add_shortcut("em_webgl")
++vlc_module_end()
++
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0021-codec-Add-a-webcodec-decoder-device-type.patch b/vlc_patches/aug/0021-codec-Add-a-webcodec-decoder-device-type.patch
new file mode 100644
index 0000000000000000000000000000000000000000..f37268aa1b066b619e944dac6b43c5bc22dc7c5a
--- /dev/null
+++ b/vlc_patches/aug/0021-codec-Add-a-webcodec-decoder-device-type.patch
@@ -0,0 +1,24 @@
+From d36a5341abd7cbb4ad39af1dc9434ba1608a6678 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Thu, 3 Feb 2022 09:47:13 +0100
+Subject: [PATCH 21/77] codec: Add a webcodec decoder device type
+
+---
+ include/vlc_codec.h | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/include/vlc_codec.h b/include/vlc_codec.h
+index ff707e73a3..7460d03ed6 100644
+--- a/include/vlc_codec.h
++++ b/include/vlc_codec.h
+@@ -583,6 +583,7 @@ enum vlc_decoder_device_type
+     VLC_DECODER_DEVICE_AWINDOW,
+     VLC_DECODER_DEVICE_NVDEC,
+     VLC_DECODER_DEVICE_MMAL,
++    VLC_DECODER_DEVICE_WEBCODEC,
+ };
+ 
+ struct vlc_decoder_device_operations
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0022-vlc_picture.h-Add-a-webcodec-video-context-type.patch b/vlc_patches/aug/0022-vlc_picture.h-Add-a-webcodec-video-context-type.patch
new file mode 100644
index 0000000000000000000000000000000000000000..4ed5ada22b46d163b53d1c5f33ac5528e5bdc21c
--- /dev/null
+++ b/vlc_patches/aug/0022-vlc_picture.h-Add-a-webcodec-video-context-type.patch
@@ -0,0 +1,24 @@
+From 1cc0491674a6287ae12dc6fa6c18a86d6e700f3a Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Thu, 3 Feb 2022 09:50:27 +0100
+Subject: [PATCH 22/77] vlc_picture.h: Add a webcodec video context type
+
+---
+ include/vlc_picture.h | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/include/vlc_picture.h b/include/vlc_picture.h
+index 5aece95120..fa62e307bd 100644
+--- a/include/vlc_picture.h
++++ b/include/vlc_picture.h
+@@ -98,6 +98,7 @@ enum vlc_video_context_type
+     VLC_VIDEO_CONTEXT_NVDEC,     //!< empty
+     VLC_VIDEO_CONTEXT_CVPX,      //!< private: cvpx_video_context*
+     VLC_VIDEO_CONTEXT_MMAL,      //!< empty
++    VLC_VIDEO_CONTEXT_WEBCODEC,
+ };
+ 
+ VLC_API vlc_video_context * vlc_video_context_Create(vlc_decoder_device *,
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0023-fourcc-Add-an-opaque-webcodec-fourcc.patch b/vlc_patches/aug/0023-fourcc-Add-an-opaque-webcodec-fourcc.patch
new file mode 100644
index 0000000000000000000000000000000000000000..7376b8c25e290ef15affe5e349cd33e62fbf9d30
--- /dev/null
+++ b/vlc_patches/aug/0023-fourcc-Add-an-opaque-webcodec-fourcc.patch
@@ -0,0 +1,40 @@
+From 2e7a43f54ae86fcf9fd913d5ec4478a022285c4a Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Thu, 3 Feb 2022 09:51:05 +0100
+Subject: [PATCH 23/77] fourcc: Add an opaque webcodec fourcc
+
+---
+ include/vlc_fourcc.h | 3 +++
+ src/misc/fourcc.c    | 2 ++
+ 2 files changed, 5 insertions(+)
+
+diff --git a/include/vlc_fourcc.h b/include/vlc_fourcc.h
+index 176028e351..4c7f35d4ff 100644
+--- a/include/vlc_fourcc.h
++++ b/include/vlc_fourcc.h
+@@ -440,6 +440,9 @@
+ #define VLC_CODEC_CVPX_BGRA       VLC_FOURCC('C','V','P','B')
+ #define VLC_CODEC_CVPX_P010       VLC_FOURCC('C','V','P','P')
+ 
++/* Webcodec opaque VideoFrame types */
++#define VLC_CODEC_WEBCODEC_OPAQUE VLC_FOURCC('W','C','O','P')
++
+ /* Image codec (video) */
+ #define VLC_CODEC_PNG             VLC_FOURCC('p','n','g',' ')
+ #define VLC_CODEC_PPM             VLC_FOURCC('p','p','m',' ')
+diff --git a/src/misc/fourcc.c b/src/misc/fourcc.c
+index 101bb0d980..479a1bd13e 100644
+--- a/src/misc/fourcc.c
++++ b/src/misc/fourcc.c
+@@ -840,6 +840,8 @@ static const struct
+     { { VLC_CODEC_VAAPI_420, VLC_CODEC_VAAPI_420_10BPP },
+                                                FAKE_FMT() },
+ 
++    { { VLC_CODEC_WEBCODEC_OPAQUE }, FAKE_FMT() },
++
+     { { 0 },                                   FAKE_FMT() }
+ };
+ 
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0024-emscripten-build.sh-Enable-gles2.patch b/vlc_patches/aug/0024-emscripten-build.sh-Enable-gles2.patch
new file mode 100644
index 0000000000000000000000000000000000000000..418ef520bcf9281fcea5ccce36fc4ea14d51eb8f
--- /dev/null
+++ b/vlc_patches/aug/0024-emscripten-build.sh-Enable-gles2.patch
@@ -0,0 +1,25 @@
+From 16bd17c0e12b943504416dd67a317abbd9142685 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Thu, 3 Feb 2022 09:52:21 +0100
+Subject: [PATCH 24/77] emscripten: build.sh: Enable gles2
+
+Already applied upstream
+---
+ extras/package/wasm-emscripten/build.sh | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/extras/package/wasm-emscripten/build.sh b/extras/package/wasm-emscripten/build.sh
+index c1ea999806..5119fe6a6e 100755
+--- a/extras/package/wasm-emscripten/build.sh
++++ b/extras/package/wasm-emscripten/build.sh
+@@ -166,6 +166,7 @@ if [ $BUILD_MODE -eq 1 ]; then
+                         --enable-gles2 \
+                         ac_cv_func_if_nameindex=yes ac_cv_header_sys_shm_h=no \
+                         ac_cv_func_accept4=no \
++                        --enable-gles2 \
+                         --with-contrib="$VLC_SRCPATH"/contrib/wasm32-unknown-emscripten
+ fi
+ 
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0025-emscripten-build-Forcefully-disable-pipe2.patch b/vlc_patches/aug/0025-emscripten-build-Forcefully-disable-pipe2.patch
new file mode 100644
index 0000000000000000000000000000000000000000..c256c2b48aae0a64a0ddd157dbb6ac4370e52b27
--- /dev/null
+++ b/vlc_patches/aug/0025-emscripten-build-Forcefully-disable-pipe2.patch
@@ -0,0 +1,24 @@
+From 5ec33317bdd1ef893d7e937c2fcc5435d3929652 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Thu, 3 Feb 2022 09:52:49 +0100
+Subject: [PATCH 25/77] emscripten: build: Forcefully disable pipe2
+
+---
+ extras/package/wasm-emscripten/build.sh | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/extras/package/wasm-emscripten/build.sh b/extras/package/wasm-emscripten/build.sh
+index 5119fe6a6e..5a1dbd230d 100755
+--- a/extras/package/wasm-emscripten/build.sh
++++ b/extras/package/wasm-emscripten/build.sh
+@@ -166,6 +166,7 @@ if [ $BUILD_MODE -eq 1 ]; then
+                         --enable-gles2 \
+                         ac_cv_func_if_nameindex=yes ac_cv_header_sys_shm_h=no \
+                         ac_cv_func_accept4=no \
++                        ac_cv_func_pipe2=no \
+                         --enable-gles2 \
+                         --with-contrib="$VLC_SRCPATH"/contrib/wasm32-unknown-emscripten
+ fi
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0026-emscripten-build-Disable-nvdec.patch b/vlc_patches/aug/0026-emscripten-build-Disable-nvdec.patch
new file mode 100644
index 0000000000000000000000000000000000000000..aaf092f1da4e764ac6ddc04029881b9f88ccd9b8
--- /dev/null
+++ b/vlc_patches/aug/0026-emscripten-build-Disable-nvdec.patch
@@ -0,0 +1,24 @@
+From d80f3c5acc6455a4d3f8afd51b80d3f456203b85 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Thu, 3 Feb 2022 09:53:10 +0100
+Subject: [PATCH 26/77] emscripten: build: Disable nvdec
+
+---
+ extras/package/wasm-emscripten/build.sh | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/extras/package/wasm-emscripten/build.sh b/extras/package/wasm-emscripten/build.sh
+index 5a1dbd230d..acd74591fc 100755
+--- a/extras/package/wasm-emscripten/build.sh
++++ b/extras/package/wasm-emscripten/build.sh
+@@ -168,6 +168,7 @@ if [ $BUILD_MODE -eq 1 ]; then
+                         ac_cv_func_accept4=no \
+                         ac_cv_func_pipe2=no \
+                         --enable-gles2 \
++                        --disable-nvdec \
+                         --with-contrib="$VLC_SRCPATH"/contrib/wasm32-unknown-emscripten
+ fi
+ 
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0027-codec-Add-a-webcodec-decoder.patch b/vlc_patches/aug/0027-codec-Add-a-webcodec-decoder.patch
new file mode 100644
index 0000000000000000000000000000000000000000..add60eba00050130262161cb66456a543d9d3a5d
--- /dev/null
+++ b/vlc_patches/aug/0027-codec-Add-a-webcodec-decoder.patch
@@ -0,0 +1,480 @@
+From fff9ead944d55025adcb89a4424e2b40baf6d7ca Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Thu, 3 Feb 2022 09:54:14 +0100
+Subject: [PATCH 27/77] codec: Add a webcodec decoder
+
+---
+ modules/codec/Makefile.am                |   6 +
+ modules/codec/webcodec.cpp               | 392 +++++++++++++++++++++++
+ modules/video_output/emscripten/common.h |  46 +++
+ 3 files changed, 444 insertions(+)
+ create mode 100644 modules/codec/webcodec.cpp
+ create mode 100644 modules/video_output/emscripten/common.h
+
+diff --git a/modules/codec/Makefile.am b/modules/codec/Makefile.am
+index 6bdc40b491..31ec138bd0 100644
+--- a/modules/codec/Makefile.am
++++ b/modules/codec/Makefile.am
+@@ -644,3 +644,9 @@ noinst_LTLIBRARIES += libhxxxhelper_testdec_plugin.la
+ libvlc_vtutils_la_SOURCES = codec/vt_utils.c codec/vt_utils.h
+ libvlc_vtutils_la_LDFLAGS = -static -no-undefined
+ EXTRA_LTLIBRARIES += libvlc_vtutils.la
++
++libwebcodec_plugin_la_SOURCES = codec/webcodec.cpp
++libwebcodec_plugin_la_LDFLAGS = $(AM_LDFLAGS)
++if HAVE_EMSCRIPTEN
++codec_LTLIBRARIES += libwebcodec_plugin.la
++endif
+diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp
+new file mode 100644
+index 0000000000..a75d3f7897
+--- /dev/null
++++ b/modules/codec/webcodec.cpp
+@@ -0,0 +1,392 @@
++/*****************************************************************************
++ * webcodec.cpp: Decoder module using browser provided codec implementations
++ *****************************************************************************
++ * Copyright © 2021 VLC authors and VideoLAN
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 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 General Public License for more details.
++ *
++ * You should have received a copy of the GNU 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 <vlc_common.h>
++#include <vlc_plugin.h>
++#include <vlc_codec.h>
++#include <vlc_threads.h>
++#include <vlc_cxx_helpers.hpp>
++#include <vlc_block.h>
++#include <vlc_tick.h>
++#include <vlc_picture.h>
++
++#include "../video_output/emscripten/common.h"
++
++#include <emscripten/emscripten.h>
++#include <emscripten/val.h>
++#include <emscripten/bind.h>
++#include <emscripten/em_js.h>
++#include <emscripten/wire.h>
++#include <memory>
++#include <functional>
++#include <cstdint>
++#include <queue>
++
++using emval = emscripten::val;
++
++struct decoder_sys_t
++{
++    emval decoder = emval::undefined();
++    std::queue<block_t*> blocks;
++    vlc::threads::mutex mutex;
++    vlc::threads::condition_variable cond;
++    vlc_thread_t th;
++
++    vlc_video_context* vctx;
++};
++
++extern "C"
++{
++
++EMSCRIPTEN_KEEPALIVE picture_t* createAndQueuePicture(decoder_t* dec, int pictureId,
++                                                      int64_t timestamp)
++{
++    auto sys = static_cast<decoder_sys_t*>( dec->p_sys );
++    if ( decoder_UpdateVideoOutput( dec, sys->vctx ) )
++    {
++        msg_Err( dec, "Failure during UpdateVideoOutput! FIXME" );
++        return NULL;
++    }
++
++    auto pic = decoder_NewPicture(dec);
++    if (pic == nullptr)
++        return nullptr;
++    pic->date = VLC_TICK_FROM_US(timestamp);
++    pic->b_progressive = true;
++    pic->p_sys = reinterpret_cast<void*>( static_cast<uintptr_t>( pictureId ) );
++    decoder_QueueVideo(dec, pic);
++    return pic;
++}
++
++}
++
++EM_JS(void, initModuleContext, (void* ctx), {
++    globalThis.Module.webCodecCtx = ctx;
++});
++
++EM_ASYNC_JS(void, declareCallbacks, (), {
++    async function getVoutMessagePort() {
++        let p = new Promise((resolve, reject) => {
++            self.addEventListener('message', function(e) {
++                let msg = e['data'];
++                if (msg.customCmd == 'transferMessagePort') {
++                    let port = msg['transferList'][0];
++                    if (!port)
++                        reject();
++                    Module.voutPort = port;
++                    resolve();
++                }
++            });
++        });
++        await p;
++    }
++
++    Module.pictureId = 0;
++    globalThis.Module.boundOutputCb = async function(frame) {
++        Module.pictureId = (Module.pictureId + 1);
++        _createAndQueuePicture(globalThis.Module.webCodecCtx, Module.pictureId,
++                    frame.timestamp);
++        if (Module.voutPort === undefined)
++            await getVoutMessagePort();
++        console.log('Posting frame', Module.pictureId);
++        Module.voutPort.postMessage({
++            customCmd: 'displayFrame',
++            frame: frame,
++            pictureId: Module.pictureId,
++          }, [frame]);
++    };
++    globalThis.Module.boundErrorCb = function(err) {
++        console.log('Error while decoding: ');
++        console.log(err);
++    };
++});
++
++EM_ASYNC_JS(bool, probeConfig, (emscripten::EM_VAL cfg), {
++    var decoderCfg = Emval.toValue(cfg);
++    var res = await VideoDecoder.isConfigSupported(decoderCfg).catch((err) => {
++        console.log(err);
++        return {'supported': false};
++    });
++    return res['supported'];
++});
++
++emval blockToEncodedVideoChunk( decoder_t* dec, block_t* block )
++{
++    auto chunkType = emval::global("EncodedVideoChunk");
++    auto chunkCfg = emval::object();
++
++    static bool first = true;
++
++    if ( first == true )
++    {
++        chunkCfg.set( "type", "key" );
++        first = false;
++    }
++    else
++        chunkCfg.set( "type", (block->i_flags & BLOCK_FLAG_TYPE_I) ? "key" : "delta" );
++    auto timestamp = block->i_pts ? block->i_pts : block->i_dts;
++    chunkCfg.set( "timestamp", (long int)US_FROM_VLC_TICK( timestamp ) );
++    //if ( block->i_length > 0 )
++    //    chunkCfg.set( "duration", (long int)US_FROM_VLC_TICK( block->i_length ) );
++    chunkCfg.set( "data", emscripten::typed_memory_view( block->i_buffer, block->p_buffer ) );
++    return chunkType.new_( std::move( chunkCfg ) );
++}
++
++static emval getDecoderConfig( decoder_t* dec, bool includeExtraData )
++{
++    auto decoderConfig = emval::object();
++    switch( dec->fmt_in.i_codec )
++    {
++    case VLC_CODEC_VP8:
++        decoderConfig.set( "codec", "vp8" );
++        break;
++    case VLC_CODEC_VP9:
++        decoderConfig.set( "codec", "vp09.*" );
++        break;
++    case VLC_CODEC_H264:
++    {
++        char codec[12];
++        snprintf(codec, sizeof(codec), "avc1.%.2X%.2X%.2X", dec->fmt_in.i_profile,
++                 0, dec->fmt_in.i_level);
++        decoderConfig.set( "codec", codec );
++
++        //decoderConfig.set("codec", "avc1.64000b" );
++        break;
++    }
++    case VLC_CODEC_AV1:
++        decoderConfig.set( "codec", "av01" );
++        break;
++    default:
++        return emval::undefined();
++    }
++    decoderConfig.set( "codedWidth", dec->fmt_in.video.i_width );
++    decoderConfig.set( "codedHeight", dec->fmt_in.video.i_height );
++    decoderConfig.set( "displayAspectWidth", dec->fmt_in.video.i_visible_width );
++    decoderConfig.set( "displayAspectHeight", dec->fmt_in.video.i_visible_height );
++    decoderConfig.set( "optimizeForLatency", true );
++    if ( includeExtraData )
++    {
++        msg_Err( dec, "i_extra: %u", dec->fmt_in.i_extra );
++        if ( dec->fmt_in.i_extra > 0 )
++        {
++            decoderConfig.set( "description",
++                               emscripten::typed_memory_view(
++                                    dec->fmt_in.i_extra,
++                                   static_cast<uint8_t*>( dec->fmt_in.p_extra ) )
++                               );
++        }
++    }
++    return decoderConfig;
++}
++
++static bool initDecoder( decoder_t* dec )
++{
++    auto sys = static_cast<decoder_sys_t*>( dec->p_sys );
++    initModuleContext(dec);
++    declareCallbacks();
++
++    auto initCfg = emval::object();
++
++    auto outputCb = emval::module_property("boundOutputCb");
++    if ( outputCb.isUndefined() )
++    {
++        msg_Err( dec, "Failed to find output callback" );
++        return false;
++    }
++    initCfg.set("output", outputCb);
++
++    auto errorCb = emval::module_property("boundErrorCb");
++    if ( errorCb.isUndefined() )
++    {
++        msg_Err( dec, "Failed to find error callback" );
++        return false;
++    }
++    initCfg.set("error", errorCb);
++
++    auto decoderType = emval::global("VideoDecoder");
++    sys->decoder = decoderType.new_(initCfg);
++    if ( sys->decoder.isUndefined() )
++    {
++        msg_Err( dec, "Failed to instantiate VideoDecoder" );
++        return false;
++    }
++
++    sys->decoder.call<void>( "configure", getDecoderConfig( dec, true ) );
++
++    return true;
++}
++
++static void WebcodecDecodeWorkerTick( void* arg )
++{
++    auto dec = static_cast<decoder_t*>( arg );
++    auto sys = static_cast<decoder_sys_t*>( dec->p_sys );
++    block_t* block;
++
++    vlc::threads::mutex_locker lock{ sys->mutex };
++    while ( sys->blocks.empty() == false )
++    {
++        block = sys->blocks.front();
++        sys->blocks.pop();
++
++        auto chunk = blockToEncodedVideoChunk( dec, block );
++        block_Release(block);
++        msg_Err(dec, "Decoding a sample...");
++        sys->decoder.call<void>( "decode", chunk );
++
++        auto queueSize = sys->decoder["decodeQueueSize"];
++        auto state = sys->decoder["state"];
++        msg_Err( dec, "Decoder state: %s ; queue size: %ld", state.as<std::string>().c_str(), queueSize.as<long int>());
++    }
++}
++
++static void* WebcodecDecodeWorker( void* arg )
++{
++    /*
++     * We need to be able to yield back to the browser main loop after we
++     * queue some blocks for decoding, otherwise the callback will never be
++     * invoked.
++     * In order to do so, we spawn a dedidated thread that will run a "main loop"
++     * which consist of dequeuing a single block and passing it to webcodec.
++     * After doing so, it yields back to the message loop and so on. If no
++     * blocks are queued for decoding, we can block as there nothing else being
++     * done from this worker.
++     */
++    auto dec = static_cast<decoder_t*>( arg );
++    if ( !initDecoder( dec ) )
++    {
++        msg_Err( dec, "Failed to initialize decoder: FIXME" );
++        return NULL;
++    }
++    auto sys = static_cast<decoder_sys_t*>( dec->p_sys );
++    auto vctxPrivate = static_cast<webcodec_context*>(
++            vlc_video_context_GetPrivate(sys->vctx, VLC_VIDEO_CONTEXT_WEBCODEC));
++    vctxPrivate->decoder_worker = pthread_self();
++    emscripten_set_main_loop_arg( &WebcodecDecodeWorkerTick, dec, 0, false );
++    /*
++     * We want our tick function to be invoked ASAP even if we'd only be blocking
++     * waiting for a new block to be queued
++     */
++    emscripten_set_main_loop_timing( EM_TIMING_SETTIMEOUT, 1 );
++    return NULL;
++}
++
++static int Decode( decoder_t* dec, block_t* block )
++{
++    auto sys = static_cast<decoder_sys_t*>( dec->p_sys );
++    vlc::threads::mutex_locker lock{ sys->mutex };
++    sys->blocks.push( block );
++    sys->cond.signal();
++    return VLCDEC_SUCCESS;
++}
++
++static void Flush( decoder_t* dec )
++{
++    //FIXME: Needs to be called from the decoder thread
++    auto sys = static_cast<decoder_sys_t*>( dec->p_sys );
++    sys->decoder.call<emval>("flush").await();
++}
++
++static int Open( vlc_object_t* obj )
++{
++    auto dec = reinterpret_cast<decoder_t*>(obj);
++
++    if ( dec->fmt_in.i_cat != VIDEO_ES )
++        return VLC_EGENERIC;
++
++    auto decoderType = emval::global("VideoDecoder");
++    if ( !decoderType.as<bool>() )
++    {
++        msg_Err( obj, "Can't get VideoDecoder type, webcodec is probably not "
++                      "supported on this browser" );
++        return false;
++    }
++    auto sys = std::make_unique<decoder_sys_t>();
++    dec->p_sys = sys.get();
++
++    auto decoderConfig = getDecoderConfig( dec, false );
++    if ( decoderConfig.isUndefined() )
++        return VLC_EGENERIC;
++    auto isSupported = probeConfig(decoderConfig.as_handle());
++
++    if ( isSupported == false )
++    {
++        msg_Err( dec, "VideoDecoder doesn't support this configuration" );
++        return VLC_EGENERIC;
++    }
++
++    if ( es_format_Copy( &dec->fmt_out, &dec->fmt_in ) != VLC_SUCCESS )
++        return VLC_ENOMEM;
++    dec->fmt_out.i_codec = dec->fmt_out.video.i_chroma = VLC_CODEC_WEBCODEC_OPAQUE;
++
++    auto dec_dev = decoder_GetDecoderDevice(dec);
++    sys->vctx = vlc_video_context_Create(dec_dev, VLC_VIDEO_CONTEXT_WEBCODEC,
++                                         sizeof(webcodec_context), nullptr);
++    auto vctxPrivate = static_cast<webcodec_context*>(
++            vlc_video_context_GetPrivate(sys->vctx, VLC_VIDEO_CONTEXT_WEBCODEC));
++    new (vctxPrivate) webcodec_context();
++
++    if ( vlc_clone( &sys->th, &WebcodecDecodeWorker, dec, 0 ) != VLC_SUCCESS )
++    {
++        msg_Err( obj, "Failed to create webcodec thread" );
++        return VLC_EGENERIC;
++    }
++
++    dec->pf_decode = &Decode;
++    dec->pf_flush = &Flush;
++
++    sys.release();
++    return VLCDEC_SUCCESS;
++}
++
++static void Close( decoder_t* dec )
++{
++    auto sys = static_cast<decoder_sys_t*>( dec->p_sys );
++    sys->decoder.call<void>("close");
++    delete sys;
++}
++
++static int
++OpenDecDevice(vlc_decoder_device *device, vout_window_t *)
++{
++    static const struct vlc_decoder_device_operations ops =
++    {
++        nullptr,
++    };
++    device->ops = &ops;
++    device->type = VLC_DECODER_DEVICE_WEBCODEC;
++
++    return VLC_SUCCESS;
++}
++
++vlc_module_begin ()
++    set_description("Video decoder using browser provided implementation")
++    set_subcategory(SUBCAT_INPUT_VCODEC)
++    set_section(N_("Decoding"), NULL)
++    set_capability("video decoder", 100)
++    set_callbacks(Open, Close)
++    add_shortcut("webcodec")
++
++    add_submodule()
++        set_callback_dec_device(OpenDecDevice, 1)
++vlc_module_end ()
+diff --git a/modules/video_output/emscripten/common.h b/modules/video_output/emscripten/common.h
+new file mode 100644
+index 0000000000..3a2d3c9099
+--- /dev/null
++++ b/modules/video_output/emscripten/common.h
+@@ -0,0 +1,46 @@
++/*****************************************************************************
++ * common.h: Emscripten decoder/vout common code
++ *****************************************************************************
++ * Copyright (C) 2021 VLC authors and VideoLAN
++ *
++ * 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.
++ *****************************************************************************/
++
++#ifndef EMSCRIPTEN_COMMON_H
++#define EMSCRIPTEN_COMMON_H
++
++#ifndef __cplusplus
++# error This only supports C++
++#endif
++
++#include <vlc_threads.h>
++#include <vlc_picture.h>
++
++#include <emscripten/html5_webgl.h>
++//#include <emscripten/val.h>
++
++#define WEBCODEC_MAX_PICTURES 32
++
++struct webcodec_context
++{
++    pthread_t decoder_worker;
++};
++
++struct webcodec_picture_sys_t
++{
++    int pictureId;
++};
++
++#endif // EMSCRIPTEN_COMMON_H
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0028-vout-Add-an-emscripten-webcodec-glinterop-module.patch b/vlc_patches/aug/0028-vout-Add-an-emscripten-webcodec-glinterop-module.patch
new file mode 100644
index 0000000000000000000000000000000000000000..8a0739a8a055025f0719608a960cdde93c49156a
--- /dev/null
+++ b/vlc_patches/aug/0028-vout-Add-an-emscripten-webcodec-glinterop-module.patch
@@ -0,0 +1,266 @@
+From 3b3a3979fb7d87035bd5607aeb3f8a3b66aacde4 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Thu, 3 Feb 2022 10:00:49 +0100
+Subject: [PATCH 28/77] vout: Add an emscripten/webcodec glinterop module
+
+---
+ modules/video_output/Makefile.am              |   7 +
+ .../opengl/interop_emscripten.cpp             | 228 ++++++++++++++++++
+ 2 files changed, 235 insertions(+)
+ create mode 100644 modules/video_output/opengl/interop_emscripten.cpp
+
+diff --git a/modules/video_output/Makefile.am b/modules/video_output/Makefile.am
+index 191992d887..ea3a0c6da9 100644
+--- a/modules/video_output/Makefile.am
++++ b/modules/video_output/Makefile.am
+@@ -305,6 +305,13 @@ vout_LTLIBRARIES += libegl_android_plugin.la libglinterop_android_plugin.la
+ endif
+ endif
+ 
++### Emscripten
++libglinterop_emscripten_plugin_la_SOURCES = video_output/opengl/interop_emscripten.cpp \
++	video_output/opengl/interop.h
++libglinterop_emscripten_plugin_la_CXXFLAGS = $(AM_CXXFLAGS) -DUSE_OPENGL_ES2
++if HAVE_EMSCRIPTEN
++vout_LTLIBRARIES += libglinterop_emscripten_plugin.la
++endif
+ 
+ ### FrameBuffer ###
+ 
+diff --git a/modules/video_output/opengl/interop_emscripten.cpp b/modules/video_output/opengl/interop_emscripten.cpp
+new file mode 100644
+index 0000000000..123c1fdb25
+--- /dev/null
++++ b/modules/video_output/opengl/interop_emscripten.cpp
+@@ -0,0 +1,228 @@
++/*****************************************************************************
++ * interop_emscripten.cpp: OpenGL Emscripten/Webcodec opaque converter
++ *****************************************************************************
++ * Copyright (C) 2021 VLC authors and VideoLAN
++ *
++ * 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
++
++//#ifndef __EMSCRIPTEN__
++//# error this file must be built with emscripten
++//#endif
++
++#include <vlc_common.h>
++#include <vlc_plugin.h>
++#include "interop.h"
++#include "gl_common.h"
++#include <cassert>
++#include "../emscripten/common.h"
++
++#include <emscripten.h>
++#include <emscripten/em_js.h>
++#include <emscripten/val.h>
++#include <emscripten/html5_webgl.h>
++
++struct EmscriptenInterop
++{
++    struct
++    {
++        PFNGLBINDTEXTUREPROC BindTexture;
++    } gl;
++};
++
++static int
++tc_emscripten_op_allocate_textures(const struct vlc_gl_interop *interop, GLuint *textures,
++                          const GLsizei *tex_width, const GLsizei *tex_height)
++{
++    (void) tex_width; (void) tex_height;
++    assert(textures[0] != 0);
++
++    return VLC_SUCCESS;
++}
++
++EM_ASYNC_JS(void, bindVideoFrame, (int pictureId), {
++    let frame = await Module.awaitFrame(pictureId);
++
++    let glCtx = Module.glCtx;
++    glCtx.texImage2D(glCtx.TEXTURE_2D, 0, glCtx.RGBA, frame.codedWidth, frame.codedHeight, 0,
++                glCtx.RGBA, glCtx.UNSIGNED_BYTE, frame);
++})
++
++static int
++tc_emscripten_op_update(const struct vlc_gl_interop *interop, GLuint *textures,
++               const GLsizei *tex_width, const GLsizei *tex_height,
++               picture_t *pic, const size_t *plane_offset)
++{
++    auto sys = static_cast<EmscriptenInterop *>( interop->priv );
++
++    sys->gl.BindTexture(interop->tex_target, textures[0]);
++
++    auto pictureId = reinterpret_cast<uintptr_t>( pic->p_sys );
++    bindVideoFrame(pictureId);
++
++    return VLC_SUCCESS;
++}
++
++extern "C"
++{
++EMSCRIPTEN_KEEPALIVE int getDecoderWorker(vlc_video_context* vctx)
++{
++    auto wcCtx = static_cast<webcodec_context*>(
++                vlc_video_context_GetPrivate(vctx, VLC_VIDEO_CONTEXT_WEBCODEC));
++    if (!vctx)
++        return 0;
++    return wcCtx->decoder_worker;
++}
++}
++
++EM_JS(void, transferMessagePort, (vlc_video_context* vctx), {
++    function onDecoderMessage(msg) {
++        let data = msg['data'];
++        if (data.customCmd == 'displayFrame') {
++            let pictureId = data.pictureId;
++            let pictureIdx = pictureId % 32;
++            let frame = data['frame'];
++            console.log('SINK Received frame ' + pictureId );
++            if ( Module.glConv.promiseResolvers[pictureIdx] ) {
++                /* The interop is already waiting for this frame, resolve the promise */
++                console.log('SINK Renderer is already waiting for frame ' + pictureId );
++                Module.glConv.promiseResolvers[pictureIdx]( frame );
++            } else {
++                /* The frame hasn't arrived to the interop yet, queue it */
++                console.log('SINK No resolver, queuing frame ' + pictureId );
++                Module.glConv.frameQueue[pictureIdx] = frame;
++            }
++        }
++    };
++    Module.msgChannel = new MessageChannel();
++    Module.msgChannel.port1.onmessage = onDecoderMessage;
++    let workerId = _getDecoderWorker(vctx);
++    if (workerId == 0)
++        return;
++    self.postMessage({
++        customCmd: 'transferMessagePort',
++        targetThread: workerId,
++        transferList: [Module.msgChannel.port2],
++    }, [Module.msgChannel.port2]);
++});
++
++EM_JS(void, initGlConvWorker, (int maxPictures), {
++    Module.glConv = {};
++    Module.glConv.promiseResolvers = [];
++    Module.glConv.frameQueue = [];
++    Module.glConv.lastFrame = {
++        pictureId: -1,
++        frame: undefined
++    };
++
++    Module.awaitFrame = async function(pictureId) {
++        console.log('CONSUME Fetching frame ' + pictureId);
++        if (Module.glConv.lastFrame.pictureId == pictureId) {
++            console.log('Reusing last frame');
++            return Module.glConv.lastFrame.frame;
++        }
++        let pictureIndex = pictureId % 32;
++        let p = new Promise((resolve, reject) => {
++            if ( Module.glConv.frameQueue[pictureIndex] ) {
++                console.log('CONSUME Frame ', pictureId, '(index ', pictureIndex,
++                            ') was already queued');
++                let frame = Module.glConv.frameQueue[pictureIndex];
++                resolve(frame);
++                Module.glConv.frameQueue[pictureIndex] = undefined;
++            } else {
++                console.log('CONSUME Frame ', pictureId, '(index ', pictureIndex,
++                            ') not available yet; exposing resolver' );
++                Module.glConv.promiseResolvers[pictureIndex] = resolve;
++            }
++        });
++        let frame = await p;
++        Module.glConv.promiseResolvers[pictureId] = undefined;
++        if (Module.glConv.lastFrame.frame)
++            Module.glConv.lastFrame.frame.close();
++        Module.glConv.lastFrame.frame = frame;
++        Module.glConv.lastFrame.pictureId = pictureId;
++        console.log('CONSUME got frame');
++        return frame;
++    }
++})
++
++EM_JS(void, closeMessagePort, (void), {
++    if (Module.msgChannel)
++        delete Module.msgChannel;
++});
++
++static void
++Close(struct vlc_gl_interop *)
++{
++    closeMessagePort();
++}
++
++static int
++Open(vlc_object_t *obj)
++{
++    auto interop = reinterpret_cast<vlc_gl_interop*>( obj );
++
++    if (interop->fmt_in.i_chroma != VLC_CODEC_WEBCODEC_OPAQUE)
++        return VLC_EGENERIC;
++
++    EmscriptenInterop *sys = (decltype(sys))
++        vlc_obj_malloc(VLC_OBJECT(interop), sizeof *sys);
++    if (sys == NULL)
++        return VLC_EGENERIC;
++    sys->gl.BindTexture = (decltype(sys->gl.BindTexture))
++        vlc_gl_GetProcAddress(interop->gl, "glBindTexture");
++    if (sys->gl.BindTexture == NULL)
++        return VLC_EGENERIC;
++
++    static const struct vlc_gl_interop_ops ops = {
++        .allocate_textures = tc_emscripten_op_allocate_textures,
++        .update_textures = tc_emscripten_op_update,
++        .close = Close,
++    };
++    interop->ops = &ops;
++    initGlConvWorker(WEBCODEC_MAX_PICTURES);
++    transferMessagePort(interop->vctx);
++
++    interop->tex_target = GL_TEXTURE_2D;
++    interop->fmt_out.i_chroma = VLC_CODEC_RGBA;
++    interop->fmt_out.space = COLOR_SPACE_UNDEF;
++    interop->tex_count = 1;
++    interop->texs[0] = vlc_gl_interop::vlc_gl_tex_cfg{
++            /*.w =*/ { 1, 1 },
++            /*.h =*/ { 1, 1 },
++            /*.internal =*/ GL_RGBA,
++            /*.format = */GL_RGBA,
++            /*.type = */GL_UNSIGNED_BYTE,
++    };
++    interop->fmt_out.i_rmask = 0xFF0000;
++    interop->fmt_out.i_gmask = 0x00FF00;
++    interop->fmt_out.i_bmask = 0x0000FF;
++    interop->priv = sys;
++
++    interop->fmt_out.orientation = ORIENT_VFLIPPED;
++
++    return VLC_SUCCESS;
++}
++
++vlc_module_begin ()
++    set_description("Emscripten OpenGL SurfaceTexture converter")
++    set_capability("glinterop", 1)
++    set_callback(Open)
++    set_subcategory(SUBCAT_VIDEO_VOUT)
++vlc_module_end ()
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0029-vout-emscripten-Adapt-to-webcodec-changes.patch b/vlc_patches/aug/0029-vout-emscripten-Adapt-to-webcodec-changes.patch
new file mode 100644
index 0000000000000000000000000000000000000000..a6b70ef6f172357629913eefe7818f75c80132d8
--- /dev/null
+++ b/vlc_patches/aug/0029-vout-emscripten-Adapt-to-webcodec-changes.patch
@@ -0,0 +1,230 @@
+From 448ea00d254b1b970000eb60ba06f55c1186b903 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Thu, 3 Feb 2022 10:11:05 +0100
+Subject: [PATCH 29/77] vout: emscripten: Adapt to webcodec changes
+
+---
+ modules/video_output/emscripten.cpp | 123 ++++++++++++++++++++++------
+ 1 file changed, 100 insertions(+), 23 deletions(-)
+
+diff --git a/modules/video_output/emscripten.cpp b/modules/video_output/emscripten.cpp
+index 857d921c27..0c863132f6 100644
+--- a/modules/video_output/emscripten.cpp
++++ b/modules/video_output/emscripten.cpp
+@@ -25,7 +25,9 @@
+ # include <config.h>
+ #endif
+ 
+-#include <stdarg.h>
++#include <cstdarg>
++#include <new>
++#include <string>
+ 
+ #include <vlc_common.h>
+ #include <vlc_plugin.h>
+@@ -34,9 +36,13 @@
+ #include <vlc_opengl.h>
+ 
+ #include "./opengl/vout_helper.h"
++#include "opengl/renderer.h"
++
++#include "emscripten/common.h"
+ 
+ #include <emscripten.h>
+ #include <emscripten/html5.h>
++#include <emscripten/val.h>
+ #include <webgl/webgl2.h>
+ // eglGetProcAddress
+ #include <EGL/egl.h>
+@@ -46,19 +52,32 @@ static const struct vout_window_operations ops = {
+     //vout_window_ReportSize() should be called from here
+ };
+ 
+-typedef struct gl_sys_t
+-{
+-    unsigned width;
+-    unsigned height;
++//MAIN_THREAD_
++//EM_JS(emscripten::EM_VAL, GetCanvasSize, (), {
++//    let canvas = document.getElementById('canvas');
++//    if (!canvas)
++//        return Emval.bindValue([]);
++//    return Emval.bindValue([canvas.width, canvas.height]);
++//});
+ 
++typedef struct emscripten_gl_sys_t
++{
+     EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context;
+-} gl_sys_t;
++} emscripten_gl_sys_t;
+ 
+ static int OpenWindow(vout_window_t *wnd)
+ {
+     wnd->type = VOUT_WINDOW_TYPE_EMSCRIPTEN_WEBGL;
+     wnd->ops = &ops;
++    wnd->handle.canvas = "canvas";
+ 
++    //emscripten::val size = GetCanvasSize();
++    //std::vector<int> vec_size = emscripten::vecFromJSArray(size);
++    //if (vec_size.size() != 2)
++    //    return VLC_EGENERIC;
++
++    //vout_window_ReportSize(wnd, vec_size[0], vec_size[1]);
++    vout_window_ReportSize(wnd, 1280, 720);
+     return VLC_SUCCESS;
+ }
+ 
+@@ -68,9 +87,10 @@ static void *GetProcAddress(vlc_gl_t *gl, const char *name)
+ 
+     return reinterpret_cast<void*>(eglGetProcAddress(name));
+ }
++
+ static int MakeCurrent(vlc_gl_t *gl)
+ {
+-    auto *sys = static_cast<gl_sys_t*>(gl->sys);
++    auto sys = static_cast<emscripten_gl_sys_t*>( gl->sys );
+ 
+     if (emscripten_webgl_make_context_current(sys->context) != EMSCRIPTEN_RESULT_SUCCESS)
+         return VLC_EGENERIC;
+@@ -85,8 +105,19 @@ static void ReleaseCurrent(vlc_gl_t *gl)
+ 
+ static void Swap(vlc_gl_t *gl)
+ {
++    /**
++     * There is no way to commit/swapbuffer from an offscreen canvas, so instead
++     * we render the offscreen canvas onto a bitmap and let the actual canvas
++     * display that bitmap from the main thread.
++     */
+     VLC_UNUSED(gl);
+-    emscripten_webgl_commit_frame();
++    EM_ASM({
++        let bitmap = Module.offscreenCanvas.transferToImageBitmap();
++        Module.voutMsgPort.postMessage({
++            customCmd: 'commitFrame',
++            bitmap: bitmap,
++        }, [bitmap]);
++    });
+ }
+ 
+ static void Resize(vlc_gl_t *gl, unsigned w, unsigned h)
+@@ -96,39 +127,85 @@ static void Resize(vlc_gl_t *gl, unsigned w, unsigned h)
+     VLC_UNUSED(h);
+ }
+ 
++EM_ASYNC_JS(void, getVoutMessagePort, (), {
++    let p = new Promise((resolve, reject) => {
++        let listener = function(e) {
++            let msg = e.data;
++            if (msg.customCmd == 'getVoutMessagePortResult') {
++                Module.voutMsgPort = msg['msgPort'];
++                resolve();
++                self.removeEventListener('message', listener);
++            };
++        };
++        self.addEventListener('message', listener);
++        self.postMessage({
++            customCmd: 'getVoutMessagePort'
++        });
++    });
++    await p;
++});
++
++EM_JS(int, createGlContext, (int width, int height), {
++    Module.offscreenCanvas = new OffscreenCanvas(width, height);
++    Module.glCtx = Module.offscreenCanvas.getContext('webgl2');
++
++    return GL.registerContext(Module.glCtx, {
++        antialias: false
++    });
++});
++
+ static void Close (vlc_gl_t *gl)
+ {
+-    free(gl->sys);
++    auto sys = static_cast<emscripten_gl_sys_t*>( gl->sys );
++    delete sys;
+ }
+ 
+ static int Open (vlc_gl_t *gl, unsigned width, unsigned height)
+ {
+     VLC_UNUSED(width), VLC_UNUSED(height);
+ 
+-    EmscriptenWebGLContextAttributes attr;
+-
+-    emscripten_webgl_init_context_attributes(&attr);
+-    attr.majorVersion=2;
+-    attr.minorVersion=0;
+-    attr.explicitSwapControl = 1;
+-
+     vout_window_t *wnd = gl->surface;
+ 
+     if (wnd->type != VOUT_WINDOW_TYPE_EMSCRIPTEN_WEBGL)
+         goto error;
+ 
+-    gl_sys_t *sys;
++    emscripten_gl_sys_t *sys;
+ 
+-    gl->sys = sys = static_cast<gl_sys_t*>(calloc(1, sizeof(*sys)));
++    gl->sys = sys = new (std::nothrow)emscripten_gl_sys_t;
+     if (!sys)
+         return VLC_ENOMEM;
+ 
+-    sys->context = emscripten_webgl_create_context("#canvas", &attr);
++    MAIN_THREAD_EM_ASM({
++        if (Module.canvasCtx === undefined) {
++            let canvasName = UTF8ToString($1);
++            Module.canvasCtx = document.getElementById(canvasName).getContext('2d');
++        }
++        function onVoutMessage(msg) {
++            let data = msg['data'];
++            if (data.customCmd == 'commitFrame') {
++                Module.canvasCtx.drawImage(data['bitmap'], 0, 0);
++            }
++        }
++        let w = Module.PThread.pthreads[$0].worker;
++        w.addEventListener('message', function (e) {
++            let msg = e['data'];
++            if (msg.customCmd == 'getVoutMessagePort') {
++                let msgChannel = new MessageChannel();
++                msgChannel.port1.onmessage = onVoutMessage;
++                w.postMessage({
++                    customCmd: 'getVoutMessagePortResult',
++                    msgPort: msgChannel.port2,
++                }, [msgChannel.port2]);
++            }
++        });
++    }, pthread_self(), wnd->handle.canvas ? wnd->handle.canvas : "canvas");
++
++    getVoutMessagePort();
++    sys->context = createGlContext(width, height);
++
+     if (!sys->context) {
+-        msg_Err(gl, "Failed to make context current");
+         goto error;
+     }
+-
+     // Check that the WebGL context is valid
+     if (emscripten_webgl_make_context_current(sys->context) != EMSCRIPTEN_RESULT_SUCCESS) {
+         emscripten_log(EM_LOG_CONSOLE, "failed to make context current");
+@@ -146,8 +223,8 @@ static int Open (vlc_gl_t *gl, unsigned width, unsigned height)
+     gl->swap = Swap;
+     gl->get_proc_address = GetProcAddress;
+     gl->destroy = Close;
+-
+     return VLC_SUCCESS;
++
+ error:
+     Close(gl);
+     return VLC_EGENERIC;
+@@ -161,7 +238,7 @@ vlc_module_begin()
+     set_description(N_("Emscripten drawing area"))
+     set_subcategory(SUBCAT_VIDEO_VOUT)
+     set_capability("vout window", 10)
+-    set_callbacks(OpenWindow, NULL)
++    set_callbacks(OpenWindow, nullptr)
+ 
+     add_submodule ()
+     set_shortname("Emscripten GL")
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0030-opengl-sampler-avoid-crashing-on-unbind-location.patch b/vlc_patches/aug/0030-opengl-sampler-avoid-crashing-on-unbind-location.patch
new file mode 100644
index 0000000000000000000000000000000000000000..e4aff5b8a085f8188e4f7ede2adcdd76bf94bacf
--- /dev/null
+++ b/vlc_patches/aug/0030-opengl-sampler-avoid-crashing-on-unbind-location.patch
@@ -0,0 +1,82 @@
+From 5874bfc37d1ebd81e8b20382ec7b9a09207ca4ba Mon Sep 17 00:00:00 2001
+From: Alexandre Janniaux <ajanni@videolabs.io>
+Date: Wed, 23 Feb 2022 11:13:41 +0100
+Subject: [PATCH 30/77] opengl: sampler: avoid crashing on unbind location
+
+---
+ modules/video_output/opengl/sampler.c | 26 +++++++++++++++++++-------
+ 1 file changed, 19 insertions(+), 7 deletions(-)
+
+diff --git a/modules/video_output/opengl/sampler.c b/modules/video_output/opengl/sampler.c
+index 846cbc75ab..9df0446284 100644
+--- a/modules/video_output/opengl/sampler.c
++++ b/modules/video_output/opengl/sampler.c
+@@ -292,25 +292,30 @@ sampler_base_load(struct vlc_gl_sampler *sampler)
+     struct vlc_gl_format *glfmt = &sampler->glfmt;
+     struct vlc_gl_picture *pic = &priv->pic;
+ 
+-    if (priv->yuv_color)
++    if (priv->yuv_color && priv->uloc.ConvMatrix != -1)
+         vt->UniformMatrix4fv(priv->uloc.ConvMatrix, 1, GL_FALSE,
+                              priv->conv_matrix);
+ 
+     for (unsigned i = 0; i < glfmt->tex_count; ++i)
+     {
++        if (priv->uloc.Textures[i] == -1)
++            continue;
+         vt->Uniform1i(priv->uloc.Textures[i], i);
+ 
+         assert(pic->textures[i] != 0);
+         vt->ActiveTexture(GL_TEXTURE0 + i);
+         vt->BindTexture(glfmt->tex_target, pic->textures[i]);
+-
+     }
+ 
+     if (glfmt->tex_target == GL_TEXTURE_RECTANGLE)
+     {
+         for (unsigned i = 0; i < glfmt->tex_count; ++i)
++        {
++            if (priv->uloc.TexSizes[i] == -1)
++                continue;
+             vt->Uniform2f(priv->uloc.TexSizes[i], glfmt->tex_widths[i],
+                           glfmt->tex_heights[i]);
++        }
+     }
+ 
+ #ifdef HAVE_LIBPLACEBO
+@@ -365,6 +370,9 @@ sampler_xyz12_load(struct vlc_gl_sampler *sampler)
+     struct vlc_gl_format *glfmt = &sampler->glfmt;
+     struct vlc_gl_picture *pic = &priv->pic;
+ 
++    if (priv->uloc.Textures[0] == -1)
++        return;
++
+     vt->Uniform1i(priv->uloc.Textures[0], 0);
+ 
+     assert(pic->textures[0] != 0);
+@@ -537,13 +545,17 @@ sampler_planes_load(struct vlc_gl_sampler *sampler)
+     struct vlc_gl_format *glfmt = &sampler->glfmt;
+     struct vlc_gl_picture *pic = &priv->pic;
+ 
+-    vt->Uniform1i(priv->uloc.Textures[0], 0);
++    if (priv->uloc.Textures[0] != -1)
++    {
++        vt->Uniform1i(priv->uloc.Textures[0], 0);
+ 
+-    assert(pic->textures[plane] != 0);
+-    vt->ActiveTexture(GL_TEXTURE0);
+-    vt->BindTexture(glfmt->tex_target, pic->textures[plane]);
++        assert(pic->textures[plane] != 0);
++        vt->ActiveTexture(GL_TEXTURE0);
++        vt->BindTexture(glfmt->tex_target, pic->textures[plane]);
++    }
+ 
+-    if (glfmt->tex_target == GL_TEXTURE_RECTANGLE)
++    if (glfmt->tex_target == GL_TEXTURE_RECTANGLE &&
++        priv->uloc.TexSizes[0] != -1)
+     {
+         vt->Uniform2f(priv->uloc.TexSizes[0], glfmt->tex_widths[plane],
+                       glfmt->tex_heights[plane]);
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0031-audio_output-emscripten-remove-set_category.patch b/vlc_patches/aug/0031-audio_output-emscripten-remove-set_category.patch
new file mode 100644
index 0000000000000000000000000000000000000000..77f978d212efe379d9d2d120d90ce064aab31edb
--- /dev/null
+++ b/vlc_patches/aug/0031-audio_output-emscripten-remove-set_category.patch
@@ -0,0 +1,25 @@
+From c4f0a0fe0e6977f99bcc16eb13d72df2adbbf0e9 Mon Sep 17 00:00:00 2001
+From: Alexandre Janniaux <ajanni@videolabs.io>
+Date: Wed, 23 Feb 2022 11:15:45 +0100
+Subject: [PATCH 31/77] audio_output: emscripten: remove set_category
+
+please fixup.
+---
+ modules/audio_output/emscripten.cpp | 1 -
+ 1 file changed, 1 deletion(-)
+
+diff --git a/modules/audio_output/emscripten.cpp b/modules/audio_output/emscripten.cpp
+index 6acea387e3..a7e9fb0d4d 100644
+--- a/modules/audio_output/emscripten.cpp
++++ b/modules/audio_output/emscripten.cpp
+@@ -416,7 +416,6 @@ vlc_module_begin ()
+ 	set_description( N_("Emscripten Worklet audio output") )
+ 	set_shortname( "emworklet" )
+ 	set_capability( "audio output", 100 )
+-	set_category( CAT_AUDIO )
+ 	set_subcategory( SUBCAT_AUDIO_AOUT )
+ 	set_callbacks( Open, Close )
+ vlc_module_end ()
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0032-vlc_vout_window-add-canvas-handle-type.patch b/vlc_patches/aug/0032-vlc_vout_window-add-canvas-handle-type.patch
new file mode 100644
index 0000000000000000000000000000000000000000..a72e7df3c6988b49e8629a2260ebeaf354bb99ca
--- /dev/null
+++ b/vlc_patches/aug/0032-vlc_vout_window-add-canvas-handle-type.patch
@@ -0,0 +1,24 @@
+From 62f2dea07966e934bec0185ecaa62cffcfa804f5 Mon Sep 17 00:00:00 2001
+From: Alexandre Janniaux <ajanni@videolabs.io>
+Date: Tue, 22 Feb 2022 16:47:25 +0100
+Subject: [PATCH 32/77] vlc_vout_window: add canvas handle type
+
+---
+ include/vlc_vout_window.h | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/include/vlc_vout_window.h b/include/vlc_vout_window.h
+index b5a01b33e4..0803e65b78 100644
+--- a/include/vlc_vout_window.h
++++ b/include/vlc_vout_window.h
+@@ -387,6 +387,7 @@ typedef struct vout_window_t {
+         void     *dcomp_visual;  /**<  Win32 direct composition visual */
+         uint32_t crtc;           /**< KMS CRTC identifier */
+         uint32_t em_context;     /* Emscripten webgl context */
++        const char *canvas;
+     } handle;
+ 
+     /** Display server (mandatory)
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0033-opengl-filter-forward-declaration-for-samplers.patch b/vlc_patches/aug/0033-opengl-filter-forward-declaration-for-samplers.patch
new file mode 100644
index 0000000000000000000000000000000000000000..b7ff21d4e74aacdfce6f81a9b3d6a3dc2ab8bad6
--- /dev/null
+++ b/vlc_patches/aug/0033-opengl-filter-forward-declaration-for-samplers.patch
@@ -0,0 +1,93 @@
+From 4d52418f13ee742ae96f2a1d5fa72f411122eb40 Mon Sep 17 00:00:00 2001
+From: Alexandre Janniaux <ajanni@videolabs.io>
+Date: Tue, 22 Feb 2022 17:06:21 +0100
+Subject: [PATCH 33/77] opengl: filter: forward-declaration for samplers
+
+---
+ modules/video_output/opengl/filter.c      | 1 +
+ modules/video_output/opengl/filter.h      | 4 ++--
+ modules/video_output/opengl/filter_mock.c | 1 +
+ modules/video_output/opengl/filter_priv.h | 1 +
+ modules/video_output/opengl/renderer.c    | 1 +
+ modules/video_output/opengl/sampler.c     | 1 +
+ 6 files changed, 7 insertions(+), 2 deletions(-)
+
+diff --git a/modules/video_output/opengl/filter.c b/modules/video_output/opengl/filter.c
+index 7f0aa73e7c..491ce88dd5 100644
+--- a/modules/video_output/opengl/filter.c
++++ b/modules/video_output/opengl/filter.c
+@@ -31,6 +31,7 @@
+ #include <vlc_modules.h>
+ 
+ #include "gl_api.h"
++#include "picture.h"
+ 
+ struct vlc_gl_filter *
+ vlc_gl_filter_New(struct vlc_gl_t *gl, const struct vlc_gl_api *api)
+diff --git a/modules/video_output/opengl/filter.h b/modules/video_output/opengl/filter.h
+index dc81b69d8a..d8b672cc35 100644
+--- a/modules/video_output/opengl/filter.h
++++ b/modules/video_output/opengl/filter.h
+@@ -24,9 +24,9 @@
+ 
+ #include <vlc_tick.h>
+ 
+-#include "picture.h"
+-
+ struct vlc_gl_filter;
++struct vlc_gl_picture;
++struct vlc_gl_format;
+ 
+ #ifdef __cplusplus
+ extern "C"
+diff --git a/modules/video_output/opengl/filter_mock.c b/modules/video_output/opengl/filter_mock.c
+index 64a73b303e..d455de4937 100644
+--- a/modules/video_output/opengl/filter_mock.c
++++ b/modules/video_output/opengl/filter_mock.c
+@@ -73,6 +73,7 @@
+ #include "gl_common.h"
+ #include "gl_util.h"
+ #include "sampler.h"
++#include "picture.h"
+ 
+ #define MOCK_CFG_PREFIX "mock-"
+ 
+diff --git a/modules/video_output/opengl/filter_priv.h b/modules/video_output/opengl/filter_priv.h
+index 7b702ddb17..cd0e3084fa 100644
+--- a/modules/video_output/opengl/filter_priv.h
++++ b/modules/video_output/opengl/filter_priv.h
+@@ -27,6 +27,7 @@
+ #include <vlc_picture.h>
+ 
+ #include "filter.h"
++#include "picture.h"
+ 
+ struct vlc_gl_filter_priv {
+     struct vlc_gl_filter filter;
+diff --git a/modules/video_output/opengl/renderer.c b/modules/video_output/opengl/renderer.c
+index aebbb84e67..092fc7b7e4 100644
+--- a/modules/video_output/opengl/renderer.c
++++ b/modules/video_output/opengl/renderer.c
+@@ -41,6 +41,7 @@
+ #include "gl_util.h"
+ #include "vout_helper.h"
+ #include "sampler.h"
++#include "picture.h"
+ 
+ #define SPHERE_RADIUS 1.f
+ 
+diff --git a/modules/video_output/opengl/sampler.c b/modules/video_output/opengl/sampler.c
+index 9df0446284..80f9d43e10 100644
+--- a/modules/video_output/opengl/sampler.c
++++ b/modules/video_output/opengl/sampler.c
+@@ -37,6 +37,7 @@
+ #include "gl_api.h"
+ #include "gl_common.h"
+ #include "gl_util.h"
++#include "picture.h"
+ 
+ struct vlc_gl_sampler_priv {
+     struct vlc_gl_sampler sampler;
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0034-include-move-vlc_opengl_filter.h-to-core.patch b/vlc_patches/aug/0034-include-move-vlc_opengl_filter.h-to-core.patch
new file mode 100644
index 0000000000000000000000000000000000000000..37ef7b076f363151694779380ce0230ff9d8319e
--- /dev/null
+++ b/vlc_patches/aug/0034-include-move-vlc_opengl_filter.h-to-core.patch
@@ -0,0 +1,160 @@
+From 356c5e7c876be70e73d84fa08b422794d674a540 Mon Sep 17 00:00:00 2001
+From: Alexandre Janniaux <ajanni@videolabs.io>
+Date: Tue, 22 Feb 2022 17:07:53 +0100
+Subject: [PATCH 34/77] include: move vlc_opengl_filter.h to core
+
+---
+ include/vlc_opengl_filter.h | 141 ++++++++++++++++++++++++++++++++++++
+ 1 file changed, 141 insertions(+)
+ create mode 100644 include/vlc_opengl_filter.h
+
+diff --git a/include/vlc_opengl_filter.h b/include/vlc_opengl_filter.h
+new file mode 100644
+index 0000000000..8cffea7708
+--- /dev/null
++++ b/include/vlc_opengl_filter.h
+@@ -0,0 +1,141 @@
++/*****************************************************************************
++ * filter.h
++ *****************************************************************************
++ * Copyright (C) 2020 VLC authors and VideoLAN
++ * Copyright (C) 2020 Videolabs
++ *
++ * 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.
++ *****************************************************************************/
++
++#ifndef VLC_GL_FILTER_H
++#define VLC_GL_FILTER_H
++
++#include <vlc_tick.h>
++
++struct vlc_gl_filter;
++struct vlc_gl_sampler;
++
++#ifdef __cplusplus
++extern "C"
++{
++#endif
++
++struct vlc_gl_tex_size {
++    unsigned width;
++    unsigned height;
++};
++
++struct vlc_gl_input_meta {
++    vlc_tick_t pts;
++    unsigned plane;
++};
++
++typedef int
++vlc_gl_filter_open_fn(struct vlc_gl_filter *filter,
++                      const config_chain_t *config,
++                      struct vlc_gl_tex_size *size_out);
++
++struct vlc_gl_filter_ops {
++    /**
++     * Draw the result of the filter to the current framebuffer
++     */
++    int (*draw)(struct vlc_gl_filter *filter,
++                const struct vlc_gl_input_meta *meta);
++
++    /**
++     * Free filter resources
++     */
++    void (*close)(struct vlc_gl_filter *filter);
++};
++
++struct vlc_gl_filter_owner_ops {
++    /**
++     * Get the sampler associated to this filter.
++     *
++     * The instance is lazy-loaded (to avoid creating one for blend filters).
++     * Successive calls to this function for the same filter is guaranteed to
++     * always return the same sampler.
++     *
++     * Important: filter->config must be initialized *before* getting the
++     * sampler, since the sampler behavior may depend on it.
++     *
++     * \param filter the filter
++     * \return sampler the sampler, NULL on error
++     */
++    struct vlc_gl_sampler *
++    (*get_sampler)(struct vlc_gl_filter *filter);
++};
++
++/**
++ * OpenGL filter, in charge of a rendering pass.
++ */
++struct vlc_gl_filter {
++    vlc_object_t obj;
++    module_t *module;
++
++    const struct vlc_gl_api *api;
++
++    struct {
++        /**
++         * An OpenGL filter may either operate on the input RGBA picture, or on
++         * individual input planes (without chroma conversion) separately.
++         *
++         * In practice, this is useful for deinterlace filters.
++         *
++         * This flag must be set by the filter module (default is false).
++         */
++        bool filter_planes;
++
++        /**
++         * A blend filter draws over the input picture (without reading it).
++         *
++         * Meaningless if filter_planes is true.
++         *
++         * This flag must be set by the filter module (default is false).
++         */
++        bool blend;
++
++        /**
++         * Request MSAA level.
++         *
++         * This value must be set by the filter module (default is 0, which
++         * means disabled).
++         *
++         * Meaningless if filter_planes is true.
++         *
++         * The actual MSAA level may be overwritten to 0 if multisampling is
++         * not supported, or to a higher value if another filter rendering on
++         * the same framebuffer requested a higher MSAA level.
++         */
++        unsigned msaa_level;
++    } config;
++
++    const struct vlc_gl_filter_ops *ops;
++    void *sys;
++
++    const struct vlc_gl_filter_owner_ops *owner_ops;
++};
++
++static inline struct vlc_gl_sampler *
++vlc_gl_filter_GetSampler(struct vlc_gl_filter *filter)
++{
++    return filter->owner_ops->get_sampler(filter);
++}
++
++#ifdef __cplusplus
++}
++#endif
++
++#endif
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0035-opengl-mock-expose-module-on-emscripten-platform.patch b/vlc_patches/aug/0035-opengl-mock-expose-module-on-emscripten-platform.patch
new file mode 100644
index 0000000000000000000000000000000000000000..a0ac19dc5e34b2c72a391d7fcab73c2416b18704
--- /dev/null
+++ b/vlc_patches/aug/0035-opengl-mock-expose-module-on-emscripten-platform.patch
@@ -0,0 +1,29 @@
+From e8dffa4b13264baa22bdb5c59b3a6c638753a589 Mon Sep 17 00:00:00 2001
+From: Alexandre Janniaux <ajanni@videolabs.io>
+Date: Tue, 22 Feb 2022 17:45:12 +0100
+Subject: [PATCH 35/77] opengl: mock: expose module on emscripten platform
+
+---
+ modules/video_output/opengl/Makefile.am | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+diff --git a/modules/video_output/opengl/Makefile.am b/modules/video_output/opengl/Makefile.am
+index bc4079fefc..bdb5a1b3eb 100644
+--- a/modules/video_output/opengl/Makefile.am
++++ b/modules/video_output/opengl/Makefile.am
+@@ -108,6 +108,12 @@ libglfilter_mock_plugin_la_LIBADD += libvlc_opengl.la $(GL_LIBS)
+ noinst_LTLIBRARIES += libglfilter_mock_plugin.la
+ endif
+ 
++if HAVE_EMSCRIPTEN
++libglfilter_mock_plugin_la_LIBADD += libvlc_opengles.la $(GL_LIBS)
++libglfilter_mock_plugin_la_CFLAGS = -DUSE_OPENGL_ES2=1
++noinst_LTLIBRARIES += libglfilter_mock_plugin.la
++endif
++
+ if HAVE_IOS
+ libglfilter_mock_plugin_la_LIBADD += libvlc_opengles.la $(GLES2_LIBS)
+ libglfilter_mock_plugin_la_CFLAGS = -DUSE_OPENGL_ES2=1
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0036-vlc_opengl_filter-add-gl-pointer.patch b/vlc_patches/aug/0036-vlc_opengl_filter-add-gl-pointer.patch
new file mode 100644
index 0000000000000000000000000000000000000000..3c5fbe84df41d0d5359c6d3e3f5180b61603a502
--- /dev/null
+++ b/vlc_patches/aug/0036-vlc_opengl_filter-add-gl-pointer.patch
@@ -0,0 +1,37 @@
+From 78f9501c96671e0793b4aeca5d712b5ff21709f2 Mon Sep 17 00:00:00 2001
+From: Alexandre Janniaux <ajanni@videolabs.io>
+Date: Tue, 22 Feb 2022 17:55:01 +0100
+Subject: [PATCH 36/77] vlc_opengl_filter: add gl pointer
+
+---
+ include/vlc_opengl_filter.h           | 1 +
+ modules/video_output/opengl/filters.c | 1 +
+ 2 files changed, 2 insertions(+)
+
+diff --git a/include/vlc_opengl_filter.h b/include/vlc_opengl_filter.h
+index 8cffea7708..02228ec15c 100644
+--- a/include/vlc_opengl_filter.h
++++ b/include/vlc_opengl_filter.h
+@@ -85,6 +85,7 @@ struct vlc_gl_filter {
+     vlc_object_t obj;
+     module_t *module;
+ 
++    vlc_gl_t *gl;
+     const struct vlc_gl_api *api;
+ 
+     struct {
+diff --git a/modules/video_output/opengl/filters.c b/modules/video_output/opengl/filters.c
+index b93c1ee2fe..abd00b755b 100644
+--- a/modules/video_output/opengl/filters.c
++++ b/modules/video_output/opengl/filters.c
+@@ -183,6 +183,7 @@ vlc_gl_filters_Append(struct vlc_gl_filters *filters, const char *name,
+     struct vlc_gl_filter *filter = vlc_gl_filter_New(filters->gl, filters->api);
+     if (!filter)
+         return NULL;
++    filter->gl = filters->gl;
+ 
+     struct vlc_gl_filter_priv *priv = vlc_gl_filter_PRIV(filter);
+ 
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0037-opengl-filter-move-to-vlc_opengl_filter.patch b/vlc_patches/aug/0037-opengl-filter-move-to-vlc_opengl_filter.patch
new file mode 100644
index 0000000000000000000000000000000000000000..15fe2ac8a27e93bb1cce7b6000d5aa0a851dff5a
--- /dev/null
+++ b/vlc_patches/aug/0037-opengl-filter-move-to-vlc_opengl_filter.patch
@@ -0,0 +1,287 @@
+From c1b6ccbce73b445fff4c7df1ffb84bd83eb3da48 Mon Sep 17 00:00:00 2001
+From: Alexandre Janniaux <ajanni@videolabs.io>
+Date: Wed, 23 Feb 2022 13:40:41 +0100
+Subject: [PATCH 37/77] opengl: filter: move to vlc_opengl_filter
+
+---
+ include/vlc_opengl_filter.h          |  67 ++++++++-----
+ modules/video_output/opengl/filter.h | 137 +--------------------------
+ 2 files changed, 42 insertions(+), 162 deletions(-)
+
+diff --git a/include/vlc_opengl_filter.h b/include/vlc_opengl_filter.h
+index 02228ec15c..3a1f18197d 100644
+--- a/include/vlc_opengl_filter.h
++++ b/include/vlc_opengl_filter.h
+@@ -1,5 +1,5 @@
+ /*****************************************************************************
+- * filter.h
++ * vlc_opengl_filter.h
+  *****************************************************************************
+  * Copyright (C) 2020 VLC authors and VideoLAN
+  * Copyright (C) 2020 Videolabs
+@@ -19,13 +19,14 @@
+  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+  *****************************************************************************/
+ 
+-#ifndef VLC_GL_FILTER_H
+-#define VLC_GL_FILTER_H
++#ifndef VLC_OPENGL_FILTER_H
++#define VLC_OPENGL_FILTER_H
+ 
+ #include <vlc_tick.h>
+ 
+ struct vlc_gl_filter;
+-struct vlc_gl_sampler;
++struct vlc_gl_picture;
++struct vlc_gl_format;
+ 
+ #ifdef __cplusplus
+ extern "C"
+@@ -45,37 +46,58 @@ struct vlc_gl_input_meta {
+ typedef int
+ vlc_gl_filter_open_fn(struct vlc_gl_filter *filter,
+                       const config_chain_t *config,
++                      const struct vlc_gl_format *glfmt,
+                       struct vlc_gl_tex_size *size_out);
+ 
++#define set_callback_opengl_filter(open) \
++    { \
++        vlc_gl_filter_open_fn *fn = open; \
++        (void) fn; \
++        set_callback(fn); \
++    }
++
+ struct vlc_gl_filter_ops {
+     /**
+      * Draw the result of the filter to the current framebuffer
+      */
+-    int (*draw)(struct vlc_gl_filter *filter,
++    int (*draw)(struct vlc_gl_filter *filter, const struct vlc_gl_picture *pic,
+                 const struct vlc_gl_input_meta *meta);
+ 
+     /**
+      * Free filter resources
+      */
+     void (*close)(struct vlc_gl_filter *filter);
+-};
+ 
+-struct vlc_gl_filter_owner_ops {
+     /**
+-     * Get the sampler associated to this filter.
++     * Request a (responsive) filter to adapt its output size (optional)
++     *
++     * A responsive filter is a filter for which the size of the produced
++     * pictures depends on the output (e.g. display) size rather than the
++     * input. This is for example the case for a renderer.
+      *
+-     * The instance is lazy-loaded (to avoid creating one for blend filters).
+-     * Successive calls to this function for the same filter is guaranteed to
+-     * always return the same sampler.
++     * A new output size is requested (size_out). The filter is authorized to
++     * change the size_out to enforce its own constraints.
+      *
+-     * Important: filter->config must be initialized *before* getting the
+-     * sampler, since the sampler behavior may depend on it.
++     * In addition, it may request to the previous filter (if any) an optimal
++     * size it wants to receive. If set to non-zero value, this previous filter
++     * will receive this size as its requested size (and so on).
+      *
+-     * \param filter the filter
+-     * \return sampler the sampler, NULL on error
++     * \retval true if the resize is accepted (possibly with a modified
++     *              size_out)
++     * \retval false if the resize is rejected (included on error)
+      */
+-    struct vlc_gl_sampler *
+-    (*get_sampler)(struct vlc_gl_filter *filter);
++    int (*request_output_size)(struct vlc_gl_filter *filter,
++                               struct vlc_gl_tex_size *size_out,
++                               struct vlc_gl_tex_size *optimal_in);
++
++    /**
++     * Callback to notify input size changes
++     *
++     * When a filter changes its output size as a result of
++     * request_output_size(), the next filter is notified by this callback.
++     */
++    void (*on_input_size_change)(struct vlc_gl_filter *filter,
++                                 const struct vlc_gl_tex_size *size);
+ };
+ 
+ /**
+@@ -85,8 +107,9 @@ struct vlc_gl_filter {
+     vlc_object_t obj;
+     module_t *module;
+ 
+-    vlc_gl_t *gl;
++    struct vlc_gl_t *gl;
+     const struct vlc_gl_api *api;
++    const struct vlc_gl_format *glfmt_in;
+ 
+     struct {
+         /**
+@@ -125,16 +148,8 @@ struct vlc_gl_filter {
+ 
+     const struct vlc_gl_filter_ops *ops;
+     void *sys;
+-
+-    const struct vlc_gl_filter_owner_ops *owner_ops;
+ };
+ 
+-static inline struct vlc_gl_sampler *
+-vlc_gl_filter_GetSampler(struct vlc_gl_filter *filter)
+-{
+-    return filter->owner_ops->get_sampler(filter);
+-}
+-
+ #ifdef __cplusplus
+ }
+ #endif
+diff --git a/modules/video_output/opengl/filter.h b/modules/video_output/opengl/filter.h
+index d8b672cc35..b8272a5115 100644
+--- a/modules/video_output/opengl/filter.h
++++ b/modules/video_output/opengl/filter.h
+@@ -19,139 +19,4 @@
+  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+  *****************************************************************************/
+ 
+-#ifndef VLC_GL_FILTER_H
+-#define VLC_GL_FILTER_H
+-
+-#include <vlc_tick.h>
+-
+-struct vlc_gl_filter;
+-struct vlc_gl_picture;
+-struct vlc_gl_format;
+-
+-#ifdef __cplusplus
+-extern "C"
+-{
+-#endif
+-
+-struct vlc_gl_tex_size {
+-    unsigned width;
+-    unsigned height;
+-};
+-
+-struct vlc_gl_input_meta {
+-    vlc_tick_t pts;
+-    unsigned plane;
+-};
+-
+-typedef int
+-vlc_gl_filter_open_fn(struct vlc_gl_filter *filter,
+-                      const config_chain_t *config,
+-                      const struct vlc_gl_format *glfmt,
+-                      struct vlc_gl_tex_size *size_out);
+-
+-#define set_callback_opengl_filter(open) \
+-    { \
+-        vlc_gl_filter_open_fn *fn = open; \
+-        (void) fn; \
+-        set_callback(fn); \
+-    }
+-
+-struct vlc_gl_filter_ops {
+-    /**
+-     * Draw the result of the filter to the current framebuffer
+-     */
+-    int (*draw)(struct vlc_gl_filter *filter, const struct vlc_gl_picture *pic,
+-                const struct vlc_gl_input_meta *meta);
+-
+-    /**
+-     * Free filter resources
+-     */
+-    void (*close)(struct vlc_gl_filter *filter);
+-
+-    /**
+-     * Request a (responsive) filter to adapt its output size (optional)
+-     *
+-     * A responsive filter is a filter for which the size of the produced
+-     * pictures depends on the output (e.g. display) size rather than the
+-     * input. This is for example the case for a renderer.
+-     *
+-     * A new output size is requested (size_out). The filter is authorized to
+-     * change the size_out to enforce its own constraints.
+-     *
+-     * In addition, it may request to the previous filter (if any) an optimal
+-     * size it wants to receive. If set to non-zero value, this previous filter
+-     * will receive this size as its requested size (and so on).
+-     *
+-     * \retval true if the resize is accepted (possibly with a modified
+-     *              size_out)
+-     * \retval false if the resize is rejected (included on error)
+-     */
+-    int (*request_output_size)(struct vlc_gl_filter *filter,
+-                               struct vlc_gl_tex_size *size_out,
+-                               struct vlc_gl_tex_size *optimal_in);
+-
+-    /**
+-     * Callback to notify input size changes
+-     *
+-     * When a filter changes its output size as a result of
+-     * request_output_size(), the next filter is notified by this callback.
+-     */
+-    void (*on_input_size_change)(struct vlc_gl_filter *filter,
+-                                 const struct vlc_gl_tex_size *size);
+-};
+-
+-/**
+- * OpenGL filter, in charge of a rendering pass.
+- */
+-struct vlc_gl_filter {
+-    vlc_object_t obj;
+-    module_t *module;
+-
+-    struct vlc_gl_t *gl;
+-    const struct vlc_gl_api *api;
+-    const struct vlc_gl_format *glfmt_in;
+-
+-    struct {
+-        /**
+-         * An OpenGL filter may either operate on the input RGBA picture, or on
+-         * individual input planes (without chroma conversion) separately.
+-         *
+-         * In practice, this is useful for deinterlace filters.
+-         *
+-         * This flag must be set by the filter module (default is false).
+-         */
+-        bool filter_planes;
+-
+-        /**
+-         * A blend filter draws over the input picture (without reading it).
+-         *
+-         * Meaningless if filter_planes is true.
+-         *
+-         * This flag must be set by the filter module (default is false).
+-         */
+-        bool blend;
+-
+-        /**
+-         * Request MSAA level.
+-         *
+-         * This value must be set by the filter module (default is 0, which
+-         * means disabled).
+-         *
+-         * Meaningless if filter_planes is true.
+-         *
+-         * The actual MSAA level may be overwritten to 0 if multisampling is
+-         * not supported, or to a higher value if another filter rendering on
+-         * the same framebuffer requested a higher MSAA level.
+-         */
+-        unsigned msaa_level;
+-    } config;
+-
+-    const struct vlc_gl_filter_ops *ops;
+-    void *sys;
+-};
+-
+-#ifdef __cplusplus
+-}
+-#endif
+-
+-#endif
++#include <vlc_opengl_filter.h>
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0038-opengl-mock-load-API-in-mock.patch b/vlc_patches/aug/0038-opengl-mock-load-API-in-mock.patch
new file mode 100644
index 0000000000000000000000000000000000000000..dc5b7153eb16b3e8a5990c8555523d25240e7e3f
--- /dev/null
+++ b/vlc_patches/aug/0038-opengl-mock-load-API-in-mock.patch
@@ -0,0 +1,34 @@
+From 098f9060b967025700bce2fe9e009e473f2eb0eb Mon Sep 17 00:00:00 2001
+From: Alexandre Janniaux <ajanni@videolabs.io>
+Date: Tue, 22 Feb 2022 17:55:36 +0100
+Subject: [PATCH 38/77] opengl: mock: load API in mock
+
+---
+ modules/video_output/opengl/filter_mock.c | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/modules/video_output/opengl/filter_mock.c b/modules/video_output/opengl/filter_mock.c
+index d455de4937..3915456d7e 100644
+--- a/modules/video_output/opengl/filter_mock.c
++++ b/modules/video_output/opengl/filter_mock.c
+@@ -102,6 +102,7 @@ struct sys {
+ 
+     float rotation_matrix[16];
+     float ar;
++    struct vlc_gl_api api;
+ };
+ 
+ static void
+@@ -587,6 +588,9 @@ Open(struct vlc_gl_filter *filter, const config_chain_t *config,
+ 
+     sys->sampler = NULL;
+ 
++    vlc_gl_api_Init(&sys->api, filter->gl);
++    filter->api = &sys->api;
++
+     int ret;
+     if (plane)
+         ret = InitPlane(filter, glfmt);
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0039-vlc_vout_display-VLC_OBJECT-wrap-vout_display_New.patch b/vlc_patches/aug/0039-vlc_vout_display-VLC_OBJECT-wrap-vout_display_New.patch
new file mode 100644
index 0000000000000000000000000000000000000000..804990e57dd9b703dbe6ef950d92956ea6182a43
--- /dev/null
+++ b/vlc_patches/aug/0039-vlc_vout_display-VLC_OBJECT-wrap-vout_display_New.patch
@@ -0,0 +1,39 @@
+From a2a7bee28811d1056880cb48f63f1d63a420a180 Mon Sep 17 00:00:00 2001
+From: Alexandre Janniaux <ajanni@videolabs.io>
+Date: Fri, 25 Feb 2022 15:08:04 +0100
+Subject: [PATCH 39/77] vlc_vout_display: VLC_OBJECT-wrap vout_display_New()
+
+---
+ include/vlc_vout_display.h | 2 ++
+ src/video_output/display.c | 2 +-
+ 2 files changed, 3 insertions(+), 1 deletion(-)
+
+diff --git a/include/vlc_vout_display.h b/include/vlc_vout_display.h
+index 6b11c4066c..64dd64581b 100644
+--- a/include/vlc_vout_display.h
++++ b/include/vlc_vout_display.h
+@@ -375,6 +375,8 @@ vout_display_t *vout_display_New(vlc_object_t *,
+     const video_format_t *, vlc_video_context *,
+     const vout_display_cfg_t *, const char *module,
+     const vout_display_owner_t *);
++#define vout_display_New(O, ...) \
++    (vout_display_New)(VLC_OBJECT(O), __VA_ARGS__)
+ 
+ /**
+  * Destroys a video output display.
+diff --git a/src/video_output/display.c b/src/video_output/display.c
+index 9f07f04e3a..b00f0d1877 100644
+--- a/src/video_output/display.c
++++ b/src/video_output/display.c
+@@ -615,7 +615,7 @@ void vout_SetDisplayViewpoint(vout_display_t *vd,
+     }
+ }
+ 
+-vout_display_t *vout_display_New(vlc_object_t *parent,
++vout_display_t *(vout_display_New)(vlc_object_t *parent,
+                                  const video_format_t *source,
+                                  vlc_video_context *vctx,
+                                  const vout_display_cfg_t *cfg,
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0040-hw-Add-emscripten-video-converter.patch b/vlc_patches/aug/0040-hw-Add-emscripten-video-converter.patch
new file mode 100644
index 0000000000000000000000000000000000000000..d007017c6add4cd0d1320c0f41a41cfa69906904
--- /dev/null
+++ b/vlc_patches/aug/0040-hw-Add-emscripten-video-converter.patch
@@ -0,0 +1,181 @@
+From f72d284a8d5aecef9c2d148048a004745d12a07a Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Wed, 16 Mar 2022 11:26:13 +0100
+Subject: [PATCH 40/77] hw: Add emscripten video converter
+
+---
+ modules/Makefile.am                 |   1 +
+ modules/hw/emscripten/Makefile.am   |  10 +++
+ modules/hw/emscripten/converter.cpp | 131 ++++++++++++++++++++++++++++
+ 3 files changed, 142 insertions(+)
+ create mode 100644 modules/hw/emscripten/Makefile.am
+ create mode 100644 modules/hw/emscripten/converter.cpp
+
+diff --git a/modules/Makefile.am b/modules/Makefile.am
+index 333acb0586..0c931d51ca 100644
+--- a/modules/Makefile.am
++++ b/modules/Makefile.am
+@@ -31,6 +31,7 @@ include hw/d3d11/Makefile.am
+ include hw/vaapi/Makefile.am
+ include hw/vdpau/Makefile.am
+ include hw/mmal/Makefile.am
++include hw/emscripten/Makefile.am
+ include isa/aarch64/Makefile.am
+ include isa/arm/Makefile.am
+ include isa/riscv/Makefile.am
+diff --git a/modules/hw/emscripten/Makefile.am b/modules/hw/emscripten/Makefile.am
+new file mode 100644
+index 0000000000..0402b1438b
+--- /dev/null
++++ b/modules/hw/emscripten/Makefile.am
+@@ -0,0 +1,10 @@
++emscriptendir = $(pluginsdir)/emscripten
++
++libglconverter_emscripten_plugin_la_SOURCES = hw/emscripten/converter.cpp
++libglconverter_emscripten_plugin_la_CPPFLAGS = $(AM_CPPFLAGS)
++libglconverter_emscripten_plugin_la_CXXFLAGS = $(AM_CXXFLAGS)
++libglconverter_emscripten_plugin_la_LIBADD = $(AM_LIBADD) 
++
++if HAVE_EMSCRIPTEN
++emscripten_LTLIBRARIES = libglconverter_emscripten_plugin.la
++endif
+diff --git a/modules/hw/emscripten/converter.cpp b/modules/hw/emscripten/converter.cpp
+new file mode 100644
+index 0000000000..623fc69919
+--- /dev/null
++++ b/modules/hw/emscripten/converter.cpp
+@@ -0,0 +1,131 @@
++/*****************************************************************************
++ * webcodec.cpp: VLC picture to WebCodec VideoFrame
++ *****************************************************************************
++ * Copyright (C) 2022 VLC authors, VideoLAN and VideoLabs
++ *
++ * 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 <vlc_common.h>
++#include <vlc_filter.h>
++#include <vlc_plugin.h>
++
++#include <emscripten/emscripten.h>
++#include <emscripten/em_js.h>
++#include <emscripten/val.h>
++
++struct chroma_sys_t
++{
++};
++
++static picture_t* UploadSurface(filter_t* filter, picture_t* src)
++{
++    auto size = 0u;
++    for ( auto i = 0; i < src->i_planes; ++i )
++        size += src->p[i].i_lines * src->p[i].i_pitch;
++    auto buffer = emscripten::val::global("Uint8Array").new_( size );
++    auto planesLayout = emscripten::val::array();
++    auto offset = 0u;
++    for ( auto i = 0; i < src->i_planes; ++i )
++    {
++        auto planeSize = src->p[i].i_lines * src->p[i].i_pitch;
++        auto p = emscripten::typed_memory_view( planeSize, src->p[i].p_pixels );
++        buffer.call<void>("set", p, offset);
++        auto layout = emscripten::val::object();
++        layout.set("offset", offset);
++        layout.set("stride", src->p[i].i_pitch);
++        planesLayout.call<void>( "push", layout );
++        offset += planeSize;
++    }
++
++    auto init = emscripten::val::object();
++    init.set( "format", "I420" );
++    init.set( "codedWidth", src->format.i_width );
++    init.set( "codedHeight", src->format.i_height );
++    init.set( "timestamp", (long)US_FROM_VLC_TICK( src->date ) );
++    init.set( "layout", planesLayout );
++    init.set( "displayWidth", src->format.i_visible_width);
++    init.set( "displayHeight", src->format.i_visible_height );
++
++    auto visibleRect = emscripten::val::object();
++    visibleRect.set( "x", src->format.i_x_offset );
++    visibleRect.set( "y", src->format.i_y_offset );
++    visibleRect.set( "width", src->format.i_visible_width);
++    visibleRect.set( "height", src->format.i_visible_height );
++    init.set( "visibleRect", visibleRect );
++
++    auto frame = emscripten::val::global("VideoFrame").new_( buffer, init );
++    uintptr_t pictureId = EM_ASM_INT({return Module['pictureId']});
++    EM_ASM({
++               let frame = Emval.toValue($0);
++               Module.msgChannel.port2.postMessage({
++                   customCmd: 'displayFrame',
++                   pictureId: Module.pictureId,
++                   frame: frame
++               });
++               Module.pictureId = Module.pictureId + 1;
++    }, frame.as_handle());
++    auto dst = filter_NewPicture( filter );
++    if (dst == nullptr)
++    {
++        picture_Release(src);
++        return nullptr;
++    }
++    dst->p_sys = reinterpret_cast<void*>( pictureId );
++    picture_CopyProperties( dst, src );
++    picture_Release(src);
++    return dst;
++}
++
++static void vlc_webcodec_CloseChroma(filter_t* filter)
++{
++
++}
++
++static const struct vlc_filter_operations filter_ops = {
++    .filter_video = UploadSurface,   .close = vlc_webcodec_CloseChroma,
++};
++
++static int vlc_webcodec_OpenChroma(filter_t* filter)
++{
++    if (filter->fmt_in.video.i_height != filter->fmt_out.video.i_height
++     || filter->fmt_in.video.i_width != filter->fmt_out.video.i_width
++     || filter->fmt_in.video.orientation != filter->fmt_out.video.orientation)
++    {
++        return VLC_EGENERIC;
++    }
++
++    if (filter->fmt_out.video.i_chroma != VLC_CODEC_WEBCODEC_OPAQUE)
++        return VLC_EGENERIC;
++    if (filter->fmt_in.video.i_chroma != VLC_CODEC_I420)
++        return VLC_EGENERIC;
++
++    filter->ops = &filter_ops;
++    EM_ASM({Module.pictureId = 0;});
++    return VLC_SUCCESS;
++}
++
++vlc_module_begin()
++    set_shortname(N_("WebCodec filters"))
++    set_description(N_("WebCodec filters"))
++    set_subcategory(SUBCAT_VIDEO_VFILTER)
++
++    add_submodule()
++    set_callback_video_converter(vlc_webcodec_OpenChroma, 10)
++vlc_module_end()
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0041-emscripten-Add-a-WebCodec-frame-to-software-converte.patch b/vlc_patches/aug/0041-emscripten-Add-a-WebCodec-frame-to-software-converte.patch
new file mode 100644
index 0000000000000000000000000000000000000000..16555c192bfb3794b715f2849478b99540c2b213
--- /dev/null
+++ b/vlc_patches/aug/0041-emscripten-Add-a-WebCodec-frame-to-software-converte.patch
@@ -0,0 +1,110 @@
+From fff1f64c6ce6245915c56c7696cc04362ab054e3 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Tue, 12 Apr 2022 13:55:51 +0200
+Subject: [PATCH 41/77] emscripten: Add a WebCodec frame to software converter
+
+---
+ .../opengl/interop_emscripten.cpp             | 77 +++++++++++++++++++
+ 1 file changed, 77 insertions(+)
+
+diff --git a/modules/video_output/opengl/interop_emscripten.cpp b/modules/video_output/opengl/interop_emscripten.cpp
+index 123c1fdb25..53b52fabd6 100644
+--- a/modules/video_output/opengl/interop_emscripten.cpp
++++ b/modules/video_output/opengl/interop_emscripten.cpp
+@@ -28,6 +28,8 @@
+ 
+ #include <vlc_common.h>
+ #include <vlc_plugin.h>
++#include <vlc_filter.h>
++
+ #include "interop.h"
+ #include "gl_common.h"
+ #include <cassert>
+@@ -220,9 +222,84 @@ Open(vlc_object_t *obj)
+     return VLC_SUCCESS;
+ }
+ 
++EM_ASYNC_JS(void, CopyFrameToBuffer, (int pictureId, emscripten::EM_VAL infoHandle), {
++    let info = Emval.toValue(infoHandle);
++    let frame = await Module.awaitFrame(pictureId);
++    let copyOpts = {
++        rect: frame.codedRect,
++        layout: info.layout
++    };
++    await frame.copyTo(info.buffer, copyOpts);
++});
++
++static picture_t* vlc_webcodec_UploadVideoFrame(filter_t* filter, picture_t* src)
++{
++    auto pic = filter_NewPicture(filter);
++    if (unlikely(pic == nullptr))
++    {
++        picture_Release(src);
++        return nullptr;
++    }
++    auto pictureId = reinterpret_cast<uintptr_t>(src->p_sys);
++
++    size_t picSize = 0;
++    auto info = emscripten::val::object();
++    auto planesLayout = emscripten::val::array();
++    for ( auto i = 0; i < pic->i_planes; ++i )
++    {
++        auto p = pic->p[i];
++        auto layout = emscripten::val::object();
++        layout.set( "offset", picSize );
++        layout.set( "stride", p.i_pitch );
++        planesLayout.call<void>( "push", std::move( layout ) );
++        picSize += p.i_lines * p.i_pitch;
++    }
++
++    info.set( "buffer", emscripten::typed_memory_view( picSize, pic->p[0].p_pixels ) );
++    info.set( "layout", std::move( planesLayout ) );
++
++    CopyFrameToBuffer( pictureId, info.as_handle() );
++
++    pic->date = src->date;
++    picture_Release(src);
++    return pic;
++}
++
++static void vlc_webcodec_CloseConverter(filter_t*)
++{
++    closeMessagePort();
++}
++
++static const struct vlc_filter_operations video_frame_to_sw_ops = {
++    .filter_video = vlc_webcodec_UploadVideoFrame,
++    .close = vlc_webcodec_CloseConverter,
++};
++
++static int OpenConverter(filter_t* filter)
++{
++    if (filter->fmt_in.video.i_height != filter->fmt_out.video.i_height
++     || filter->fmt_in.video.i_width != filter->fmt_out.video.i_width
++     || filter->fmt_in.video.orientation != filter->fmt_out.video.orientation
++     || filter->fmt_in.video.i_chroma != VLC_CODEC_WEBCODEC_OPAQUE
++     || filter->fmt_out.video.i_chroma != VLC_CODEC_I420 )
++    {
++        return VLC_EGENERIC;
++    }
++    initGlConvWorker(WEBCODEC_MAX_PICTURES);
++    transferMessagePort(filter->vctx_in);
++
++    filter->ops = &video_frame_to_sw_ops;
++
++    return VLC_SUCCESS;
++}
++
+ vlc_module_begin ()
+     set_description("Emscripten OpenGL SurfaceTexture converter")
+     set_capability("glinterop", 1)
+     set_callback(Open)
+     set_subcategory(SUBCAT_VIDEO_VOUT)
++
++    add_submodule()
++        set_subcategory(SUBCAT_VIDEO_VFILTER)
++        set_callback_video_converter(OpenConverter, 10)
+ vlc_module_end ()
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0042-contrib-disable-ubsan-in-gme.patch b/vlc_patches/aug/0042-contrib-disable-ubsan-in-gme.patch
new file mode 100644
index 0000000000000000000000000000000000000000..6920afe22fd16236da3f234a76d36fa11851a57e
--- /dev/null
+++ b/vlc_patches/aug/0042-contrib-disable-ubsan-in-gme.patch
@@ -0,0 +1,28 @@
+From 624edf3c2ec7deb967e1071620762bca438aa3aa Mon Sep 17 00:00:00 2001
+From: Mehdi Sabwat <mehdisabwat@gmail.com>
+Date: Wed, 13 Apr 2022 10:56:02 +0200
+Subject: [PATCH 42/77] contrib: disable ubsan in gme
+
+ubsan is enabled in the contrib recipe :
+https://bitbucket.org/mpyne/game-music-emu/src/b3d158a30492181fd7c38ef795c8d4dcfd77eaa9/CMakeLists.txt#lines-67
+
+This will require vlc to be always built with ubsan and we don't want this in production.
+---
+ contrib/src/gme/rules.mak | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/contrib/src/gme/rules.mak b/contrib/src/gme/rules.mak
+index b0acdb8ca4..411b83fb24 100644
+--- a/contrib/src/gme/rules.mak
++++ b/contrib/src/gme/rules.mak
+@@ -16,6 +16,6 @@ game-music-emu: game-music-emu-$(GME_VERSION).tar.xz .sum-gme
+ 	$(MOVE)
+ 
+ .gme: game-music-emu toolchain.cmake
+-	cd $< && $(HOSTVARS_PIC) $(CMAKE) .
++	cd $< && $(HOSTVARS_PIC) $(CMAKE) . -DENABLE_UBSAN=OFF
+ 	+$(CMAKEBUILD) $< --target install
+ 	touch $@
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0043-emscripten-Add-a-picture_pool-to-the-common-code.patch b/vlc_patches/aug/0043-emscripten-Add-a-picture_pool-to-the-common-code.patch
new file mode 100644
index 0000000000000000000000000000000000000000..8cf5aafdf1287395d32e372a50d2876d8041980b
--- /dev/null
+++ b/vlc_patches/aug/0043-emscripten-Add-a-picture_pool-to-the-common-code.patch
@@ -0,0 +1,138 @@
+From 1a01a94d5d2eeb1fcc7e206c2e420f155bd25faa Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Tue, 3 May 2022 11:10:28 +0200
+Subject: [PATCH 43/77] emscripten: Add a picture_pool to the common code
+
+---
+ modules/video_output/emscripten/common.cpp | 82 ++++++++++++++++++++++
+ modules/video_output/emscripten/common.h   | 15 +++-
+ 2 files changed, 95 insertions(+), 2 deletions(-)
+ create mode 100644 modules/video_output/emscripten/common.cpp
+
+diff --git a/modules/video_output/emscripten/common.cpp b/modules/video_output/emscripten/common.cpp
+new file mode 100644
+index 0000000000..b2065be3b4
+--- /dev/null
++++ b/modules/video_output/emscripten/common.cpp
+@@ -0,0 +1,82 @@
++/*****************************************************************************
++ * common.h: Emscripten decoder/vout common code
++ *****************************************************************************
++ * Copyright (C) 2021 VLC authors and VideoLAN
++ *
++ * 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 <vlc_common.h>
++#include <vlc_decoder.h>
++#include <vlc_picture_pool.h>
++
++#include "common.h"
++
++static picture_context_t* PictureContextCopy(picture_context_t* picCtx)
++{
++    vlc_video_context_Hold( picCtx->vctx );
++    return picCtx;
++}
++
++static void PictureContextDestroy( picture_context_t* )
++{
++    /* Context is already released by picture_Release() */
++}
++
++static picture_context_t* webcodec_CreatePictureContext( vlc_video_context* vctx, uint32_t picIdx )
++{
++    auto picCtx = static_cast<webcodec_picture_ctx*>(
++                malloc( sizeof(  webcodec_picture_ctx ) ) );
++    if ( picCtx == nullptr )
++        return nullptr;
++    picCtx->context.copy = PictureContextCopy;
++    picCtx->context.destroy = PictureContextDestroy;
++    picCtx->context.vctx = vctx;
++    picCtx->pictureIdx = picIdx;
++    return &picCtx->context;
++}
++
++
++int webcodec_CreatePool( vlc_video_context* vctx, const video_format_t* fmt )
++{
++    auto ctx = static_cast<webcodec_context*>(
++        vlc_video_context_GetPrivate( vctx, VLC_VIDEO_CONTEXT_WEBCODEC ) );
++
++    for ( auto i = 0u; i < ARRAY_SIZE(ctx->pictures); ++i )
++    {
++        auto pic = picture_NewFromFormat(fmt);
++        if ( pic != nullptr )
++            pic->context = webcodec_CreatePictureContext( vctx, i );
++        if ( pic == nullptr || pic->context == nullptr )
++        {
++            while ( i-- )
++                picture_Release( ctx->pictures[i] );
++            return VLC_EGENERIC;
++        }
++        ctx->pictures[i] = pic;
++    }
++    ctx->pool = picture_pool_New(ARRAY_SIZE(ctx->pictures), ctx->pictures);
++    if ( ctx->pool == nullptr )
++    {
++        for ( auto i = 0u; i < ARRAY_SIZE(ctx->pictures); ++i )
++            picture_Release( ctx->pictures[i] );
++        return VLC_EGENERIC;
++    }
++    return VLC_SUCCESS;
++}
+diff --git a/modules/video_output/emscripten/common.h b/modules/video_output/emscripten/common.h
+index 3a2d3c9099..08967d0703 100644
+--- a/modules/video_output/emscripten/common.h
++++ b/modules/video_output/emscripten/common.h
+@@ -27,6 +27,7 @@
+ 
+ #include <vlc_threads.h>
+ #include <vlc_picture.h>
++#include <vlc_picture_pool.h>
+ 
+ #include <emscripten/html5_webgl.h>
+ //#include <emscripten/val.h>
+@@ -36,11 +37,21 @@
+ struct webcodec_context
+ {
+     pthread_t decoder_worker;
++    picture_pool_t* pool;
++    picture_t* pictures[WEBCODEC_MAX_PICTURES];
+ };
+ 
+-struct webcodec_picture_sys_t
++struct webcodec_picture_ctx
+ {
+-    int pictureId;
++    picture_context_t context;
++    uint32_t pictureIdx;
+ };
+ 
++int webcodec_CreatePool( vlc_video_context* vctx, const video_format_t* fmt );
++
++static inline webcodec_picture_ctx* PictureContextPrivate( picture_context_t* picCtx )
++{
++    return container_of( picCtx, webcodec_picture_ctx, context );
++}
++
+ #endif // EMSCRIPTEN_COMMON_H
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0044-emscripten-webcodec-Use-picture-pool.patch b/vlc_patches/aug/0044-emscripten-webcodec-Use-picture-pool.patch
new file mode 100644
index 0000000000000000000000000000000000000000..bd7a54eae1c61cde52950d20b6f960815b17a1a7
--- /dev/null
+++ b/vlc_patches/aug/0044-emscripten-webcodec-Use-picture-pool.patch
@@ -0,0 +1,104 @@
+From ec7c16fc7ae005b33f95742e0a33bba3ab6e19c9 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Tue, 3 May 2022 11:12:13 +0200
+Subject: [PATCH 44/77] emscripten: webcodec: Use picture pool
+
+---
+ modules/codec/Makefile.am  |  2 +-
+ modules/codec/webcodec.cpp | 30 ++++++++++++++++--------------
+ 2 files changed, 17 insertions(+), 15 deletions(-)
+
+diff --git a/modules/codec/Makefile.am b/modules/codec/Makefile.am
+index 31ec138bd0..59079ebce6 100644
+--- a/modules/codec/Makefile.am
++++ b/modules/codec/Makefile.am
+@@ -645,7 +645,7 @@ libvlc_vtutils_la_SOURCES = codec/vt_utils.c codec/vt_utils.h
+ libvlc_vtutils_la_LDFLAGS = -static -no-undefined
+ EXTRA_LTLIBRARIES += libvlc_vtutils.la
+ 
+-libwebcodec_plugin_la_SOURCES = codec/webcodec.cpp
++libwebcodec_plugin_la_SOURCES = codec/webcodec.cpp video_output/emscripten/common.cpp
+ libwebcodec_plugin_la_LDFLAGS = $(AM_LDFLAGS)
+ if HAVE_EMSCRIPTEN
+ codec_LTLIBRARIES += libwebcodec_plugin.la
+diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp
+index a75d3f7897..cd902851b0 100644
+--- a/modules/codec/webcodec.cpp
++++ b/modules/codec/webcodec.cpp
+@@ -59,24 +59,23 @@ struct decoder_sys_t
+ extern "C"
+ {
+ 
+-EMSCRIPTEN_KEEPALIVE picture_t* createAndQueuePicture(decoder_t* dec, int pictureId,
+-                                                      int64_t timestamp)
++EMSCRIPTEN_KEEPALIVE int32_t createAndQueuePicture(decoder_t* dec, int64_t timestamp)
+ {
+     auto sys = static_cast<decoder_sys_t*>( dec->p_sys );
+     if ( decoder_UpdateVideoOutput( dec, sys->vctx ) )
+     {
+         msg_Err( dec, "Failure during UpdateVideoOutput! FIXME" );
+-        return NULL;
++        return -1;
+     }
+-
+-    auto pic = decoder_NewPicture(dec);
++    auto vctx = static_cast<webcodec_context*>(
++                vlc_video_context_GetPrivate(sys->vctx, VLC_VIDEO_CONTEXT_WEBCODEC) );
++    auto pic = picture_pool_Wait(vctx->pool);
+     if (pic == nullptr)
+-        return nullptr;
++        return -1;
+     pic->date = VLC_TICK_FROM_US(timestamp);
+     pic->b_progressive = true;
+-    pic->p_sys = reinterpret_cast<void*>( static_cast<uintptr_t>( pictureId ) );
+     decoder_QueueVideo(dec, pic);
+-    return pic;
++    return PictureContextPrivate(pic->context)->pictureIdx;
+ }
+ 
+ }
+@@ -102,18 +101,15 @@ EM_ASYNC_JS(void, declareCallbacks, (), {
+         await p;
+     }
+ 
+-    Module.pictureId = 0;
+     globalThis.Module.boundOutputCb = async function(frame) {
+-        Module.pictureId = (Module.pictureId + 1);
+-        _createAndQueuePicture(globalThis.Module.webCodecCtx, Module.pictureId,
+-                    frame.timestamp);
++        let picIdx = _createAndQueuePicture(globalThis.Module.webCodecCtx, frame.timestamp);
+         if (Module.voutPort === undefined)
+             await getVoutMessagePort();
+-        console.log('Posting frame', Module.pictureId);
++        console.log('Posting frame', picIdx);
+         Module.voutPort.postMessage({
+             customCmd: 'displayFrame',
+             frame: frame,
+-            pictureId: Module.pictureId,
++            pictureId: picIdx,
+           }, [frame]);
+     };
+     globalThis.Module.boundErrorCb = function(err) {
+@@ -346,6 +342,9 @@ static int Open( vlc_object_t* obj )
+             vlc_video_context_GetPrivate(sys->vctx, VLC_VIDEO_CONTEXT_WEBCODEC));
+     new (vctxPrivate) webcodec_context();
+ 
++    if ( webcodec_CreatePool(sys->vctx, &dec->fmt_out.video) != VLC_SUCCESS )
++        return VLC_EGENERIC;
++
+     if ( vlc_clone( &sys->th, &WebcodecDecodeWorker, dec, 0 ) != VLC_SUCCESS )
+     {
+         msg_Err( obj, "Failed to create webcodec thread" );
+@@ -363,6 +362,9 @@ static void Close( decoder_t* dec )
+ {
+     auto sys = static_cast<decoder_sys_t*>( dec->p_sys );
+     sys->decoder.call<void>("close");
++    auto vctx = static_cast<webcodec_context*>(
++                vlc_video_context_GetPrivate(sys->vctx, VLC_VIDEO_CONTEXT_WEBCODEC ) );
++    picture_pool_Release(vctx->pool);
+     delete sys;
+ }
+ 
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0045-emscripten-converter-Use-picture-pool-untested.patch b/vlc_patches/aug/0045-emscripten-converter-Use-picture-pool-untested.patch
new file mode 100644
index 0000000000000000000000000000000000000000..7f591edf3f33d4d9d48de17fe0246818e348721d
--- /dev/null
+++ b/vlc_patches/aug/0045-emscripten-converter-Use-picture-pool-untested.patch
@@ -0,0 +1,109 @@
+From e3696d52278c6fa8bdf780a2ec927374982b8300 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Tue, 3 May 2022 11:12:29 +0200
+Subject: [PATCH 45/77] emscripten: converter: Use picture pool (untested)
+
+---
+ modules/hw/emscripten/Makefile.am   |  2 +-
+ modules/hw/emscripten/converter.cpp | 44 ++++++++++++++++-------------
+ 2 files changed, 26 insertions(+), 20 deletions(-)
+
+diff --git a/modules/hw/emscripten/Makefile.am b/modules/hw/emscripten/Makefile.am
+index 0402b1438b..3f2a118bc1 100644
+--- a/modules/hw/emscripten/Makefile.am
++++ b/modules/hw/emscripten/Makefile.am
+@@ -1,6 +1,6 @@
+ emscriptendir = $(pluginsdir)/emscripten
+ 
+-libglconverter_emscripten_plugin_la_SOURCES = hw/emscripten/converter.cpp
++libglconverter_emscripten_plugin_la_SOURCES = hw/emscripten/converter.cpp video_output/emscripten/common.cpp
+ libglconverter_emscripten_plugin_la_CPPFLAGS = $(AM_CPPFLAGS)
+ libglconverter_emscripten_plugin_la_CXXFLAGS = $(AM_CXXFLAGS)
+ libglconverter_emscripten_plugin_la_LIBADD = $(AM_LIBADD) 
+diff --git a/modules/hw/emscripten/converter.cpp b/modules/hw/emscripten/converter.cpp
+index 623fc69919..edc8d93bc0 100644
+--- a/modules/hw/emscripten/converter.cpp
++++ b/modules/hw/emscripten/converter.cpp
+@@ -26,6 +26,8 @@
+ #include <vlc_filter.h>
+ #include <vlc_plugin.h>
+ 
++#include "../../video_output/emscripten/common.h"
++
+ #include <emscripten/emscripten.h>
+ #include <emscripten/em_js.h>
+ #include <emscripten/val.h>
+@@ -36,6 +38,15 @@ struct chroma_sys_t
+ 
+ static picture_t* UploadSurface(filter_t* filter, picture_t* src)
+ {
++    auto vctx = static_cast<webcodec_context*>(
++            vlc_video_context_GetPrivate( filter->vctx_out, VLC_VIDEO_CONTEXT_WEBCODEC) );
++    auto dst = picture_pool_Wait( vctx->pool );
++    if (dst == nullptr)
++    {
++        picture_Release(src);
++        return nullptr;
++    }
++
+     auto size = 0u;
+     for ( auto i = 0; i < src->i_planes; ++i )
+         size += src->p[i].i_lines * src->p[i].i_pitch;
+@@ -71,23 +82,17 @@ static picture_t* UploadSurface(filter_t* filter, picture_t* src)
+     init.set( "visibleRect", visibleRect );
+ 
+     auto frame = emscripten::val::global("VideoFrame").new_( buffer, init );
+-    uintptr_t pictureId = EM_ASM_INT({return Module['pictureId']});
++    auto pictureIdx = PictureContextPrivate(dst->context)->pictureIdx;
+     EM_ASM({
+-               let frame = Emval.toValue($0);
+-               Module.msgChannel.port2.postMessage({
+-                   customCmd: 'displayFrame',
+-                   pictureId: Module.pictureId,
+-                   frame: frame
+-               });
+-               Module.pictureId = Module.pictureId + 1;
+-    }, frame.as_handle());
+-    auto dst = filter_NewPicture( filter );
+-    if (dst == nullptr)
+-    {
+-        picture_Release(src);
+-        return nullptr;
+-    }
+-    dst->p_sys = reinterpret_cast<void*>( pictureId );
++        let frame = Emval.toValue($0);
++        Module.msgChannel.port2.postMessage({
++            customCmd: 'displayFrame',
++            pictureId: $1,
++            frame: frame
++        });
++    }, frame.as_handle(), pictureIdx);
++
++
+     picture_CopyProperties( dst, src );
+     picture_Release(src);
+     return dst;
+@@ -95,7 +100,9 @@ static picture_t* UploadSurface(filter_t* filter, picture_t* src)
+ 
+ static void vlc_webcodec_CloseChroma(filter_t* filter)
+ {
+-
++    auto vctx = static_cast<webcodec_context*>(
++                vlc_video_context_GetPrivate(filter->vctx_out, VLC_VIDEO_CONTEXT_WEBCODEC ) );
++    picture_pool_Release(vctx->pool);
+ }
+ 
+ static const struct vlc_filter_operations filter_ops = {
+@@ -117,8 +124,7 @@ static int vlc_webcodec_OpenChroma(filter_t* filter)
+         return VLC_EGENERIC;
+ 
+     filter->ops = &filter_ops;
+-    EM_ASM({Module.pictureId = 0;});
+-    return VLC_SUCCESS;
++    return webcodec_CreatePool(filter->vctx_out, &filter->fmt_out.video);
+ }
+ 
+ vlc_module_begin()
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0046-emscripten-interop-Use-picture-context.patch b/vlc_patches/aug/0046-emscripten-interop-Use-picture-context.patch
new file mode 100644
index 0000000000000000000000000000000000000000..1bc7323a404146e3946d915e7bb031b066f1d37f
--- /dev/null
+++ b/vlc_patches/aug/0046-emscripten-interop-Use-picture-context.patch
@@ -0,0 +1,109 @@
+From a56c006fa9ff4108421ecce21684a63a2d723088 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Tue, 3 May 2022 11:12:45 +0200
+Subject: [PATCH 46/77] emscripten: interop: Use picture context
+
+Instead of a the Module.pictureId global
+---
+ .../opengl/interop_emscripten.cpp             | 37 +++++++++----------
+ 1 file changed, 18 insertions(+), 19 deletions(-)
+
+diff --git a/modules/video_output/opengl/interop_emscripten.cpp b/modules/video_output/opengl/interop_emscripten.cpp
+index 53b52fabd6..b9a9a5087d 100644
+--- a/modules/video_output/opengl/interop_emscripten.cpp
++++ b/modules/video_output/opengl/interop_emscripten.cpp
+@@ -58,9 +58,10 @@ tc_emscripten_op_allocate_textures(const struct vlc_gl_interop *interop, GLuint
+     return VLC_SUCCESS;
+ }
+ 
+-EM_ASYNC_JS(void, bindVideoFrame, (int pictureId), {
+-    let frame = await Module.awaitFrame(pictureId);
++EM_ASYNC_JS(void, bindVideoFrame, (int pictureIdx), {
++    let frame = await Module.awaitFrame(pictureIdx);
+ 
++    console.log('Binding texture for picture at index ' + pictureIdx);
+     let glCtx = Module.glCtx;
+     glCtx.texImage2D(glCtx.TEXTURE_2D, 0, glCtx.RGBA, frame.codedWidth, frame.codedHeight, 0,
+                 glCtx.RGBA, glCtx.UNSIGNED_BYTE, frame);
+@@ -75,8 +76,8 @@ tc_emscripten_op_update(const struct vlc_gl_interop *interop, GLuint *textures,
+ 
+     sys->gl.BindTexture(interop->tex_target, textures[0]);
+ 
+-    auto pictureId = reinterpret_cast<uintptr_t>( pic->p_sys );
+-    bindVideoFrame(pictureId);
++    auto picCtx = PictureContextPrivate(pic->context);
++    bindVideoFrame(picCtx->pictureIdx);
+ 
+     return VLC_SUCCESS;
+ }
+@@ -97,17 +98,16 @@ EM_JS(void, transferMessagePort, (vlc_video_context* vctx), {
+     function onDecoderMessage(msg) {
+         let data = msg['data'];
+         if (data.customCmd == 'displayFrame') {
+-            let pictureId = data.pictureId;
+-            let pictureIdx = pictureId % 32;
++            let pictureIdx = data.pictureId;
+             let frame = data['frame'];
+-            console.log('SINK Received frame ' + pictureId );
++            console.log('SINK Received frame with index ' + pictureIdx );
+             if ( Module.glConv.promiseResolvers[pictureIdx] ) {
+                 /* The interop is already waiting for this frame, resolve the promise */
+-                console.log('SINK Renderer is already waiting for frame ' + pictureId );
++                console.log('SINK Renderer is already waiting for frame ' + pictureIdx );
+                 Module.glConv.promiseResolvers[pictureIdx]( frame );
+             } else {
+                 /* The frame hasn't arrived to the interop yet, queue it */
+-                console.log('SINK No resolver, queuing frame ' + pictureId );
++                console.log('SINK No resolver, queuing frame ' + pictureIdx );
+                 Module.glConv.frameQueue[pictureIdx] = frame;
+             }
+         }
+@@ -129,36 +129,35 @@ EM_JS(void, initGlConvWorker, (int maxPictures), {
+     Module.glConv.promiseResolvers = [];
+     Module.glConv.frameQueue = [];
+     Module.glConv.lastFrame = {
+-        pictureId: -1,
++        pictureIdx: -1,
+         frame: undefined
+     };
+ 
+-    Module.awaitFrame = async function(pictureId) {
+-        console.log('CONSUME Fetching frame ' + pictureId);
+-        if (Module.glConv.lastFrame.pictureId == pictureId) {
++    Module.awaitFrame = async function(pictureIndex) {
++        console.log('CONSUME Fetching frame ' + pictureIndex);
++        if (Module.glConv.lastFrame.pictureIdx == pictureIndex) {
+             console.log('Reusing last frame');
+             return Module.glConv.lastFrame.frame;
+         }
+-        let pictureIndex = pictureId % 32;
+         let p = new Promise((resolve, reject) => {
+             if ( Module.glConv.frameQueue[pictureIndex] ) {
+-                console.log('CONSUME Frame ', pictureId, '(index ', pictureIndex,
+-                            ') was already queued');
++                console.log('CONSUME Frame at index ', pictureIndex,
++                            ' was already queued');
+                 let frame = Module.glConv.frameQueue[pictureIndex];
+                 resolve(frame);
+                 Module.glConv.frameQueue[pictureIndex] = undefined;
+             } else {
+-                console.log('CONSUME Frame ', pictureId, '(index ', pictureIndex,
++                console.log('CONSUME Frame at index ', pictureIndex,
+                             ') not available yet; exposing resolver' );
+                 Module.glConv.promiseResolvers[pictureIndex] = resolve;
+             }
+         });
+         let frame = await p;
+-        Module.glConv.promiseResolvers[pictureId] = undefined;
++        Module.glConv.promiseResolvers[pictureIndex] = undefined;
+         if (Module.glConv.lastFrame.frame)
+             Module.glConv.lastFrame.frame.close();
+         Module.glConv.lastFrame.frame = frame;
+-        Module.glConv.lastFrame.pictureId = pictureId;
++        Module.glConv.lastFrame.pictureIdx = pictureIndex;
+         console.log('CONSUME got frame');
+         return frame;
+     }
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0047-emscripten-vout-Fix-memory-leak.patch b/vlc_patches/aug/0047-emscripten-vout-Fix-memory-leak.patch
new file mode 100644
index 0000000000000000000000000000000000000000..3c12904e94bf22f6e136d74f513405f3f464dde4
--- /dev/null
+++ b/vlc_patches/aug/0047-emscripten-vout-Fix-memory-leak.patch
@@ -0,0 +1,27 @@
+From ef16d725f44538a175ae1fff23b268375f98dc72 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Fri, 6 May 2022 08:22:17 +0200
+Subject: [PATCH 47/77] emscripten: vout: Fix memory leak
+
+---
+ modules/video_output/emscripten.cpp | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+diff --git a/modules/video_output/emscripten.cpp b/modules/video_output/emscripten.cpp
+index 0c863132f6..9fd34350b5 100644
+--- a/modules/video_output/emscripten.cpp
++++ b/modules/video_output/emscripten.cpp
+@@ -183,7 +183,9 @@ static int Open (vlc_gl_t *gl, unsigned width, unsigned height)
+         function onVoutMessage(msg) {
+             let data = msg['data'];
+             if (data.customCmd == 'commitFrame') {
+-                Module.canvasCtx.drawImage(data['bitmap'], 0, 0);
++                let bmp = data['bitmap'];
++                Module.canvasCtx.drawImage(bmp, 0, 0);
++                bmp.close();
+             }
+         }
+         let w = Module.PThread.pthreads[$0].worker;
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0048-emscripten-interop-Query-the-frame-to-the-decoder-fr.patch b/vlc_patches/aug/0048-emscripten-interop-Query-the-frame-to-the-decoder-fr.patch
new file mode 100644
index 0000000000000000000000000000000000000000..fbd95ceb03b4fb7c83dbb0013c87c9e587d3130a
--- /dev/null
+++ b/vlc_patches/aug/0048-emscripten-interop-Query-the-frame-to-the-decoder-fr.patch
@@ -0,0 +1,157 @@
+From 7e0e3b2c1a0acbd4cebae963a465cd22053f9cde Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Mon, 9 May 2022 10:15:45 +0200
+Subject: [PATCH 48/77] emscripten: interop: Query the frame to the decoder
+ from the interop
+
+Instead of maintaining a 2nd queue in parallel with the one from the
+vout
+---
+ .../opengl/interop_emscripten.cpp             | 63 ++++++++-----------
+ 1 file changed, 25 insertions(+), 38 deletions(-)
+
+diff --git a/modules/video_output/opengl/interop_emscripten.cpp b/modules/video_output/opengl/interop_emscripten.cpp
+index b9a9a5087d..65d4c84f94 100644
+--- a/modules/video_output/opengl/interop_emscripten.cpp
++++ b/modules/video_output/opengl/interop_emscripten.cpp
+@@ -61,7 +61,6 @@ tc_emscripten_op_allocate_textures(const struct vlc_gl_interop *interop, GLuint
+ EM_ASYNC_JS(void, bindVideoFrame, (int pictureIdx), {
+     let frame = await Module.awaitFrame(pictureIdx);
+ 
+-    console.log('Binding texture for picture at index ' + pictureIdx);
+     let glCtx = Module.glCtx;
+     glCtx.texImage2D(glCtx.TEXTURE_2D, 0, glCtx.RGBA, frame.codedWidth, frame.codedHeight, 0,
+                 glCtx.RGBA, glCtx.UNSIGNED_BYTE, frame);
+@@ -94,22 +93,13 @@ EMSCRIPTEN_KEEPALIVE int getDecoderWorker(vlc_video_context* vctx)
+ }
+ }
+ 
+-EM_JS(void, transferMessagePort, (vlc_video_context* vctx), {
++EM_JS(void, setupMessagePort, (vlc_video_context* vctx), {
+     function onDecoderMessage(msg) {
+         let data = msg['data'];
+         if (data.customCmd == 'displayFrame') {
+             let pictureIdx = data.pictureId;
+             let frame = data['frame'];
+-            console.log('SINK Received frame with index ' + pictureIdx );
+-            if ( Module.glConv.promiseResolvers[pictureIdx] ) {
+-                /* The interop is already waiting for this frame, resolve the promise */
+-                console.log('SINK Renderer is already waiting for frame ' + pictureIdx );
+-                Module.glConv.promiseResolvers[pictureIdx]( frame );
+-            } else {
+-                /* The frame hasn't arrived to the interop yet, queue it */
+-                console.log('SINK No resolver, queuing frame ' + pictureIdx );
+-                Module.glConv.frameQueue[pictureIdx] = frame;
+-            }
++            Module.glConv.frameResolver( frame );
+         }
+     };
+     Module.msgChannel = new MessageChannel();
+@@ -126,39 +116,35 @@ EM_JS(void, transferMessagePort, (vlc_video_context* vctx), {
+ 
+ EM_JS(void, initGlConvWorker, (int maxPictures), {
+     Module.glConv = {};
+-    Module.glConv.promiseResolvers = [];
+-    Module.glConv.frameQueue = [];
+     Module.glConv.lastFrame = {
+         pictureIdx: -1,
+         frame: undefined
+     };
+ 
+-    Module.awaitFrame = async function(pictureIndex) {
+-        console.log('CONSUME Fetching frame ' + pictureIndex);
+-        if (Module.glConv.lastFrame.pictureIdx == pictureIndex) {
+-            console.log('Reusing last frame');
++    Module.awaitFrame = async function(pictureIdx) {
++        if (Module.glConv.lastFrame.pictureIdx == pictureIdx) {
+             return Module.glConv.lastFrame.frame;
+         }
+         let p = new Promise((resolve, reject) => {
+-            if ( Module.glConv.frameQueue[pictureIndex] ) {
+-                console.log('CONSUME Frame at index ', pictureIndex,
+-                            ' was already queued');
+-                let frame = Module.glConv.frameQueue[pictureIndex];
+-                resolve(frame);
+-                Module.glConv.frameQueue[pictureIndex] = undefined;
+-            } else {
+-                console.log('CONSUME Frame at index ', pictureIndex,
+-                            ') not available yet; exposing resolver' );
+-                Module.glConv.promiseResolvers[pictureIndex] = resolve;
+-            }
++                  if (Module.glConv.frameResolver)
++                        {
++                            alert('Duplicated interop promise');
++                        }
++            Module.glConv.frameResolver = resolve;
++            Module.msgChannel.port1.postMessage({
++                'customCmd': 'sendFrame',
++                'pictureIdx': pictureIdx,
++            });
++
+         });
++        console.log('Waiting for frame ', pictureIdx, ' in interop');
+         let frame = await p;
+-        Module.glConv.promiseResolvers[pictureIndex] = undefined;
++        Module.glConv.frameResolver = undefined;
++        console.log('Received frame ', pictureIdx, ' in interop');
+         if (Module.glConv.lastFrame.frame)
+             Module.glConv.lastFrame.frame.close();
+         Module.glConv.lastFrame.frame = frame;
+-        Module.glConv.lastFrame.pictureIdx = pictureIndex;
+-        console.log('CONSUME got frame');
++        Module.glConv.lastFrame.pictureIdx = pictureIdx;
+         return frame;
+     }
+ })
+@@ -198,7 +184,7 @@ Open(vlc_object_t *obj)
+     };
+     interop->ops = &ops;
+     initGlConvWorker(WEBCODEC_MAX_PICTURES);
+-    transferMessagePort(interop->vctx);
++    setupMessagePort(interop->vctx);
+ 
+     interop->tex_target = GL_TEXTURE_2D;
+     interop->fmt_out.i_chroma = VLC_CODEC_RGBA;
+@@ -221,9 +207,9 @@ Open(vlc_object_t *obj)
+     return VLC_SUCCESS;
+ }
+ 
+-EM_ASYNC_JS(void, CopyFrameToBuffer, (int pictureId, emscripten::EM_VAL infoHandle), {
++EM_ASYNC_JS(void, CopyFrameToBuffer, (int pictureIdx, emscripten::EM_VAL infoHandle), {
+     let info = Emval.toValue(infoHandle);
+-    let frame = await Module.awaitFrame(pictureId);
++    let frame = await Module.awaitFrame(pictureIdx);
+     let copyOpts = {
+         rect: frame.codedRect,
+         layout: info.layout
+@@ -239,7 +225,8 @@ static picture_t* vlc_webcodec_UploadVideoFrame(filter_t* filter, picture_t* src
+         picture_Release(src);
+         return nullptr;
+     }
+-    auto pictureId = reinterpret_cast<uintptr_t>(src->p_sys);
++
++    auto pictureIdx = PictureContextPrivate(src->context)->pictureIdx;
+ 
+     size_t picSize = 0;
+     auto info = emscripten::val::object();
+@@ -257,7 +244,7 @@ static picture_t* vlc_webcodec_UploadVideoFrame(filter_t* filter, picture_t* src
+     info.set( "buffer", emscripten::typed_memory_view( picSize, pic->p[0].p_pixels ) );
+     info.set( "layout", std::move( planesLayout ) );
+ 
+-    CopyFrameToBuffer( pictureId, info.as_handle() );
++    CopyFrameToBuffer( pictureIdx, info.as_handle() );
+ 
+     pic->date = src->date;
+     picture_Release(src);
+@@ -285,7 +272,7 @@ static int OpenConverter(filter_t* filter)
+         return VLC_EGENERIC;
+     }
+     initGlConvWorker(WEBCODEC_MAX_PICTURES);
+-    transferMessagePort(filter->vctx_in);
++    setupMessagePort(filter->vctx_in);
+ 
+     filter->ops = &video_frame_to_sw_ops;
+ 
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0049-emscripten-webcodec-Simplify-initialization-and-adap.patch b/vlc_patches/aug/0049-emscripten-webcodec-Simplify-initialization-and-adap.patch
new file mode 100644
index 0000000000000000000000000000000000000000..cab7bcf0c8f8dc2b3f4e73c9658ef170e82da1ab
--- /dev/null
+++ b/vlc_patches/aug/0049-emscripten-webcodec-Simplify-initialization-and-adap.patch
@@ -0,0 +1,253 @@
+From 996deba93b85bc9a9bf7cdb12a35d4628d08d897 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Mon, 9 May 2022 10:16:29 +0200
+Subject: [PATCH 49/77] emscripten: webcodec: Simplify initialization and adapt
+ to new queue
+
+---
+ modules/codec/webcodec.cpp | 183 +++++++++++++++++++++++--------------
+ 1 file changed, 112 insertions(+), 71 deletions(-)
+
+diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp
+index cd902851b0..92b23d2b94 100644
+--- a/modules/codec/webcodec.cpp
++++ b/modules/codec/webcodec.cpp
+@@ -58,20 +58,28 @@ struct decoder_sys_t
+ 
+ extern "C"
+ {
++EMSCRIPTEN_KEEPALIVE picture_t* tryGetPictureFromPool(decoder_t* dec)
++{
++    auto sys = static_cast<decoder_sys_t*>( dec->p_sys );
++    auto vctx = static_cast<webcodec_context*>(
++                vlc_video_context_GetPrivate(sys->vctx, VLC_VIDEO_CONTEXT_WEBCODEC) );
++    return picture_pool_Get(vctx->pool);
++}
+ 
+-EMSCRIPTEN_KEEPALIVE int32_t createAndQueuePicture(decoder_t* dec, int64_t timestamp)
++EMSCRIPTEN_KEEPALIVE bool updateVideoOutput(decoder_t* dec)
+ {
+     auto sys = static_cast<decoder_sys_t*>( dec->p_sys );
+     if ( decoder_UpdateVideoOutput( dec, sys->vctx ) )
+     {
+         msg_Err( dec, "Failure during UpdateVideoOutput! FIXME" );
+-        return -1;
++        return false;
+     }
+-    auto vctx = static_cast<webcodec_context*>(
+-                vlc_video_context_GetPrivate(sys->vctx, VLC_VIDEO_CONTEXT_WEBCODEC) );
+-    auto pic = picture_pool_Wait(vctx->pool);
+-    if (pic == nullptr)
+-        return -1;
++    return true;
++}
++
++EMSCRIPTEN_KEEPALIVE int32_t queuePicture(void* ctx, picture_t* pic, int64_t timestamp)
++{
++    auto dec = static_cast<decoder_t*>(ctx);
+     pic->date = VLC_TICK_FROM_US(timestamp);
+     pic->b_progressive = true;
+     decoder_QueueVideo(dec, pic);
+@@ -80,44 +88,6 @@ EMSCRIPTEN_KEEPALIVE int32_t createAndQueuePicture(decoder_t* dec, int64_t times
+ 
+ }
+ 
+-EM_JS(void, initModuleContext, (void* ctx), {
+-    globalThis.Module.webCodecCtx = ctx;
+-});
+-
+-EM_ASYNC_JS(void, declareCallbacks, (), {
+-    async function getVoutMessagePort() {
+-        let p = new Promise((resolve, reject) => {
+-            self.addEventListener('message', function(e) {
+-                let msg = e['data'];
+-                if (msg.customCmd == 'transferMessagePort') {
+-                    let port = msg['transferList'][0];
+-                    if (!port)
+-                        reject();
+-                    Module.voutPort = port;
+-                    resolve();
+-                }
+-            });
+-        });
+-        await p;
+-    }
+-
+-    globalThis.Module.boundOutputCb = async function(frame) {
+-        let picIdx = _createAndQueuePicture(globalThis.Module.webCodecCtx, frame.timestamp);
+-        if (Module.voutPort === undefined)
+-            await getVoutMessagePort();
+-        console.log('Posting frame', picIdx);
+-        Module.voutPort.postMessage({
+-            customCmd: 'displayFrame',
+-            frame: frame,
+-            pictureId: picIdx,
+-          }, [frame]);
+-    };
+-    globalThis.Module.boundErrorCb = function(err) {
+-        console.log('Error while decoding: ');
+-        console.log(err);
+-    };
+-});
+-
+ EM_ASYNC_JS(bool, probeConfig, (emscripten::EM_VAL cfg), {
+     var decoderCfg = Emval.toValue(cfg);
+     var res = await VideoDecoder.isConfigSupported(decoderCfg).catch((err) => {
+@@ -183,7 +153,6 @@ static emval getDecoderConfig( decoder_t* dec, bool includeExtraData )
+     decoderConfig.set( "optimizeForLatency", true );
+     if ( includeExtraData )
+     {
+-        msg_Err( dec, "i_extra: %u", dec->fmt_in.i_extra );
+         if ( dec->fmt_in.i_extra > 0 )
+         {
+             decoderConfig.set( "description",
+@@ -196,32 +165,100 @@ static emval getDecoderConfig( decoder_t* dec, bool includeExtraData )
+     return decoderConfig;
+ }
+ 
+-static bool initDecoder( decoder_t* dec )
+-{
+-    auto sys = static_cast<decoder_sys_t*>( dec->p_sys );
+-    initModuleContext(dec);
+-    declareCallbacks();
+-
+-    auto initCfg = emval::object();
++EM_JS(emscripten::EM_VAL, initDecoderJS, (void* decoder), {
++    function onInteropMessage(msg) {
++        let data = msg['data'];
++        if (data['customCmd'] == 'sendFrame') {
++            let picIdx = data['pictureIdx'];
++            let frame = Module.framesReady[picIdx];
++            Module.framesReady[picIdx] = undefined;
++            console.log('Posting frame', picIdx, 'from webcodec', frame);
++            Module.voutPort.postMessage({
++                customCmd: 'displayFrame',
++                frame: frame,
++                pictureId: picIdx,
++              }, [frame]);
++        }
++    }
+ 
+-    auto outputCb = emval::module_property("boundOutputCb");
+-    if ( outputCb.isUndefined() )
+-    {
+-        msg_Err( dec, "Failed to find output callback" );
+-        return false;
++    async function getVoutMessagePort() {
++        let p = new Promise((resolve, reject) => {
++            self.addEventListener('message', function(e) {
++                let msg = e['data'];
++                if (msg.customCmd == 'transferMessagePort') {
++                    let port = msg['transferList'][0];
++                    if (!port)
++                        reject();
++                    Module.voutPort = port;
++                    Module.voutPort.onmessage = onInteropMessage;
++                    resolve();
++                }
++            });
++        });
++        await p;
+     }
+-    initCfg.set("output", outputCb);
+ 
+-    auto errorCb = emval::module_property("boundErrorCb");
+-    if ( errorCb.isUndefined() )
+-    {
+-        msg_Err( dec, "Failed to find error callback" );
+-        return false;
++    async function getPictureAsync(dec) {
++        function getPicture(dec, resolve) {
++            let pic = _tryGetPictureFromPool(dec);
++            if (!pic) {
++                setTimeout( function(d, r) {
++                      getPicture(d, r);
++                  }, 1, dec, resolve );
++                return;
++            }
++            resolve(pic);
++        }
++        let p = new Promise((resolve) => {
++            getPicture(dec, resolve);
++        });
++        return await p;
+     }
+-    initCfg.set("error", errorCb);
+ 
+-    auto decoderType = emval::global("VideoDecoder");
+-    sys->decoder = decoderType.new_(initCfg);
++    /* Store a handle to our decoder_t for later invocations */
++    Module.webCodecCtx = decoder;
++
++    /* Prepare an array containing the decoded frames, so the interop can query
++     * then later */
++    Module.framesReady = [];
++
++    let initCfg = {
++        'output': async function (frame) {
++            /* Always update the video output format so we can ensure the vout
++             * is created when we try to acquire its message port */
++            if ( !_updateVideoOutput(Module.webCodecCtx) )
++            {
++                frame.close();
++                return;
++            }
++            let p = await getPictureAsync(Module.webCodecCtx);
++            let picIdx = _queuePicture(Module.webCodecCtx, p, frame.timestamp);
++            if (Module.voutPort === undefined)
++                await getVoutMessagePort();
++            if ( Module.framesReady[picIdx] ) {
++                /*
++                 *  If we end up overriding a frame, it means it was dropped
++                 *  by the vout before the interop asked for it, so we can close
++                 *  it safely.
++                 */
++                console.log('Discarding dropped frame ', picIdx);
++                Module.framesReady[picIdx].close();
++            }
++            Module.framesReady[picIdx] = frame;
++        },
++        'error': function(err) {
++            console.log('Error while decoding: ');
++            console.log(err);
++        }
++    };
++    return Emval.toHandle( new VideoDecoder( initCfg ) );
++});
++
++static bool initDecoder( decoder_t* dec )
++{
++    auto sys = static_cast<decoder_sys_t*>( dec->p_sys );
++
++    sys->decoder = emval::take_ownership( initDecoderJS( dec ) );
+     if ( sys->decoder.isUndefined() )
+     {
+         msg_Err( dec, "Failed to instantiate VideoDecoder" );
+@@ -247,12 +284,12 @@ static void WebcodecDecodeWorkerTick( void* arg )
+ 
+         auto chunk = blockToEncodedVideoChunk( dec, block );
+         block_Release(block);
+-        msg_Err(dec, "Decoding a sample...");
+         sys->decoder.call<void>( "decode", chunk );
+ 
+-        auto queueSize = sys->decoder["decodeQueueSize"];
+-        auto state = sys->decoder["state"];
+-        msg_Err( dec, "Decoder state: %s ; queue size: %ld", state.as<std::string>().c_str(), queueSize.as<long int>());
++//        auto queueSize = sys->decoder["decodeQueueSize"];
++//        auto state = sys->decoder["state"];
++//        msg_Err( dec, "Decoder state: %s ; queue size: %ld",
++//          state.as<std::string>().c_str(), queueSize.as<long int>());
+     }
+ }
+ 
+@@ -392,3 +429,7 @@ vlc_module_begin ()
+     add_submodule()
+         set_callback_dec_device(OpenDecDevice, 1)
+ vlc_module_end ()
++
++EMSCRIPTEN_BINDINGS(vlc_picture) {
++    emscripten::class_<picture_t>("picture_t");
++}
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0050-webcodec-Remove-unused-binding.patch b/vlc_patches/aug/0050-webcodec-Remove-unused-binding.patch
new file mode 100644
index 0000000000000000000000000000000000000000..704543700d76b400d1ca3c16c92fa810fde1254a
--- /dev/null
+++ b/vlc_patches/aug/0050-webcodec-Remove-unused-binding.patch
@@ -0,0 +1,24 @@
+From 0cc2af768d3d7f01460b092bed2bdb37f72b8a46 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Mon, 9 May 2022 11:58:08 +0200
+Subject: [PATCH 50/77] webcodec: Remove unused binding
+
+---
+ modules/codec/webcodec.cpp | 4 ----
+ 1 file changed, 4 deletions(-)
+
+diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp
+index 92b23d2b94..d44e9ee5ae 100644
+--- a/modules/codec/webcodec.cpp
++++ b/modules/codec/webcodec.cpp
+@@ -429,7 +429,3 @@ vlc_module_begin ()
+     add_submodule()
+         set_callback_dec_device(OpenDecDevice, 1)
+ vlc_module_end ()
+-
+-EMSCRIPTEN_BINDINGS(vlc_picture) {
+-    emscripten::class_<picture_t>("picture_t");
+-}
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0051-interop-remove-debug.patch b/vlc_patches/aug/0051-interop-remove-debug.patch
new file mode 100644
index 0000000000000000000000000000000000000000..0994e3b616c0ff1f7b863ff3d61e15907e0dfe72
--- /dev/null
+++ b/vlc_patches/aug/0051-interop-remove-debug.patch
@@ -0,0 +1,38 @@
+From 0d2dfae2f2894a30d673c608813f7c7e84da3dc3 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Mon, 9 May 2022 11:58:15 +0200
+Subject: [PATCH 51/77] interop: remove debug
+
+---
+ modules/video_output/opengl/interop_emscripten.cpp | 6 ------
+ 1 file changed, 6 deletions(-)
+
+diff --git a/modules/video_output/opengl/interop_emscripten.cpp b/modules/video_output/opengl/interop_emscripten.cpp
+index 65d4c84f94..e9905ac1a3 100644
+--- a/modules/video_output/opengl/interop_emscripten.cpp
++++ b/modules/video_output/opengl/interop_emscripten.cpp
+@@ -126,10 +126,6 @@ EM_JS(void, initGlConvWorker, (int maxPictures), {
+             return Module.glConv.lastFrame.frame;
+         }
+         let p = new Promise((resolve, reject) => {
+-                  if (Module.glConv.frameResolver)
+-                        {
+-                            alert('Duplicated interop promise');
+-                        }
+             Module.glConv.frameResolver = resolve;
+             Module.msgChannel.port1.postMessage({
+                 'customCmd': 'sendFrame',
+@@ -137,10 +133,8 @@ EM_JS(void, initGlConvWorker, (int maxPictures), {
+             });
+ 
+         });
+-        console.log('Waiting for frame ', pictureIdx, ' in interop');
+         let frame = await p;
+         Module.glConv.frameResolver = undefined;
+-        console.log('Received frame ', pictureIdx, ' in interop');
+         if (Module.glConv.lastFrame.frame)
+             Module.glConv.lastFrame.frame.close();
+         Module.glConv.lastFrame.frame = frame;
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0052-webcodec-remove-debug.patch b/vlc_patches/aug/0052-webcodec-remove-debug.patch
new file mode 100644
index 0000000000000000000000000000000000000000..d384ce680eb9134cfb757bd1741f02d21d9b020f
--- /dev/null
+++ b/vlc_patches/aug/0052-webcodec-remove-debug.patch
@@ -0,0 +1,24 @@
+From c12fc5c5bc34fa684baf7a21ed16886462eb8756 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Mon, 9 May 2022 11:58:31 +0200
+Subject: [PATCH 52/77] webcodec: remove debug
+
+---
+ modules/codec/webcodec.cpp | 1 -
+ 1 file changed, 1 deletion(-)
+
+diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp
+index d44e9ee5ae..bd2a1a083d 100644
+--- a/modules/codec/webcodec.cpp
++++ b/modules/codec/webcodec.cpp
+@@ -172,7 +172,6 @@ EM_JS(emscripten::EM_VAL, initDecoderJS, (void* decoder), {
+             let picIdx = data['pictureIdx'];
+             let frame = Module.framesReady[picIdx];
+             Module.framesReady[picIdx] = undefined;
+-            console.log('Posting frame', picIdx, 'from webcodec', frame);
+             Module.voutPort.postMessage({
+                 customCmd: 'displayFrame',
+                 frame: frame,
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0053-WIP-webcodec-remove-manual-block-queue.patch b/vlc_patches/aug/0053-WIP-webcodec-remove-manual-block-queue.patch
new file mode 100644
index 0000000000000000000000000000000000000000..7caf1e444cb36151c4b329db254928b957bc2004
--- /dev/null
+++ b/vlc_patches/aug/0053-WIP-webcodec-remove-manual-block-queue.patch
@@ -0,0 +1,246 @@
+From 43c1f6c435426134decbd7dda0b86feccc2e6695 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Tue, 10 May 2022 08:16:38 +0200
+Subject: [PATCH 53/77] WIP webcodec remove manual block queue
+
+---
+ modules/codec/webcodec.cpp | 147 ++++++++++++++++++++++++-------------
+ 1 file changed, 97 insertions(+), 50 deletions(-)
+
+diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp
+index bd2a1a083d..48383575f0 100644
+--- a/modules/codec/webcodec.cpp
++++ b/modules/codec/webcodec.cpp
+@@ -47,10 +47,6 @@ using emval = emscripten::val;
+ 
+ struct decoder_sys_t
+ {
+-    emval decoder = emval::undefined();
+-    std::queue<block_t*> blocks;
+-    vlc::threads::mutex mutex;
+-    vlc::threads::condition_variable cond;
+     vlc_thread_t th;
+ 
+     vlc_video_context* vctx;
+@@ -81,11 +77,23 @@ EMSCRIPTEN_KEEPALIVE int32_t queuePicture(void* ctx, picture_t* pic, int64_t tim
+ {
+     auto dec = static_cast<decoder_t*>(ctx);
+     pic->date = VLC_TICK_FROM_US(timestamp);
++    msg_Dbg(dec, "Picture timestamp: %" PRId64 "\n", timestamp);
+     pic->b_progressive = true;
+     decoder_QueueVideo(dec, pic);
+     return PictureContextPrivate(pic->context)->pictureIdx;
+ }
+ 
++EMSCRIPTEN_KEEPALIVE pthread_t getVlcDecoderWorkerThread(decoder_t* dec)
++{
++    auto sys = static_cast<decoder_sys_t*>( dec->p_sys );
++    return sys->th.handle;
++}
++
++EMSCRIPTEN_KEEPALIVE void releaseBlock( block_t* block )
++{
++    block_Release( block );
++}
++
+ }
+ 
+ EM_ASYNC_JS(bool, probeConfig, (emscripten::EM_VAL cfg), {
+@@ -165,7 +173,7 @@ static emval getDecoderConfig( decoder_t* dec, bool includeExtraData )
+     return decoderConfig;
+ }
+ 
+-EM_JS(emscripten::EM_VAL, initDecoderJS, (void* decoder), {
++EM_JS(emscripten::EM_VAL, initDecoderJS, (void* decoder, emscripten::EM_VAL decCfgHandle), {
+     function onInteropMessage(msg) {
+         let data = msg['data'];
+         if (data['customCmd'] == 'sendFrame') {
+@@ -197,6 +205,18 @@ EM_JS(emscripten::EM_VAL, initDecoderJS, (void* decoder), {
+         await p;
+     }
+ 
++    function onDecoderWorkerMessage(msg) {
++        const data = msg['data'];
++        if (data['customCmd'] == 'decode') {
++            Module.decoder.decode( data['chunk'] );
++            _releaseBlock( data['block'] );
++        } else if ( data['customCmd'] == 'flush' ) {
++            Module.decoder.flush();
++        } else if ( data['customCmd'] == 'close' ) {
++            Module.decoder.close();
++        }
++    }
++
+     async function getPictureAsync(dec) {
+         function getPicture(dec, resolve) {
+             let pic = _tryGetPictureFromPool(dec);
+@@ -214,6 +234,25 @@ EM_JS(emscripten::EM_VAL, initDecoderJS, (void* decoder), {
+         return await p;
+     }
+ 
++    self.addEventListener('message', (e) => {
++        let msg = e['data'];
++
++        console.log('Received message in decoder worker');
++        console.dir(msg);
++
++        if (msg['customCmd'] == 'getDecoderWorkerMessagePort') {
++            Module.msgChannel = new MessageChannel();
++            Module.msgChannel.port1.onmessage = function(msg) {
++                onDecoderWorkerMessage(msg);
++            };
++            self.postMessage({
++                customCmd: 'transferMessagePort',
++                targetThread: msg['replyTo'],
++                transferList: [Module.msgChannel.port2],
++            }, [Module.msgChannel.port2]);
++        }
++    });
++
+     /* Store a handle to our decoder_t for later invocations */
+     Module.webCodecCtx = decoder;
+ 
+@@ -250,46 +289,21 @@ EM_JS(emscripten::EM_VAL, initDecoderJS, (void* decoder), {
+             console.log(err);
+         }
+     };
+-    return Emval.toHandle( new VideoDecoder( initCfg ) );
++    Module.decoder = new VideoDecoder( initCfg );
++    let decCfg = Emval.toValue( decCfgHandle );
++    Module.decoder.configure( decCfg );
+ });
+ 
+ static bool initDecoder( decoder_t* dec )
+ {
+-    auto sys = static_cast<decoder_sys_t*>( dec->p_sys );
+-
+-    sys->decoder = emval::take_ownership( initDecoderJS( dec ) );
+-    if ( sys->decoder.isUndefined() )
+-    {
+-        msg_Err( dec, "Failed to instantiate VideoDecoder" );
+-        return false;
+-    }
+-
+-    sys->decoder.call<void>( "configure", getDecoderConfig( dec, true ) );
++    auto decCfg = getDecoderConfig( dec, true );
++    initDecoderJS( dec, decCfg.as_handle() );
+ 
+     return true;
+ }
+ 
+-static void WebcodecDecodeWorkerTick( void* arg )
++static void mainloop_tick()
+ {
+-    auto dec = static_cast<decoder_t*>( arg );
+-    auto sys = static_cast<decoder_sys_t*>( dec->p_sys );
+-    block_t* block;
+-
+-    vlc::threads::mutex_locker lock{ sys->mutex };
+-    while ( sys->blocks.empty() == false )
+-    {
+-        block = sys->blocks.front();
+-        sys->blocks.pop();
+-
+-        auto chunk = blockToEncodedVideoChunk( dec, block );
+-        block_Release(block);
+-        sys->decoder.call<void>( "decode", chunk );
+-
+-//        auto queueSize = sys->decoder["decodeQueueSize"];
+-//        auto state = sys->decoder["state"];
+-//        msg_Err( dec, "Decoder state: %s ; queue size: %ld",
+-//          state.as<std::string>().c_str(), queueSize.as<long int>());
+-    }
+ }
+ 
+ static void* WebcodecDecodeWorker( void* arg )
+@@ -314,29 +328,58 @@ static void* WebcodecDecodeWorker( void* arg )
+     auto vctxPrivate = static_cast<webcodec_context*>(
+             vlc_video_context_GetPrivate(sys->vctx, VLC_VIDEO_CONTEXT_WEBCODEC));
+     vctxPrivate->decoder_worker = pthread_self();
+-    emscripten_set_main_loop_arg( &WebcodecDecodeWorkerTick, dec, 0, false );
+-    /*
+-     * We want our tick function to be invoked ASAP even if we'd only be blocking
+-     * waiting for a new block to be queued
+-     */
+-    emscripten_set_main_loop_timing( EM_TIMING_SETTIMEOUT, 1 );
++    emscripten_set_main_loop(mainloop_tick, 1, true);
+     return NULL;
+ }
+ 
++EM_ASYNC_JS(void, initDecoderWorkerMessagePort, (decoder_t* dec), {
++    if (Module.decoderWorkerPort !== undefined) {
++        return;
++    }
++    let workerMessagePortPromise = new Promise((resolve, reject) => {
++        self.addEventListener('message', function(e) {
++            let msg = e['data'];
++            if (msg.customCmd == 'transferMessagePort') {
++                let port = msg['transferList'][0];
++                if (!port) {
++                    console.log('No port provided, rejecting');
++                    reject();
++                }
++                Module.decoderWorkerPort = port;
++                resolve();
++            }
++        });
++        self.postMessage({
++            customCmd: 'getDecoderWorkerMessagePort',
++            targetThread: _getVlcDecoderWorkerThread(dec),
++            replyTo: _pthread_self()
++        });
++    });
++    await workerMessagePortPromise;
++})
++
+ static int Decode( decoder_t* dec, block_t* block )
+ {
+-    auto sys = static_cast<decoder_sys_t*>( dec->p_sys );
+-    vlc::threads::mutex_locker lock{ sys->mutex };
+-    sys->blocks.push( block );
+-    sys->cond.signal();
++    initDecoderWorkerMessagePort(dec);
++    auto chunk = blockToEncodedVideoChunk( dec, block );
++    EM_ASM({
++        Module.decoderWorkerPort.postMessage({
++            customCmd: 'decode',
++            chunk: Emval.toValue($0),
++            block: $1
++        });
++    }, chunk.as_handle(), block);
+     return VLCDEC_SUCCESS;
+ }
+ 
+ static void Flush( decoder_t* dec )
+ {
+-    //FIXME: Needs to be called from the decoder thread
+-    auto sys = static_cast<decoder_sys_t*>( dec->p_sys );
+-    sys->decoder.call<emval>("flush").await();
++    initDecoderWorkerMessagePort(dec);
++    EM_ASM({
++        Module.decoderWorkerPort.postMessage({
++            customCmd: 'flush'
++        });
++    });
+ }
+ 
+ static int Open( vlc_object_t* obj )
+@@ -397,10 +440,14 @@ static int Open( vlc_object_t* obj )
+ static void Close( decoder_t* dec )
+ {
+     auto sys = static_cast<decoder_sys_t*>( dec->p_sys );
+-    sys->decoder.call<void>("close");
+     auto vctx = static_cast<webcodec_context*>(
+                 vlc_video_context_GetPrivate(sys->vctx, VLC_VIDEO_CONTEXT_WEBCODEC ) );
+     picture_pool_Release(vctx->pool);
++    EM_ASM({
++        Module.decoderWorkerPort.postMessage({
++            customCmd: 'close'
++        });
++    });
+     delete sys;
+ }
+ 
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0054-webcodec-remove-debug.patch b/vlc_patches/aug/0054-webcodec-remove-debug.patch
new file mode 100644
index 0000000000000000000000000000000000000000..b855dbb9e0c72dc38ae2b47f726b29d721ee8b1e
--- /dev/null
+++ b/vlc_patches/aug/0054-webcodec-remove-debug.patch
@@ -0,0 +1,24 @@
+From 1c9fdc8f0e1df8ea34f6f6cae882b2559d802a88 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Tue, 10 May 2022 10:23:28 +0200
+Subject: [PATCH 54/77] webcodec: remove debug
+
+---
+ modules/codec/webcodec.cpp | 1 -
+ 1 file changed, 1 deletion(-)
+
+diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp
+index 48383575f0..983084103e 100644
+--- a/modules/codec/webcodec.cpp
++++ b/modules/codec/webcodec.cpp
+@@ -77,7 +77,6 @@ EMSCRIPTEN_KEEPALIVE int32_t queuePicture(void* ctx, picture_t* pic, int64_t tim
+ {
+     auto dec = static_cast<decoder_t*>(ctx);
+     pic->date = VLC_TICK_FROM_US(timestamp);
+-    msg_Dbg(dec, "Picture timestamp: %" PRId64 "\n", timestamp);
+     pic->b_progressive = true;
+     decoder_QueueVideo(dec, pic);
+     return PictureContextPrivate(pic->context)->pictureIdx;
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0055-webcodec-Only-transfer-a-block-pointer-to-the-decode.patch b/vlc_patches/aug/0055-webcodec-Only-transfer-a-block-pointer-to-the-decode.patch
new file mode 100644
index 0000000000000000000000000000000000000000..7b168604ea35b70a6f57f396f57d3e9069c63e8e
--- /dev/null
+++ b/vlc_patches/aug/0055-webcodec-Only-transfer-a-block-pointer-to-the-decode.patch
@@ -0,0 +1,100 @@
+From 95375e731c42928ae886c7c18f852f73916ace45 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Tue, 10 May 2022 10:25:00 +0200
+Subject: [PATCH 55/77] webcodec: Only transfer a block pointer to the decoder
+ worker
+
+It's unclear how performant it is to transfer an entire object and its
+embedded memory view
+---
+ modules/codec/webcodec.cpp | 47 +++++++++++++++++++++++---------------
+ 1 file changed, 28 insertions(+), 19 deletions(-)
+
+diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp
+index 983084103e..51f63a2089 100644
+--- a/modules/codec/webcodec.cpp
++++ b/modules/codec/webcodec.cpp
+@@ -93,18 +93,7 @@ EMSCRIPTEN_KEEPALIVE void releaseBlock( block_t* block )
+     block_Release( block );
+ }
+ 
+-}
+-
+-EM_ASYNC_JS(bool, probeConfig, (emscripten::EM_VAL cfg), {
+-    var decoderCfg = Emval.toValue(cfg);
+-    var res = await VideoDecoder.isConfigSupported(decoderCfg).catch((err) => {
+-        console.log(err);
+-        return {'supported': false};
+-    });
+-    return res['supported'];
+-});
+-
+-emval blockToEncodedVideoChunk( decoder_t* dec, block_t* block )
++EMSCRIPTEN_KEEPALIVE void decodeBlock( block_t* block )
+ {
+     auto chunkType = emval::global("EncodedVideoChunk");
+     auto chunkCfg = emval::object();
+@@ -123,9 +112,30 @@ emval blockToEncodedVideoChunk( decoder_t* dec, block_t* block )
+     //if ( block->i_length > 0 )
+     //    chunkCfg.set( "duration", (long int)US_FROM_VLC_TICK( block->i_length ) );
+     chunkCfg.set( "data", emscripten::typed_memory_view( block->i_buffer, block->p_buffer ) );
+-    return chunkType.new_( std::move( chunkCfg ) );
++    auto chunk = chunkType.new_( std::move( chunkCfg ) );
++
++    /* We can't return a javascript object from the stack back to the JS caller
++     * as the object would be destroyed when falling out of scope.
++     * We can't also pass an object by reference as the only way to get a
++     * emscripten::val instance is to take ownership from the javascript object
++     * so we couldn't use it back in the JS
++     * As a work around, we access the JS decoder instance straight from the C++ code
++     */
++    EM_ASM({Module.decoder.decode( Emval.toValue( $0 ) ); }, chunk.as_handle());
+ }
+ 
++
++}
++
++EM_ASYNC_JS(bool, probeConfig, (emscripten::EM_VAL cfg), {
++    var decoderCfg = Emval.toValue(cfg);
++    var res = await VideoDecoder.isConfigSupported(decoderCfg).catch((err) => {
++        console.log(err);
++        return {'supported': false};
++    });
++    return res['supported'];
++});
++
+ static emval getDecoderConfig( decoder_t* dec, bool includeExtraData )
+ {
+     auto decoderConfig = emval::object();
+@@ -207,8 +217,9 @@ EM_JS(emscripten::EM_VAL, initDecoderJS, (void* decoder, emscripten::EM_VAL decC
+     function onDecoderWorkerMessage(msg) {
+         const data = msg['data'];
+         if (data['customCmd'] == 'decode') {
+-            Module.decoder.decode( data['chunk'] );
+-            _releaseBlock( data['block'] );
++            let block = data['block'];
++            _decodeBlock( block );
++            _releaseBlock( block );
+         } else if ( data['customCmd'] == 'flush' ) {
+             Module.decoder.flush();
+         } else if ( data['customCmd'] == 'close' ) {
+@@ -360,14 +371,12 @@ EM_ASYNC_JS(void, initDecoderWorkerMessagePort, (decoder_t* dec), {
+ static int Decode( decoder_t* dec, block_t* block )
+ {
+     initDecoderWorkerMessagePort(dec);
+-    auto chunk = blockToEncodedVideoChunk( dec, block );
+     EM_ASM({
+         Module.decoderWorkerPort.postMessage({
+             customCmd: 'decode',
+-            chunk: Emval.toValue($0),
+-            block: $1
++            block: $0
+         });
+-    }, chunk.as_handle(), block);
++    }, block);
+     return VLCDEC_SUCCESS;
+ }
+ 
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0056-webcodec-minor-simplification.patch b/vlc_patches/aug/0056-webcodec-minor-simplification.patch
new file mode 100644
index 0000000000000000000000000000000000000000..4a571879473bb58c8671bd9a04d643e55b8849e1
--- /dev/null
+++ b/vlc_patches/aug/0056-webcodec-minor-simplification.patch
@@ -0,0 +1,27 @@
+From 19e4b631ba7faf7e11bf2c07008346e6d5d3657a Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Tue, 10 May 2022 10:31:16 +0200
+Subject: [PATCH 56/77] webcodec: minor simplification
+
+---
+ modules/codec/webcodec.cpp | 4 +---
+ 1 file changed, 1 insertion(+), 3 deletions(-)
+
+diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp
+index 51f63a2089..f82b1a7bf0 100644
+--- a/modules/codec/webcodec.cpp
++++ b/modules/codec/webcodec.cpp
+@@ -252,9 +252,7 @@ EM_JS(emscripten::EM_VAL, initDecoderJS, (void* decoder, emscripten::EM_VAL decC
+ 
+         if (msg['customCmd'] == 'getDecoderWorkerMessagePort') {
+             Module.msgChannel = new MessageChannel();
+-            Module.msgChannel.port1.onmessage = function(msg) {
+-                onDecoderWorkerMessage(msg);
+-            };
++            Module.msgChannel.port1.onmessage = onDecoderWorkerMessage;
+             self.postMessage({
+                 customCmd: 'transferMessagePort',
+                 targetThread: msg['replyTo'],
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0057-webcodec-Acquire-vout-message-port-before-queuing-pi.patch b/vlc_patches/aug/0057-webcodec-Acquire-vout-message-port-before-queuing-pi.patch
new file mode 100644
index 0000000000000000000000000000000000000000..3313a9a098201cb12b1022241e68718896ab9e12
--- /dev/null
+++ b/vlc_patches/aug/0057-webcodec-Acquire-vout-message-port-before-queuing-pi.patch
@@ -0,0 +1,30 @@
+From 452cad2efcd15a8f30e9f5ef58bb70c82e23ad00 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Tue, 10 May 2022 10:31:33 +0200
+Subject: [PATCH 57/77] webcodec: Acquire vout message port before queuing
+ pictures
+
+---
+ modules/codec/webcodec.cpp | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp
+index f82b1a7bf0..4c6250294f 100644
+--- a/modules/codec/webcodec.cpp
++++ b/modules/codec/webcodec.cpp
+@@ -277,10 +277,10 @@ EM_JS(emscripten::EM_VAL, initDecoderJS, (void* decoder, emscripten::EM_VAL decC
+                 frame.close();
+                 return;
+             }
+-            let p = await getPictureAsync(Module.webCodecCtx);
+-            let picIdx = _queuePicture(Module.webCodecCtx, p, frame.timestamp);
+             if (Module.voutPort === undefined)
+                 await getVoutMessagePort();
++            let p = await getPictureAsync(Module.webCodecCtx);
++            let picIdx = _queuePicture(Module.webCodecCtx, p, frame.timestamp);
+             if ( Module.framesReady[picIdx] ) {
+                 /*
+                  *  If we end up overriding a frame, it means it was dropped
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0058-webcodec-Fix-invalid-error-return.patch b/vlc_patches/aug/0058-webcodec-Fix-invalid-error-return.patch
new file mode 100644
index 0000000000000000000000000000000000000000..5983bee8780ba9276cb92cb7fb00707de614e0f8
--- /dev/null
+++ b/vlc_patches/aug/0058-webcodec-Fix-invalid-error-return.patch
@@ -0,0 +1,25 @@
+From 85987521d4a5995038b155277268bbd07ab4c34d Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Wed, 11 May 2022 12:03:39 +0200
+Subject: [PATCH 58/77] webcodec: Fix invalid error return
+
+---
+ modules/codec/webcodec.cpp | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp
+index 4c6250294f..b3546edaea 100644
+--- a/modules/codec/webcodec.cpp
++++ b/modules/codec/webcodec.cpp
+@@ -400,7 +400,7 @@ static int Open( vlc_object_t* obj )
+     {
+         msg_Err( obj, "Can't get VideoDecoder type, webcodec is probably not "
+                       "supported on this browser" );
+-        return false;
++        return VLC_EGENERIC;
+     }
+     auto sys = std::make_unique<decoder_sys_t>();
+     dec->p_sys = sys.get();
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0059-WIP-webcodec-Reintroduce-a-block-queue-hoping-to-sol.patch b/vlc_patches/aug/0059-WIP-webcodec-Reintroduce-a-block-queue-hoping-to-sol.patch
new file mode 100644
index 0000000000000000000000000000000000000000..27dd1830bd97daf8d0fa52400d2f86535a308548
--- /dev/null
+++ b/vlc_patches/aug/0059-WIP-webcodec-Reintroduce-a-block-queue-hoping-to-sol.patch
@@ -0,0 +1,117 @@
+From 55339e6437de6ccc6813b0551ac737d6f7a43d3d Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Wed, 11 May 2022 13:17:03 +0200
+Subject: [PATCH 59/77] WIP: webcodec: Reintroduce a block queue hoping to
+ solve the late frames
+
+which doesn't work for now
+---
+ modules/codec/webcodec.cpp | 44 +++++++++++++++++++++++++++++++-------
+ 1 file changed, 36 insertions(+), 8 deletions(-)
+
+diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp
+index b3546edaea..1967231999 100644
+--- a/modules/codec/webcodec.cpp
++++ b/modules/codec/webcodec.cpp
+@@ -48,6 +48,8 @@ using emval = emscripten::val;
+ struct decoder_sys_t
+ {
+     vlc_thread_t th;
++    std::queue<block_t*> blocks;
++    vlc::threads::mutex mutex;
+ 
+     vlc_video_context* vctx;
+ };
+@@ -93,7 +95,8 @@ EMSCRIPTEN_KEEPALIVE void releaseBlock( block_t* block )
+     block_Release( block );
+ }
+ 
+-EMSCRIPTEN_KEEPALIVE void decodeBlock( block_t* block )
++// doesn't need to be extern anymore
++void decodeBlock( block_t* block )
+ {
+     auto chunkType = emval::global("EncodedVideoChunk");
+     auto chunkCfg = emval::object();
+@@ -124,6 +127,23 @@ EMSCRIPTEN_KEEPALIVE void decodeBlock( block_t* block )
+     EM_ASM({Module.decoder.decode( Emval.toValue( $0 ) ); }, chunk.as_handle());
+ }
+ 
++EMSCRIPTEN_KEEPALIVE void decodeBlocks( decoder_t* dec )
++{
++    auto sys = static_cast<decoder_sys_t*>( dec->p_sys );
++    while ( true )
++    {
++        block_t* block;
++        {
++            vlc::threads::mutex_locker lock{ sys->mutex };
++            if ( sys->blocks.empty() )
++                return;
++            block = sys->blocks.front();
++            sys->blocks.pop();
++        }
++        decodeBlock( block );
++        block_Release( block );
++    }
++}
+ 
+ }
+ 
+@@ -217,9 +237,7 @@ EM_JS(emscripten::EM_VAL, initDecoderJS, (void* decoder, emscripten::EM_VAL decC
+     function onDecoderWorkerMessage(msg) {
+         const data = msg['data'];
+         if (data['customCmd'] == 'decode') {
+-            let block = data['block'];
+-            _decodeBlock( block );
+-            _releaseBlock( block );
++            _decodeBlocks( Module.webCodecCtx );
+         } else if ( data['customCmd'] == 'flush' ) {
+             Module.decoder.flush();
+         } else if ( data['customCmd'] == 'close' ) {
+@@ -310,8 +328,10 @@ static bool initDecoder( decoder_t* dec )
+     return true;
+ }
+ 
+-static void mainloop_tick()
++static void mainloop_tick( void* arg )
+ {
++//    auto dec = static_cast<decoder_t*>( arg );
++//    decodeBlocks( dec );
+ }
+ 
+ static void* WebcodecDecodeWorker( void* arg )
+@@ -336,7 +356,7 @@ static void* WebcodecDecodeWorker( void* arg )
+     auto vctxPrivate = static_cast<webcodec_context*>(
+             vlc_video_context_GetPrivate(sys->vctx, VLC_VIDEO_CONTEXT_WEBCODEC));
+     vctxPrivate->decoder_worker = pthread_self();
+-    emscripten_set_main_loop(mainloop_tick, 1, true);
++    emscripten_set_main_loop_arg(mainloop_tick, dec, 1, true);
+     return NULL;
+ }
+ 
+@@ -368,13 +388,21 @@ EM_ASYNC_JS(void, initDecoderWorkerMessagePort, (decoder_t* dec), {
+ 
+ static int Decode( decoder_t* dec, block_t* block )
+ {
++    if ( block == nullptr )
++        fprintf(stderr, "NULL BLOCK\n");
++    auto sys = static_cast<decoder_sys_t*>( dec->p_sys );
+     initDecoderWorkerMessagePort(dec);
++    {
++        vlc::threads::mutex_locker lock{ sys->mutex };
++        sys->blocks.push( block );
++        if ( sys->blocks.size() > 1 )
++            return VLCDEC_SUCCESS;
++    }
+     EM_ASM({
+         Module.decoderWorkerPort.postMessage({
+             customCmd: 'decode',
+-            block: $0
+         });
+-    }, block);
++    });
+     return VLCDEC_SUCCESS;
+ }
+ 
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0060-wip-setjmp-lgjmp-requires-atomics.patch b/vlc_patches/aug/0060-wip-setjmp-lgjmp-requires-atomics.patch
new file mode 100644
index 0000000000000000000000000000000000000000..9cc5f2179b6017c88e7796f091a9505da92448f8
--- /dev/null
+++ b/vlc_patches/aug/0060-wip-setjmp-lgjmp-requires-atomics.patch
@@ -0,0 +1,60 @@
+From 68e2e53d6acf832d57322911dd06e48d85485357 Mon Sep 17 00:00:00 2001
+From: Mehdi Sabwat <mehdi@videolabs.io>
+Date: Tue, 26 Apr 2022 13:45:07 +0200
+Subject: [PATCH 60/77] wip: setjmp/lgjmp requires atomics
+
+I suppose this is because we can use it for coroutines. Non atomic operations could create race conditions.
+---
+ contrib/src/aribb24/rules.mak | 4 ++++
+ contrib/src/png/rules.mak     | 4 ++++
+ contrib/src/zlib/rules.mak    | 4 ++++
+ 3 files changed, 12 insertions(+)
+
+diff --git a/contrib/src/aribb24/rules.mak b/contrib/src/aribb24/rules.mak
+index 0c0ba9ff91..ba8a70b6d5 100644
+--- a/contrib/src/aribb24/rules.mak
++++ b/contrib/src/aribb24/rules.mak
+@@ -25,6 +25,10 @@ aribb24: aribb24-$(ARIBB24_VERSION).tar.gz .sum-aribb24
+ 
+ DEPS_aribb24 = png
+ 
++ifdef HAVE_EMSCRIPTEN
++CFLAGS+="-pthread"
++endif
++
+ .aribb24: aribb24
+ 	$(REQUIRE_GPL)
+ 	$(REQUIRE_GNUV3)
+diff --git a/contrib/src/png/rules.mak b/contrib/src/png/rules.mak
+index 7a8db11d5a..f1c93b835b 100644
+--- a/contrib/src/png/rules.mak
++++ b/contrib/src/png/rules.mak
+@@ -7,6 +7,10 @@ ifeq ($(call need_pkg,"libpng >= 1.5.4"),)
+ PKGS_FOUND += png
+ endif
+ 
++ifdef HAVE_EMSCRIPTEN
++HOSTVARS += CFLAGS="$(CFLAGS) -pthread"
++endif
++
+ $(TARBALLS)/libpng-$(PNG_VERSION).tar.xz:
+ 	$(call download_pkg,$(PNG_URL),png)
+ 
+diff --git a/contrib/src/zlib/rules.mak b/contrib/src/zlib/rules.mak
+index 4f7fc7397a..166d6b528a 100644
+--- a/contrib/src/zlib/rules.mak
++++ b/contrib/src/zlib/rules.mak
+@@ -7,6 +7,10 @@ ifeq ($(call need_pkg,"zlib"),)
+ PKGS_FOUND += zlib
+ endif
+ 
++ifdef HAVE_EMSCRIPTEN
++CFLAGS+="-pthread"
++endif
++
+ ifeq ($(shell uname),Darwin) # zlib tries to use libtool on Darwin
+ ifdef HAVE_CROSS_COMPILE
+ ZLIB_CONFIG_VARS=CHOST=$(HOST)
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0061-webcodec-revert-manual-block-queue.patch b/vlc_patches/aug/0061-webcodec-revert-manual-block-queue.patch
new file mode 100644
index 0000000000000000000000000000000000000000..9b416a4c8f85dfff4e1af39cf6ccf72dc7f48326
--- /dev/null
+++ b/vlc_patches/aug/0061-webcodec-revert-manual-block-queue.patch
@@ -0,0 +1,107 @@
+From 409c444f88c5a3e9f05ecdcd6de2c16098b3ca2e Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Thu, 12 May 2022 09:47:32 +0200
+Subject: [PATCH 61/77] webcodec: revert manual block queue
+
+---
+ modules/codec/webcodec.cpp | 43 +++++++-------------------------------
+ 1 file changed, 8 insertions(+), 35 deletions(-)
+
+diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp
+index 1967231999..9c39a5c2b4 100644
+--- a/modules/codec/webcodec.cpp
++++ b/modules/codec/webcodec.cpp
+@@ -95,8 +95,7 @@ EMSCRIPTEN_KEEPALIVE void releaseBlock( block_t* block )
+     block_Release( block );
+ }
+ 
+-// doesn't need to be extern anymore
+-void decodeBlock( block_t* block )
++EMSCRIPTEN_KEEPALIVE void decodeBlock( block_t* block )
+ {
+     auto chunkType = emval::global("EncodedVideoChunk");
+     auto chunkCfg = emval::object();
+@@ -127,24 +126,6 @@ void decodeBlock( block_t* block )
+     EM_ASM({Module.decoder.decode( Emval.toValue( $0 ) ); }, chunk.as_handle());
+ }
+ 
+-EMSCRIPTEN_KEEPALIVE void decodeBlocks( decoder_t* dec )
+-{
+-    auto sys = static_cast<decoder_sys_t*>( dec->p_sys );
+-    while ( true )
+-    {
+-        block_t* block;
+-        {
+-            vlc::threads::mutex_locker lock{ sys->mutex };
+-            if ( sys->blocks.empty() )
+-                return;
+-            block = sys->blocks.front();
+-            sys->blocks.pop();
+-        }
+-        decodeBlock( block );
+-        block_Release( block );
+-    }
+-}
+-
+ }
+ 
+ EM_ASYNC_JS(bool, probeConfig, (emscripten::EM_VAL cfg), {
+@@ -237,7 +218,9 @@ EM_JS(emscripten::EM_VAL, initDecoderJS, (void* decoder, emscripten::EM_VAL decC
+     function onDecoderWorkerMessage(msg) {
+         const data = msg['data'];
+         if (data['customCmd'] == 'decode') {
+-            _decodeBlocks( Module.webCodecCtx );
++            let block = data['block'];
++            _decodeBlock( block );
++            _releaseBlock(block);
+         } else if ( data['customCmd'] == 'flush' ) {
+             Module.decoder.flush();
+         } else if ( data['customCmd'] == 'close' ) {
+@@ -328,10 +311,8 @@ static bool initDecoder( decoder_t* dec )
+     return true;
+ }
+ 
+-static void mainloop_tick( void* arg )
++static void mainloop_tick()
+ {
+-//    auto dec = static_cast<decoder_t*>( arg );
+-//    decodeBlocks( dec );
+ }
+ 
+ static void* WebcodecDecodeWorker( void* arg )
+@@ -356,7 +337,7 @@ static void* WebcodecDecodeWorker( void* arg )
+     auto vctxPrivate = static_cast<webcodec_context*>(
+             vlc_video_context_GetPrivate(sys->vctx, VLC_VIDEO_CONTEXT_WEBCODEC));
+     vctxPrivate->decoder_worker = pthread_self();
+-    emscripten_set_main_loop_arg(mainloop_tick, dec, 1, true);
++    emscripten_set_main_loop(mainloop_tick, 1, true);
+     return NULL;
+ }
+ 
+@@ -388,21 +369,13 @@ EM_ASYNC_JS(void, initDecoderWorkerMessagePort, (decoder_t* dec), {
+ 
+ static int Decode( decoder_t* dec, block_t* block )
+ {
+-    if ( block == nullptr )
+-        fprintf(stderr, "NULL BLOCK\n");
+-    auto sys = static_cast<decoder_sys_t*>( dec->p_sys );
+     initDecoderWorkerMessagePort(dec);
+-    {
+-        vlc::threads::mutex_locker lock{ sys->mutex };
+-        sys->blocks.push( block );
+-        if ( sys->blocks.size() > 1 )
+-            return VLCDEC_SUCCESS;
+-    }
+     EM_ASM({
+         Module.decoderWorkerPort.postMessage({
+             customCmd: 'decode',
++            block: $0
+         });
+-    });
++    }, block);
+     return VLCDEC_SUCCESS;
+ }
+ 
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0062-webcodec-Don-t-optimize-for-latency.patch b/vlc_patches/aug/0062-webcodec-Don-t-optimize-for-latency.patch
new file mode 100644
index 0000000000000000000000000000000000000000..9d70992522e4595e36f2ee9ae22bb3989f42c415
--- /dev/null
+++ b/vlc_patches/aug/0062-webcodec-Don-t-optimize-for-latency.patch
@@ -0,0 +1,27 @@
+From 7de88352bd623463cd6d4c5df50ef951e3b4d76a Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Thu, 12 May 2022 09:48:20 +0200
+Subject: [PATCH 62/77] webcodec: Don't optimize for latency
+
+This seems to pace the decoding better but is definitely not a stable
+enough solution, more of a temporary work around
+---
+ modules/codec/webcodec.cpp | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp
+index 9c39a5c2b4..fbdbd3af5f 100644
+--- a/modules/codec/webcodec.cpp
++++ b/modules/codec/webcodec.cpp
+@@ -168,7 +168,7 @@ static emval getDecoderConfig( decoder_t* dec, bool includeExtraData )
+     decoderConfig.set( "codedHeight", dec->fmt_in.video.i_height );
+     decoderConfig.set( "displayAspectWidth", dec->fmt_in.video.i_visible_width );
+     decoderConfig.set( "displayAspectHeight", dec->fmt_in.video.i_visible_height );
+-    decoderConfig.set( "optimizeForLatency", true );
++    decoderConfig.set( "optimizeForLatency", false );
+     if ( includeExtraData )
+     {
+         if ( dec->fmt_in.i_extra > 0 )
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0063-webcodec-Properly-implement-flushing.patch b/vlc_patches/aug/0063-webcodec-Properly-implement-flushing.patch
new file mode 100644
index 0000000000000000000000000000000000000000..cacd8244f1e4e2f2e370ec7bc23d59420476a119
--- /dev/null
+++ b/vlc_patches/aug/0063-webcodec-Properly-implement-flushing.patch
@@ -0,0 +1,81 @@
+From dc500714aeea0c8d2d630245244df2a7b8c24177 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Thu, 12 May 2022 09:50:23 +0200
+Subject: [PATCH 63/77] webcodec: Properly implement flushing
+
+---
+ modules/codec/webcodec.cpp | 34 ++++++++++++++++++++++++++++------
+ 1 file changed, 28 insertions(+), 6 deletions(-)
+
+diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp
+index fbdbd3af5f..a7b67d4ef6 100644
+--- a/modules/codec/webcodec.cpp
++++ b/modules/codec/webcodec.cpp
+@@ -215,14 +215,23 @@ EM_JS(emscripten::EM_VAL, initDecoderJS, (void* decoder, emscripten::EM_VAL decC
+         await p;
+     }
+ 
+-    function onDecoderWorkerMessage(msg) {
++    async function onDecoderWorkerMessage(msg) {
+         const data = msg['data'];
+         if (data['customCmd'] == 'decode') {
+             let block = data['block'];
+             _decodeBlock( block );
+             _releaseBlock(block);
+         } else if ( data['customCmd'] == 'flush' ) {
+-            Module.decoder.flush();
++            await Module.decoder.flush();
++            for ( let i = 0; i < Module.framesReady.length; ++i ) {
++                if (Module.framesReady[i]) {
++                    Module.framesReady[i].close();
++                    Module.framesReady[i] = undefined;
++                }
++            }
++            Module.msgChannel.port1.postMessage({
++                customCmd: 'onFlushCompleted'
++            });
+         } else if ( data['customCmd'] == 'close' ) {
+             Module.decoder.close();
+         }
+@@ -365,6 +374,12 @@ EM_ASYNC_JS(void, initDecoderWorkerMessagePort, (decoder_t* dec), {
+         });
+     });
+     await workerMessagePortPromise;
++    Module.decoderWorkerPort.onmessage = (e) => {
++        let data = e['data'];
++        if (data['customCmd'] == 'onFlushCompleted') {
++            Module.flushPromiseResolver();
++        }
++    };
+ })
+ 
+ static int Decode( decoder_t* dec, block_t* block )
+@@ -379,14 +394,21 @@ static int Decode( decoder_t* dec, block_t* block )
+     return VLCDEC_SUCCESS;
+ }
+ 
+-static void Flush( decoder_t* dec )
+-{
+-    initDecoderWorkerMessagePort(dec);
+-    EM_ASM({
++EM_ASYNC_JS(void, flushAsync, (), {
++    let p = new Promise((r) => {
++        Module.flushPromiseResolver = r;
+         Module.decoderWorkerPort.postMessage({
+             customCmd: 'flush'
+         });
+     });
++    await p;
++    Module.flushPromiseResolver = undefined;
++});
++
++static void Flush( decoder_t* dec )
++{
++    initDecoderWorkerMessagePort(dec);
++    flushAsync();
+ }
+ 
+ static int Open( vlc_object_t* obj )
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0064-emscripten-Handle-late-displayFrame-messages-after-a.patch b/vlc_patches/aug/0064-emscripten-Handle-late-displayFrame-messages-after-a.patch
new file mode 100644
index 0000000000000000000000000000000000000000..2bc6f2c08c3e712bf253d1b59a53d270872cfdb2
--- /dev/null
+++ b/vlc_patches/aug/0064-emscripten-Handle-late-displayFrame-messages-after-a.patch
@@ -0,0 +1,50 @@
+From 2a5fac1b3e80e4647a535f9162e59d7f130db558 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Thu, 12 May 2022 09:58:01 +0200
+Subject: [PATCH 64/77] emscripten: Handle late displayFrame messages after a
+ flush
+
+---
+ modules/codec/webcodec.cpp                         | 12 ++++++++++++
+ modules/video_output/opengl/interop_emscripten.cpp |  2 ++
+ 2 files changed, 14 insertions(+)
+
+diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp
+index a7b67d4ef6..e619cdde00 100644
+--- a/modules/codec/webcodec.cpp
++++ b/modules/codec/webcodec.cpp
+@@ -190,6 +190,18 @@ EM_JS(emscripten::EM_VAL, initDecoderJS, (void* decoder, emscripten::EM_VAL decC
+             let picIdx = data['pictureIdx'];
+             let frame = Module.framesReady[picIdx];
+             Module.framesReady[picIdx] = undefined;
++            if (!frame) {
++                /* We may receive a late message after a flush, in which case
++                 * we still need to reply to the interop, but we don't have
++                 * anything to provide to it
++                 */
++                Module.voutPort.postMessage({
++                    customCmd: 'displayFrame',
++                    frame: undefined,
++                    pictureId: picIdx
++                });
++                return;
++            }
+             Module.voutPort.postMessage({
+                 customCmd: 'displayFrame',
+                 frame: frame,
+diff --git a/modules/video_output/opengl/interop_emscripten.cpp b/modules/video_output/opengl/interop_emscripten.cpp
+index e9905ac1a3..f745fcfef4 100644
+--- a/modules/video_output/opengl/interop_emscripten.cpp
++++ b/modules/video_output/opengl/interop_emscripten.cpp
+@@ -60,6 +60,8 @@ tc_emscripten_op_allocate_textures(const struct vlc_gl_interop *interop, GLuint
+ 
+ EM_ASYNC_JS(void, bindVideoFrame, (int pictureIdx), {
+     let frame = await Module.awaitFrame(pictureIdx);
++    if (!frame)
++        return;
+ 
+     let glCtx = Module.glCtx;
+     glCtx.texImage2D(glCtx.TEXTURE_2D, 0, glCtx.RGBA, frame.codedWidth, frame.codedHeight, 0,
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0065-webcodec-Minor-simplification.patch b/vlc_patches/aug/0065-webcodec-Minor-simplification.patch
new file mode 100644
index 0000000000000000000000000000000000000000..fe95a1398313f39ef243282b4a0740d2e8d9ff15
--- /dev/null
+++ b/vlc_patches/aug/0065-webcodec-Minor-simplification.patch
@@ -0,0 +1,29 @@
+From cd80be3a8c37d4c294c957dac7d7515d71a53843 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Thu, 12 May 2022 09:59:06 +0200
+Subject: [PATCH 65/77] webcodec: Minor simplification
+
+---
+ modules/codec/webcodec.cpp | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp
+index e619cdde00..07b3690dab 100644
+--- a/modules/codec/webcodec.cpp
++++ b/modules/codec/webcodec.cpp
+@@ -253,9 +253,9 @@ EM_JS(emscripten::EM_VAL, initDecoderJS, (void* decoder, emscripten::EM_VAL decC
+         function getPicture(dec, resolve) {
+             let pic = _tryGetPictureFromPool(dec);
+             if (!pic) {
+-                setTimeout( function(d, r) {
+-                      getPicture(d, r);
+-                  }, 1, dec, resolve );
++                setTimeout( function() {
++                      getPicture(dec, resolve);
++                  }, 1);
+                 return;
+             }
+             resolve(pic);
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0066-emscripten-add-js-File-access-plugin.patch b/vlc_patches/aug/0066-emscripten-add-js-File-access-plugin.patch
new file mode 100644
index 0000000000000000000000000000000000000000..30e2d184b5acda2f148dc2456e3d4d39b2355a2f
--- /dev/null
+++ b/vlc_patches/aug/0066-emscripten-add-js-File-access-plugin.patch
@@ -0,0 +1,368 @@
+From 49f585a2a5fbbc04d85fbccbf63881d2a659cc54 Mon Sep 17 00:00:00 2001
+From: Mehdi Sabwat <mehdi@videolabs.io>
+Date: Thu, 9 Jun 2022 21:05:04 +0200
+Subject: [PATCH 66/77] emscripten: add js File access plugin
+
+Emscripten currently does not support picking a file from the DOM with the <input> element, and reading it from a webassembly application.
+
+This access plugin will be able to receive a File object handle in the input thread, and read into buffers in linear memory with the file content while keeping track of the offset.
+
+A basic setup could look like :
+
+```
+<input type="file" id="fpicker_btn">Select File</input>
+<script>
+let instance = _libvlc_new();
+let media_player_ptr = _libvlc_media_player_new(instance);
+
+/*
+	Module is the emscripten runtime object,
+	it has the vlc_access_file array as a property
+*/
+
+let inputElement = document.getElementById("fpicker_btn");
+
+function handleFile() {
+	 var name = this.files.item(0).name;
+	 console.log("opened file: ", name);
+	 let path_ptr = Module.allocateUTF8("emjsfile:///mediafile_" + name);
+	 let media_ptr = _libvlc_media_new_location(path_ptr);
+	 Module._free(path_ptr);
+	 _libvlc_media_player_set_media(media_player_ptr, media_ptr);
+	 Module['vlc_access_file'].push(this.files.item(0));
+}
+inputElement.addEventListener("change", handleFile, false);
+
+_libvlc_media_player_play(media_player_ptr);
+</script>
+```
+---
+ modules/access/Makefile.am |   5 +
+ modules/access/emjsfile.c  | 299 +++++++++++++++++++++++++++++++++++++
+ 2 files changed, 304 insertions(+)
+ create mode 100644 modules/access/emjsfile.c
+
+diff --git a/modules/access/Makefile.am b/modules/access/Makefile.am
+index d4f6b5f10a..cc827027df 100644
+--- a/modules/access/Makefile.am
++++ b/modules/access/Makefile.am
+@@ -26,6 +26,11 @@ libfilesystem_plugin_la_LIBADD = -lshlwapi
+ endif
+ access_LTLIBRARIES += libfilesystem_plugin.la
+ 
++if HAVE_EMSCRIPTEN
++libemjsfile_plugin_la_SOURCES = access/emjsfile.c
++access_LTLIBRARIES += libemjsfile_plugin.la
++endif
++
+ libidummy_plugin_la_SOURCES = access/idummy.c
+ access_LTLIBRARIES += libidummy_plugin.la
+ 
+diff --git a/modules/access/emjsfile.c b/modules/access/emjsfile.c
+new file mode 100644
+index 0000000000..920a408f95
+--- /dev/null
++++ b/modules/access/emjsfile.c
+@@ -0,0 +1,299 @@
++/*****************************************************************************
++ * emjsfile.c: emscripten js file access plugin
++ *****************************************************************************
++ * Copyright (C) 2022 VLC authors Videolabs, and VideoLAN
++ *
++ *
++ * 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 <vlc_common.h>
++#include <vlc_plugin.h>
++#include <vlc_access.h>
++#include <vlc_threads.h>
++
++#include <emscripten.h>
++
++typedef struct
++{
++    uint64_t offset;
++    uint64_t js_file_size;
++} access_sys_t;
++
++static ssize_t Read (stream_t *p_access, void *buffer, size_t size) {
++    access_sys_t *p_sys = p_access->p_sys;
++
++    size_t offset = p_sys->offset;
++    size_t js_file_size = p_sys->js_file_size; 
++
++    if (offset >= js_file_size)
++        return 0;
++    if (size > offset + js_file_size) {
++        size = js_file_size - offset;
++    }
++    EM_ASM({
++        const offset = $0;
++        const buffer = $1;
++        const size = $2;
++        const blob = Module.vlcAccess[$3].worker_js_file.slice(offset, offset + size);
++        HEAPU8.set(new Uint8Array(Module.vlcAccess[$3].reader.readAsArrayBuffer(blob)), buffer);
++    }, offset, buffer, size, p_access);
++    p_sys->offset += size;
++    return size;
++}
++
++static int Seek (stream_t *p_access, uint64_t offset) {
++    access_sys_t *p_sys = p_access->p_sys;
++
++    p_sys->offset = offset;
++    return VLC_SUCCESS;
++}
++
++static int get_js_file_size(stream_t *p_access, uint64_t *value) {
++    /*
++      to avoid RangeError on BigUint64 view creation,
++      the start offset must be a multiple of 8.
++    */
++    if ((uintptr_t)value % 8 != 0) {
++        msg_Err(p_access, "error: value is not aligned in get_js_file_size!");
++        return 1;
++    }        
++    return EM_ASM_INT({
++        try {
++            var v = new BigUint64Array(wasmMemory.buffer, $0, 1);
++            v[0] = BigInt(Module.vlcAccess[$1].worker_js_file.size);
++            return 0;
++        }
++        catch (error) {
++            if (error instanceof RangeError) {
++                // TODO:
++                // RangeError: start offset of BigUint64Array should be a multiple of 8
++                // unaligned accesses are not supported on all platforms?
++                return 0;
++            }
++            console.error("get_js_file_size error: " + error);
++            return 1;
++        }
++    }, value, p_access);
++}
++
++static int Control( stream_t *p_access, int i_query, va_list args )
++{
++    bool    *pb_bool;
++    vlc_tick_t *pi_64;
++    access_sys_t *p_sys = p_access->p_sys;
++
++    switch( i_query )
++    {
++        case STREAM_CAN_SEEK:
++        case STREAM_CAN_FASTSEEK:
++            pb_bool = va_arg( args, bool * );
++            *pb_bool = true;
++            break;
++
++        case STREAM_CAN_PAUSE:
++        case STREAM_CAN_CONTROL_PACE:
++            pb_bool = va_arg( args, bool * );
++            *pb_bool = true;
++            break;
++
++        case STREAM_GET_SIZE:
++        {
++            *va_arg( args, uint64_t * ) = p_sys->js_file_size;
++            break;
++        }
++
++        case STREAM_GET_PTS_DELAY:
++            pi_64 = va_arg( args, vlc_tick_t * );
++            *pi_64 = VLC_TICK_FROM_MS(
++                var_InheritInteger (p_access, "file-caching") );
++            break;
++
++        case STREAM_SET_PAUSE_STATE:
++            break;
++
++        default:
++            return VLC_EGENERIC;
++
++    }
++    return VLC_SUCCESS;
++}
++
++EM_ASYNC_JS(int, init_js_file, (stream_t *p_access, int id), {
++    let p = new Promise((resolve, reject) => {
++        function handleFileResult(e) {
++            const msg = e['data'];
++            if (msg.type === 'FileResult') {
++                self.removeEventListener('message', handleFileResult);
++                if (msg.file !== undefined) {
++                    Module.vlcAccess[p_access].worker_js_file = msg.file;
++                    Module.vlcAccess[p_access].reader = new FileReaderSync();
++                    resolve();
++                }
++                else {
++                    reject("error: sent an undefined File object from the main thread");
++                }
++            }
++            else if (msg.type === 'ErrorVLCAccessFileUndefined') {
++                reject("error: vlc_access_file object is not defined");
++            }
++            else if (msg.type === 'ErrorRequestFileMessageWithoutId') {
++                reject("error: request file message send without an id");
++            }
++            else if (msg.type === 'ErrorMissingFile') {
++                reject("error: missing file, bad id or vlc_access_file[id] is not defined");
++            }
++        }
++        self.addEventListener('message', handleFileResult);
++    });
++    let timer = undefined;
++    let timeout = new Promise(function (resolve, reject) {
++            timer = setTimeout(resolve, 1000, 'timeout')
++        });
++    let promises = [p, timeout];
++    /* id must be unique */
++    self.postMessage({ cmd: "customCmd", type: "requestFile", id: id});
++    try {
++        let return_value = await Promise.race(promises);
++        if (return_value === 'timeout') {
++            console.error("vlc_access timeout: could not get file!");
++        }
++    }
++    catch(error) {
++        console.error("vlc_access error in init_js_file(): ", error);
++        return 1;
++    }
++    clearTimeout(timer);
++    return 0;
++});
++
++static int EmFileOpen( vlc_object_t *p_this ) {
++    stream_t *p_access = (stream_t*)p_this;
++
++    /* init per worker module.vlcAccess object */
++    EM_ASM({
++            if (Module.vlcAccess === undefined) {
++                Module.vlcAccess = {};
++            }
++            (Module.vlcAccess[$0] = {worker_js_file: undefined, reader: undefined});            
++    }, p_access);
++
++    /*
++      This block will run in the main thread, to access the DOM.
++      When the user selects a file, it is assigned to the Module.vlc_access_file
++      array.
++      
++      We listen to 'message' events so that when the file is requested by the
++      input thread, we can answer and send the File object from the main thread.
++    */
++    MAIN_THREAD_EM_ASM({
++        const thread_id = $0;
++        let w = Module.PThread.pthreads[thread_id].worker;
++        function handleFileRequest(e) {
++            const msg = e.data;
++            if (msg.type === "requestFile") {
++                w.removeEventListener('message', handleFileRequest);
++                if (Module.vlc_access_file === undefined) {
++                    console.error("vlc_access_file property missing!");
++                    w.postMessage({ cmd: "customCmd",
++                            type: "ErrorVLCAccessFileUndefined"
++                        });
++                    return ;
++                }
++                if (msg.id === undefined) {
++                    console.error("id property missing in requestFile message!");
++                    w.postMessage({ cmd: "customCmd",
++                            type: "ErrorRequestFileMessageWithoutId"
++                        });
++                    return ;
++                }
++                if (Module.vlc_access_file[msg.id] === undefined) {
++                    console.error("error file missing!");
++                    w.postMessage({ cmd: "customCmd",
++                            type: "ErrorMissingFile"
++                        });
++                    return ;
++                }
++                /*
++                  keeping the File object in the main thread too,
++                  in case we want to reopen the media, without re-selecting
++                  the file.
++                */
++                w.postMessage({ cmd: "customCmd",
++                    type: "FileResult",
++                    file: Module.vlc_access_file[msg.id]
++                });
++            }
++        }
++        w.addEventListener('message', handleFileRequest);
++    }, pthread_self());
++
++    int id = atoi(p_access->psz_location);
++    if (id == 0) {
++        msg_Err(p_access, "error: failed init uri has invalid id!");
++        return VLC_EGENERIC;
++    }
++
++    /*
++      Request the file from the main thread.
++      If it was not selected, it will return an error.
++
++      to open a file, we need to call libvlc_media_new_location with
++      the following uri : emjsfile://<id>
++    */
++    if (init_js_file(p_access, id)) {
++        msg_Err(p_access, "EMJsFile error: failed init!");
++        return VLC_EGENERIC;
++    }
++
++    access_sys_t *p_sys = vlc_obj_malloc(p_this, sizeof (*p_sys));
++    if (unlikely(p_sys == NULL))
++        return VLC_ENOMEM;
++
++    p_access->pf_read = Read;
++    p_access->pf_block = NULL;
++    p_access->pf_control = Control;
++    p_access->pf_seek = Seek;
++    p_access->p_sys = p_sys;
++    p_sys->js_file_size = 0;
++    p_sys->offset = 0;
++    if (get_js_file_size(p_access, &p_sys->js_file_size)) {
++        msg_Err(p_access, "EMJsFile error: could not get file size!");
++        return VLC_EGENERIC;
++    } 
++
++    return VLC_SUCCESS;
++}
++
++static void EmFileClose (vlc_object_t * p_this) {
++    stream_t *p_access = (stream_t*)p_this;
++    EM_ASM({
++            Module.vlcAccess[$0].worker_js_file = undefined;
++            Module.vlcAccess[$0].reader = undefined;            
++        }, p_access);
++}
++
++vlc_module_begin ()
++    set_description( N_("Emscripten module to allow reading local files from the DOM's <input>") )
++    set_shortname( N_("Emscripten Local File Input") )
++    set_subcategory( SUBCAT_INPUT_ACCESS )
++    set_capability( "access", 50 )
++    add_shortcut( "emjsfile" )
++    set_callbacks( EmFileOpen, EmFileClose )
++vlc_module_end()
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0067-gl-utils-Add-a-vlc_gl_IsWebGL-helper.patch b/vlc_patches/aug/0067-gl-utils-Add-a-vlc_gl_IsWebGL-helper.patch
new file mode 100644
index 0000000000000000000000000000000000000000..49cf77039e27fd53932a06a5c275c5c94f3b3048
--- /dev/null
+++ b/vlc_patches/aug/0067-gl-utils-Add-a-vlc_gl_IsWebGL-helper.patch
@@ -0,0 +1,29 @@
+From 92253242486f94556cb52ebaa603e7779545d897 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Tue, 19 Jul 2022 11:24:01 +0200
+Subject: [PATCH 67/77] gl: utils: Add a vlc_gl_IsWebGL helper
+
+---
+ modules/video_output/opengl/gl_util.h | 8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+diff --git a/modules/video_output/opengl/gl_util.h b/modules/video_output/opengl/gl_util.h
+index 551e9ac85e..f72131a9fd 100644
+--- a/modules/video_output/opengl/gl_util.h
++++ b/modules/video_output/opengl/gl_util.h
+@@ -155,4 +155,12 @@ vlc_gl_HasExtension(
+     return false;
+ }
+ 
++static inline bool vlc_gl_IsWebGL(struct vlc_gl_extension_vt *vt)
++{
++    const GLubyte *version = vt->GetString(GL_VERSION);
++    if (!version)
++        return false;
++    return strcasestr((const char*)version, "WebGL") != NULL;
++}
++
+ #endif
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0068-opengl-interop_sw-Force-use-of-GL_LUMINANCE-for-webg.patch b/vlc_patches/aug/0068-opengl-interop_sw-Force-use-of-GL_LUMINANCE-for-webg.patch
new file mode 100644
index 0000000000000000000000000000000000000000..e2ced6ed301f98e459929ccef2f7d277d27feffd
--- /dev/null
+++ b/vlc_patches/aug/0068-opengl-interop_sw-Force-use-of-GL_LUMINANCE-for-webg.patch
@@ -0,0 +1,34 @@
+From c763ea6946e168d1c6c04f83f789e64ea6035bdc Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Hugo=20Beauz=C3=A9e-Luyssen?= <hugo@beauzee.fr>
+Date: Tue, 19 Jul 2022 11:24:17 +0200
+Subject: [PATCH 68/77] opengl: interop_sw: Force use of GL_LUMINANCE for webgl
+
+WebGL doesn't handle our combination of internal format & formats the
+same way opengl(es) does, which leads to GL_INVALID_OPERATION being
+returned when the interop initializes
+---
+ modules/video_output/opengl/interop_sw.c | 5 +++--
+ 1 file changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/modules/video_output/opengl/interop_sw.c b/modules/video_output/opengl/interop_sw.c
+index 6669559b21..d87e20c2aa 100644
+--- a/modules/video_output/opengl/interop_sw.c
++++ b/modules/video_output/opengl/interop_sw.c
+@@ -587,11 +587,12 @@ opengl_interop_generic_init(struct vlc_gl_interop *interop, bool allow_dr)
+         || vlc_gl_HasExtension(&extension_vt, "GL_EXT_unpack_subimage");
+ 
+     /* RG textures are available natively since OpenGL 3.0 and OpenGL ES 3.0 */
+-    priv->has_texture_rg = vlc_gl_GetVersionMajor(&extension_vt) >= 3
++    priv->has_texture_rg = vlc_gl_IsWebGL(&extension_vt) == false &&
++            (vlc_gl_GetVersionMajor(&extension_vt) >= 3
+         || (interop->gl->api_type == VLC_OPENGL
+             && vlc_gl_HasExtension(&extension_vt, "GL_ARB_texture_rg"))
+         || (interop->gl->api_type == VLC_OPENGL_ES2
+-            && vlc_gl_HasExtension(&extension_vt, "GL_EXT_texture_rg"));
++            && vlc_gl_HasExtension(&extension_vt, "GL_EXT_texture_rg")));
+ 
+     video_color_space_t space;
+     const vlc_fourcc_t *list;
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0069-wip-cast-to-avoid-compilation-error.patch b/vlc_patches/aug/0069-wip-cast-to-avoid-compilation-error.patch
new file mode 100644
index 0000000000000000000000000000000000000000..ae7c75bdbb911d46c37b1eaf2613889d94113bb4
--- /dev/null
+++ b/vlc_patches/aug/0069-wip-cast-to-avoid-compilation-error.patch
@@ -0,0 +1,23 @@
+From 9814aba20e525c5dfce0f531a7481aef9dcfde90 Mon Sep 17 00:00:00 2001
+From: Mehdi Sabwat <mehdi@videolabs.io>
+Date: Wed, 10 Aug 2022 13:43:34 +0200
+Subject: [PATCH 1/1] wip: cast to avoid compilation error
+
+---
+ src/emscripten/thread.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/emscripten/thread.c b/src/emscripten/thread.c
+index 8496174278..8fb8c5bb57 100644
+--- a/src/emscripten/thread.c
++++ b/src/emscripten/thread.c
+@@ -28,5 +28,5 @@
+ unsigned long vlc_thread_id(void)
+ {
+     static_assert(sizeof(pthread_t) <= sizeof(unsigned long),"invalid pthread_t size");
+-    return pthread_self();
++    return (unsigned long) pthread_self();
+ }
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0069-wip.patch b/vlc_patches/aug/0069-wip.patch
new file mode 100644
index 0000000000000000000000000000000000000000..24f72761490a4f4064714f8cca1b340df034ee55
--- /dev/null
+++ b/vlc_patches/aug/0069-wip.patch
@@ -0,0 +1,67 @@
+From 08c0fe66b51b2a54b143b079e72674c8f0d5e792 Mon Sep 17 00:00:00 2001
+From: Mehdi Sabwat <mehdi@videolabs.io>
+Date: Wed, 3 Aug 2022 10:43:19 +0200
+Subject: [PATCH 69/77] wip
+
+---
+ src/posix/thread.c | 35 ++++++++++++++++++++++++++++++++++-
+ 1 file changed, 34 insertions(+), 1 deletion(-)
+
+diff --git a/src/posix/thread.c b/src/posix/thread.c
+index 78045d9746..9c7afdfbb1 100644
+--- a/src/posix/thread.c
++++ b/src/posix/thread.c
+@@ -128,7 +128,29 @@ void vlc_threads_setup (libvlc_int_t *p_libvlc)
+ {
+     (void) p_libvlc;
+ }
++#ifdef __EMSCRIPTEN__
++#include <emscripten.h>
++typedef struct s_vlc_clone_data {
++    void *(*entry) (void *);
++    void *data;
++} vlc_clone_data_t;
++
++void vlc_js_runner(void *args) { VLC_UNUSED(args); }
++
++void *vlc_js_entry(void *args) {
++    vlc_clone_data_t *data = (vlc_clone_data_t *)args;
++    /*
++      don't simulate infinite loop to allow running
++      the pthread entry point.
++    */
++    emscripten_set_main_loop_arg(vlc_js_runner, args, -1, 0);
++    void *ret = data->entry(data->data);
++    emscripten_cancel_main_loop();
++    free(data);
++    return ret;
++}
+ 
++#endif
+ static int vlc_clone_attr (vlc_thread_t *th, pthread_attr_t *attr,
+                            void *(*entry) (void *), void *data, int priority)
+ {
+@@ -175,8 +197,19 @@ static int vlc_clone_attr (vlc_thread_t *th, pthread_attr_t *attr,
+     ret = pthread_attr_setstacksize (attr, VLC_STACKSIZE);
+     assert (ret == 0); /* fails iff VLC_STACKSIZE is invalid */
+ #endif
+-
++#ifdef __EMSCRIPTEN__
++    vlc_clone_data_t *vlc_js_data = (vlc_clone_data_t *) malloc(sizeof(vlc_clone_data_t));
++    if (unlikely(vlc_js_data == NULL)) {
++        ret = EAGAIN;
++        goto error;
++    }
++    vlc_js_data->entry = entry;
++    vlc_js_data->data = data;
++    ret = pthread_create(&th->handle, attr, vlc_js_entry, vlc_js_data);
++#else
+     ret = pthread_create(&th->handle, attr, entry, data);
++#endif
++error:
+     pthread_sigmask (SIG_SETMASK, &oldset, NULL);
+     pthread_attr_destroy (attr);
+     (void) priority;
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0070-adapt-to-new-emscripten-version.patch b/vlc_patches/aug/0070-adapt-to-new-emscripten-version.patch
new file mode 100644
index 0000000000000000000000000000000000000000..54b71cb2980e26cc9fbe3b9ba44d8cc73fc60769
--- /dev/null
+++ b/vlc_patches/aug/0070-adapt-to-new-emscripten-version.patch
@@ -0,0 +1,39 @@
+From 54b9e53c6709ed2f15720f3e66396adeef8359ce Mon Sep 17 00:00:00 2001
+From: Mehdi Sabwat <mehdi@videolabs.io>
+Date: Wed, 3 Aug 2022 13:17:46 +0200
+Subject: [PATCH 70/77] adapt to new emscripten version
+
+---
+ modules/access/emjsfile.c           | 2 +-
+ modules/video_output/emscripten.cpp | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/modules/access/emjsfile.c b/modules/access/emjsfile.c
+index 920a408f95..0fea11afc4 100644
+--- a/modules/access/emjsfile.c
++++ b/modules/access/emjsfile.c
+@@ -204,7 +204,7 @@ static int EmFileOpen( vlc_object_t *p_this ) {
+     */
+     MAIN_THREAD_EM_ASM({
+         const thread_id = $0;
+-        let w = Module.PThread.pthreads[thread_id].worker;
++        let w = Module.PThread.pthreads[thread_id];
+         function handleFileRequest(e) {
+             const msg = e.data;
+             if (msg.type === "requestFile") {
+diff --git a/modules/video_output/emscripten.cpp b/modules/video_output/emscripten.cpp
+index 9fd34350b5..e22342e894 100644
+--- a/modules/video_output/emscripten.cpp
++++ b/modules/video_output/emscripten.cpp
+@@ -188,7 +188,7 @@ static int Open (vlc_gl_t *gl, unsigned width, unsigned height)
+                 bmp.close();
+             }
+         }
+-        let w = Module.PThread.pthreads[$0].worker;
++        let w = Module.PThread.pthreads[$0];
+         w.addEventListener('message', function (e) {
+             let msg = e['data'];
+             if (msg.customCmd == 'getVoutMessagePort') {
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0071-don-t-call-emscripten_set_main_loop-twice.patch b/vlc_patches/aug/0071-don-t-call-emscripten_set_main_loop-twice.patch
new file mode 100644
index 0000000000000000000000000000000000000000..b6126ed5b1c00e8941e1d3c618d482a05be6faec
--- /dev/null
+++ b/vlc_patches/aug/0071-don-t-call-emscripten_set_main_loop-twice.patch
@@ -0,0 +1,25 @@
+From 394a123418b39a827467598bf19aef7d9e0a3c3c Mon Sep 17 00:00:00 2001
+From: Mehdi Sabwat <mehdi@videolabs.io>
+Date: Wed, 3 Aug 2022 17:17:27 +0200
+Subject: [PATCH 71/77] don't call emscripten_set_main_loop twice
+
+---
+ modules/codec/webcodec.cpp | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp
+index 07b3690dab..fd93713480 100644
+--- a/modules/codec/webcodec.cpp
++++ b/modules/codec/webcodec.cpp
+@@ -358,7 +358,7 @@ static void* WebcodecDecodeWorker( void* arg )
+     auto vctxPrivate = static_cast<webcodec_context*>(
+             vlc_video_context_GetPrivate(sys->vctx, VLC_VIDEO_CONTEXT_WEBCODEC));
+     vctxPrivate->decoder_worker = pthread_self();
+-    emscripten_set_main_loop(mainloop_tick, 1, true);
++
+     return NULL;
+ }
+ 
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0072-Revert-emscripten-add-js-File-access-plugin.patch b/vlc_patches/aug/0072-Revert-emscripten-add-js-File-access-plugin.patch
new file mode 100644
index 0000000000000000000000000000000000000000..20a093f103fa2649f310f60b77ff5eec81c14d16
--- /dev/null
+++ b/vlc_patches/aug/0072-Revert-emscripten-add-js-File-access-plugin.patch
@@ -0,0 +1,336 @@
+From 000a50334fe09da173a986455c796d97c348108c Mon Sep 17 00:00:00 2001
+From: Mehdi Sabwat <mehdi@videolabs.io>
+Date: Mon, 8 Aug 2022 13:55:50 +0200
+Subject: [PATCH 72/77] Revert "emscripten: add js File access plugin"
+
+This reverts commit 49f585a2a5fbbc04d85fbccbf63881d2a659cc54.
+---
+ modules/access/Makefile.am |   5 -
+ modules/access/emjsfile.c  | 299 -------------------------------------
+ 2 files changed, 304 deletions(-)
+ delete mode 100644 modules/access/emjsfile.c
+
+diff --git a/modules/access/Makefile.am b/modules/access/Makefile.am
+index cc827027df..d4f6b5f10a 100644
+--- a/modules/access/Makefile.am
++++ b/modules/access/Makefile.am
+@@ -26,11 +26,6 @@ libfilesystem_plugin_la_LIBADD = -lshlwapi
+ endif
+ access_LTLIBRARIES += libfilesystem_plugin.la
+ 
+-if HAVE_EMSCRIPTEN
+-libemjsfile_plugin_la_SOURCES = access/emjsfile.c
+-access_LTLIBRARIES += libemjsfile_plugin.la
+-endif
+-
+ libidummy_plugin_la_SOURCES = access/idummy.c
+ access_LTLIBRARIES += libidummy_plugin.la
+ 
+diff --git a/modules/access/emjsfile.c b/modules/access/emjsfile.c
+deleted file mode 100644
+index 0fea11afc4..0000000000
+--- a/modules/access/emjsfile.c
++++ /dev/null
+@@ -1,299 +0,0 @@
+-/*****************************************************************************
+- * emjsfile.c: emscripten js file access plugin
+- *****************************************************************************
+- * Copyright (C) 2022 VLC authors Videolabs, and VideoLAN
+- *
+- *
+- * 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 <vlc_common.h>
+-#include <vlc_plugin.h>
+-#include <vlc_access.h>
+-#include <vlc_threads.h>
+-
+-#include <emscripten.h>
+-
+-typedef struct
+-{
+-    uint64_t offset;
+-    uint64_t js_file_size;
+-} access_sys_t;
+-
+-static ssize_t Read (stream_t *p_access, void *buffer, size_t size) {
+-    access_sys_t *p_sys = p_access->p_sys;
+-
+-    size_t offset = p_sys->offset;
+-    size_t js_file_size = p_sys->js_file_size; 
+-
+-    if (offset >= js_file_size)
+-        return 0;
+-    if (size > offset + js_file_size) {
+-        size = js_file_size - offset;
+-    }
+-    EM_ASM({
+-        const offset = $0;
+-        const buffer = $1;
+-        const size = $2;
+-        const blob = Module.vlcAccess[$3].worker_js_file.slice(offset, offset + size);
+-        HEAPU8.set(new Uint8Array(Module.vlcAccess[$3].reader.readAsArrayBuffer(blob)), buffer);
+-    }, offset, buffer, size, p_access);
+-    p_sys->offset += size;
+-    return size;
+-}
+-
+-static int Seek (stream_t *p_access, uint64_t offset) {
+-    access_sys_t *p_sys = p_access->p_sys;
+-
+-    p_sys->offset = offset;
+-    return VLC_SUCCESS;
+-}
+-
+-static int get_js_file_size(stream_t *p_access, uint64_t *value) {
+-    /*
+-      to avoid RangeError on BigUint64 view creation,
+-      the start offset must be a multiple of 8.
+-    */
+-    if ((uintptr_t)value % 8 != 0) {
+-        msg_Err(p_access, "error: value is not aligned in get_js_file_size!");
+-        return 1;
+-    }        
+-    return EM_ASM_INT({
+-        try {
+-            var v = new BigUint64Array(wasmMemory.buffer, $0, 1);
+-            v[0] = BigInt(Module.vlcAccess[$1].worker_js_file.size);
+-            return 0;
+-        }
+-        catch (error) {
+-            if (error instanceof RangeError) {
+-                // TODO:
+-                // RangeError: start offset of BigUint64Array should be a multiple of 8
+-                // unaligned accesses are not supported on all platforms?
+-                return 0;
+-            }
+-            console.error("get_js_file_size error: " + error);
+-            return 1;
+-        }
+-    }, value, p_access);
+-}
+-
+-static int Control( stream_t *p_access, int i_query, va_list args )
+-{
+-    bool    *pb_bool;
+-    vlc_tick_t *pi_64;
+-    access_sys_t *p_sys = p_access->p_sys;
+-
+-    switch( i_query )
+-    {
+-        case STREAM_CAN_SEEK:
+-        case STREAM_CAN_FASTSEEK:
+-            pb_bool = va_arg( args, bool * );
+-            *pb_bool = true;
+-            break;
+-
+-        case STREAM_CAN_PAUSE:
+-        case STREAM_CAN_CONTROL_PACE:
+-            pb_bool = va_arg( args, bool * );
+-            *pb_bool = true;
+-            break;
+-
+-        case STREAM_GET_SIZE:
+-        {
+-            *va_arg( args, uint64_t * ) = p_sys->js_file_size;
+-            break;
+-        }
+-
+-        case STREAM_GET_PTS_DELAY:
+-            pi_64 = va_arg( args, vlc_tick_t * );
+-            *pi_64 = VLC_TICK_FROM_MS(
+-                var_InheritInteger (p_access, "file-caching") );
+-            break;
+-
+-        case STREAM_SET_PAUSE_STATE:
+-            break;
+-
+-        default:
+-            return VLC_EGENERIC;
+-
+-    }
+-    return VLC_SUCCESS;
+-}
+-
+-EM_ASYNC_JS(int, init_js_file, (stream_t *p_access, int id), {
+-    let p = new Promise((resolve, reject) => {
+-        function handleFileResult(e) {
+-            const msg = e['data'];
+-            if (msg.type === 'FileResult') {
+-                self.removeEventListener('message', handleFileResult);
+-                if (msg.file !== undefined) {
+-                    Module.vlcAccess[p_access].worker_js_file = msg.file;
+-                    Module.vlcAccess[p_access].reader = new FileReaderSync();
+-                    resolve();
+-                }
+-                else {
+-                    reject("error: sent an undefined File object from the main thread");
+-                }
+-            }
+-            else if (msg.type === 'ErrorVLCAccessFileUndefined') {
+-                reject("error: vlc_access_file object is not defined");
+-            }
+-            else if (msg.type === 'ErrorRequestFileMessageWithoutId') {
+-                reject("error: request file message send without an id");
+-            }
+-            else if (msg.type === 'ErrorMissingFile') {
+-                reject("error: missing file, bad id or vlc_access_file[id] is not defined");
+-            }
+-        }
+-        self.addEventListener('message', handleFileResult);
+-    });
+-    let timer = undefined;
+-    let timeout = new Promise(function (resolve, reject) {
+-            timer = setTimeout(resolve, 1000, 'timeout')
+-        });
+-    let promises = [p, timeout];
+-    /* id must be unique */
+-    self.postMessage({ cmd: "customCmd", type: "requestFile", id: id});
+-    try {
+-        let return_value = await Promise.race(promises);
+-        if (return_value === 'timeout') {
+-            console.error("vlc_access timeout: could not get file!");
+-        }
+-    }
+-    catch(error) {
+-        console.error("vlc_access error in init_js_file(): ", error);
+-        return 1;
+-    }
+-    clearTimeout(timer);
+-    return 0;
+-});
+-
+-static int EmFileOpen( vlc_object_t *p_this ) {
+-    stream_t *p_access = (stream_t*)p_this;
+-
+-    /* init per worker module.vlcAccess object */
+-    EM_ASM({
+-            if (Module.vlcAccess === undefined) {
+-                Module.vlcAccess = {};
+-            }
+-            (Module.vlcAccess[$0] = {worker_js_file: undefined, reader: undefined});            
+-    }, p_access);
+-
+-    /*
+-      This block will run in the main thread, to access the DOM.
+-      When the user selects a file, it is assigned to the Module.vlc_access_file
+-      array.
+-      
+-      We listen to 'message' events so that when the file is requested by the
+-      input thread, we can answer and send the File object from the main thread.
+-    */
+-    MAIN_THREAD_EM_ASM({
+-        const thread_id = $0;
+-        let w = Module.PThread.pthreads[thread_id];
+-        function handleFileRequest(e) {
+-            const msg = e.data;
+-            if (msg.type === "requestFile") {
+-                w.removeEventListener('message', handleFileRequest);
+-                if (Module.vlc_access_file === undefined) {
+-                    console.error("vlc_access_file property missing!");
+-                    w.postMessage({ cmd: "customCmd",
+-                            type: "ErrorVLCAccessFileUndefined"
+-                        });
+-                    return ;
+-                }
+-                if (msg.id === undefined) {
+-                    console.error("id property missing in requestFile message!");
+-                    w.postMessage({ cmd: "customCmd",
+-                            type: "ErrorRequestFileMessageWithoutId"
+-                        });
+-                    return ;
+-                }
+-                if (Module.vlc_access_file[msg.id] === undefined) {
+-                    console.error("error file missing!");
+-                    w.postMessage({ cmd: "customCmd",
+-                            type: "ErrorMissingFile"
+-                        });
+-                    return ;
+-                }
+-                /*
+-                  keeping the File object in the main thread too,
+-                  in case we want to reopen the media, without re-selecting
+-                  the file.
+-                */
+-                w.postMessage({ cmd: "customCmd",
+-                    type: "FileResult",
+-                    file: Module.vlc_access_file[msg.id]
+-                });
+-            }
+-        }
+-        w.addEventListener('message', handleFileRequest);
+-    }, pthread_self());
+-
+-    int id = atoi(p_access->psz_location);
+-    if (id == 0) {
+-        msg_Err(p_access, "error: failed init uri has invalid id!");
+-        return VLC_EGENERIC;
+-    }
+-
+-    /*
+-      Request the file from the main thread.
+-      If it was not selected, it will return an error.
+-
+-      to open a file, we need to call libvlc_media_new_location with
+-      the following uri : emjsfile://<id>
+-    */
+-    if (init_js_file(p_access, id)) {
+-        msg_Err(p_access, "EMJsFile error: failed init!");
+-        return VLC_EGENERIC;
+-    }
+-
+-    access_sys_t *p_sys = vlc_obj_malloc(p_this, sizeof (*p_sys));
+-    if (unlikely(p_sys == NULL))
+-        return VLC_ENOMEM;
+-
+-    p_access->pf_read = Read;
+-    p_access->pf_block = NULL;
+-    p_access->pf_control = Control;
+-    p_access->pf_seek = Seek;
+-    p_access->p_sys = p_sys;
+-    p_sys->js_file_size = 0;
+-    p_sys->offset = 0;
+-    if (get_js_file_size(p_access, &p_sys->js_file_size)) {
+-        msg_Err(p_access, "EMJsFile error: could not get file size!");
+-        return VLC_EGENERIC;
+-    } 
+-
+-    return VLC_SUCCESS;
+-}
+-
+-static void EmFileClose (vlc_object_t * p_this) {
+-    stream_t *p_access = (stream_t*)p_this;
+-    EM_ASM({
+-            Module.vlcAccess[$0].worker_js_file = undefined;
+-            Module.vlcAccess[$0].reader = undefined;            
+-        }, p_access);
+-}
+-
+-vlc_module_begin ()
+-    set_description( N_("Emscripten module to allow reading local files from the DOM's <input>") )
+-    set_shortname( N_("Emscripten Local File Input") )
+-    set_subcategory( SUBCAT_INPUT_ACCESS )
+-    set_capability( "access", 50 )
+-    add_shortcut( "emjsfile" )
+-    set_callbacks( EmFileOpen, EmFileClose )
+-vlc_module_end()
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0073-emscripten-add-js-File-access-plugin.patch b/vlc_patches/aug/0073-emscripten-add-js-File-access-plugin.patch
new file mode 100644
index 0000000000000000000000000000000000000000..d8f31f0b2096e8771af342c01d058b78df290208
--- /dev/null
+++ b/vlc_patches/aug/0073-emscripten-add-js-File-access-plugin.patch
@@ -0,0 +1,368 @@
+From 77373ae9ca4cb51cadc14a12e2d5d928c5328727 Mon Sep 17 00:00:00 2001
+From: Mehdi Sabwat <mehdi@videolabs.io>
+Date: Thu, 9 Jun 2022 21:05:04 +0200
+Subject: [PATCH 73/77] emscripten: add js File access plugin
+
+Emscripten currently does not support picking a file from the DOM with the element, and reading it from a webassembly application.
+
+This access plugin will be able to receive a File object handle in the input thread, and read into buffers in linear memory with the file content while keeping track of the offset.
+
+A basic setup could look like :
+
+```
+<input type="file" id="fpicker_btn">Select File</input>
+<script>
+let instance = _libvlc_new();
+let media_player_ptr = _libvlc_media_player_new(instance);
+
+/*
+	Module is the emscripten runtime object,
+	it has the vlc_access_file array as a property
+*/
+
+let inputElement = document.getElementById("fpicker_btn");
+
+function handleFile() {
+	 var name = this.files.item(0).name;
+	 console.log("opened file: ", name);
+         // id starts at 1
+         let id = 1;
+	 let path_ptr = Module.allocateUTF8("emjsfile://" + id);
+	 let media_ptr = _libvlc_media_new_location(path_ptr);
+	 Module._free(path_ptr);
+	 _libvlc_media_player_set_media(media_player_ptr, media_ptr);
+	 Module['vlc_access_file'][id] = this.files.item(0);
+}
+inputElement.addEventListener("change", handleFile, false);
+
+_libvlc_media_player_play(media_player_ptr);
+</script>
+```
+---
+ modules/access/Makefile.am |   5 +
+ modules/access/emjsfile.c  | 297 +++++++++++++++++++++++++++++++++++++
+ 2 files changed, 302 insertions(+)
+ create mode 100644 modules/access/emjsfile.c
+
+diff --git a/modules/access/Makefile.am b/modules/access/Makefile.am
+index d4f6b5f10a..cc827027df 100644
+--- a/modules/access/Makefile.am
++++ b/modules/access/Makefile.am
+@@ -26,6 +26,11 @@ libfilesystem_plugin_la_LIBADD = -lshlwapi
+ endif
+ access_LTLIBRARIES += libfilesystem_plugin.la
+ 
++if HAVE_EMSCRIPTEN
++libemjsfile_plugin_la_SOURCES = access/emjsfile.c
++access_LTLIBRARIES += libemjsfile_plugin.la
++endif
++
+ libidummy_plugin_la_SOURCES = access/idummy.c
+ access_LTLIBRARIES += libidummy_plugin.la
+ 
+diff --git a/modules/access/emjsfile.c b/modules/access/emjsfile.c
+new file mode 100644
+index 0000000000..139b12b68a
+--- /dev/null
++++ b/modules/access/emjsfile.c
+@@ -0,0 +1,297 @@
++/*****************************************************************************
++ * emjsfile.c: emscripten js file access plugin
++ *****************************************************************************
++ * Copyright (C) 2022 VLC authors Videolabs, and VideoLAN
++ *
++ *
++ * 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 <vlc_common.h>
++#include <vlc_plugin.h>
++#include <vlc_access.h>
++#include <vlc_threads.h>
++
++#include <emscripten.h>
++
++typedef struct
++{
++    uint64_t offset;
++    uint64_t js_file_size;
++} access_sys_t;
++
++static ssize_t Read (stream_t *p_access, void *buffer, size_t size) {
++    access_sys_t *p_sys = p_access->p_sys;
++
++    size_t offset = p_sys->offset;
++    size_t js_file_size = p_sys->js_file_size; 
++
++    if (offset >= js_file_size)
++        return 0;
++    if (size > offset + js_file_size) {
++        size = js_file_size - offset;
++    }
++    EM_ASM({
++        const offset = $0;
++        const buffer = $1;
++        const size = $2;
++        const blob = Module.vlcAccess[$3].worker_js_file.slice(offset, offset + size);
++        HEAPU8.set(new Uint8Array(Module.vlcAccess[$3].reader.readAsArrayBuffer(blob)), buffer);
++    }, offset, buffer, size, p_access);
++    p_sys->offset += size;
++    return size;
++}
++
++static int Seek (stream_t *p_access, uint64_t offset) {
++    access_sys_t *p_sys = p_access->p_sys;
++
++    p_sys->offset = offset;
++    return VLC_SUCCESS;
++}
++
++static int get_js_file_size(stream_t *p_access, uint64_t *value) {
++    /*
++      to avoid RangeError on BigUint64 view creation,
++      the start offset must be a multiple of 8.
++    */
++    if ((uintptr_t)value % 8 != 0) {
++        msg_Err(p_access, "error: value is not aligned in get_js_file_size!");
++        return VLC_EGENERIC;
++    }        
++    return (EM_ASM_INT({
++        try {
++            var v = new BigUint64Array(wasmMemory.buffer, $0, 1);
++            v[0] = BigInt(Module.vlcAccess[$1].worker_js_file.size);
++            return 0;
++        }
++        catch (error) {
++            console.error("get_js_file_size error: " + error);
++            return 1;
++        }
++    }, value, p_access) == 0) ? VLC_SUCCESS: VLC_EGENERIC;
++}
++
++static int Control( stream_t *p_access, int i_query, va_list args )
++{
++    bool    *pb_bool;
++    vlc_tick_t *pi_64;
++    access_sys_t *p_sys = p_access->p_sys;
++
++    switch( i_query )
++    {
++        case STREAM_CAN_SEEK:
++        case STREAM_CAN_FASTSEEK:
++            pb_bool = va_arg( args, bool * );
++            *pb_bool = true;
++            break;
++
++        case STREAM_CAN_PAUSE:
++        case STREAM_CAN_CONTROL_PACE:
++            pb_bool = va_arg( args, bool * );
++            *pb_bool = true;
++            break;
++
++        case STREAM_GET_SIZE:
++        {
++            *va_arg( args, uint64_t * ) = p_sys->js_file_size;
++            break;
++        }
++
++        case STREAM_GET_PTS_DELAY:
++            pi_64 = va_arg( args, vlc_tick_t * );
++            *pi_64 = VLC_TICK_FROM_MS(
++                var_InheritInteger (p_access, "file-caching") );
++            break;
++
++        case STREAM_SET_PAUSE_STATE:
++            break;
++
++        default:
++            return VLC_EGENERIC;
++
++    }
++    return VLC_SUCCESS;
++}
++
++EM_ASYNC_JS(int, init_js_file, (stream_t *p_access, long id), {
++    let p = new Promise((resolve, reject) => {
++        function handleFileResult(e) {
++            const msg = e['data'];
++            if (msg.type === 'FileResult') {
++                self.removeEventListener('message', handleFileResult);
++                if (msg.file !== undefined) {
++                    Module.vlcAccess[p_access].worker_js_file = msg.file;
++                    Module.vlcAccess[p_access].reader = new FileReaderSync();
++                    resolve();
++                }
++                else {
++                    reject("error: sent an undefined File object from the main thread");
++                }
++            }
++            else if (msg.type === 'ErrorVLCAccessFileUndefined') {
++                reject("error: vlc_access_file object is not defined");
++            }
++            else if (msg.type === 'ErrorRequestFileMessageWithoutId') {
++                reject("error: request file message send without an id");
++            }
++            else if (msg.type === 'ErrorMissingFile') {
++                reject("error: missing file, bad id or vlc_access_file[id] is not defined");
++            }
++        }
++        self.addEventListener('message', handleFileResult);
++    });
++    let timer = undefined;
++    let timeout = new Promise(function (resolve, reject) {
++            timer = setTimeout(resolve, 1000, 'timeout')
++        });
++    let promises = [p, timeout];
++    /* id must be unique */
++    self.postMessage({ cmd: "customCmd", type: "requestFile", id: id});
++    let return_value = 0;
++    try {
++        let value = await Promise.race(promises);
++        if (value === 'timeout') {
++            console.error("vlc_access timeout: could not get file!");
++            return_value = 1;
++        }
++    }
++    catch(error) {
++        console.error("vlc_access error in init_js_file(): ", error);
++        return_value = 1;
++    }
++    clearTimeout(timer);
++    return return_value;
++});
++
++static int EmFileOpen( vlc_object_t *p_this ) {
++    stream_t *p_access = (stream_t*)p_this;
++
++    /* init per worker module.vlcAccess object */
++    EM_ASM({
++            if (Module.vlcAccess === undefined) {
++                Module.vlcAccess = {};
++            }
++            (Module.vlcAccess[$0] = {worker_js_file: undefined, reader: undefined});            
++    }, p_access);
++
++    /*
++      This block will run in the main thread, to access the DOM.
++      When the user selects a file, it is assigned to the Module.vlc_access_file
++      array.
++      
++      We listen to 'message' events so that when the file is requested by the
++      input thread, we can answer and send the File object from the main thread.
++    */
++    MAIN_THREAD_EM_ASM({
++        const thread_id = $0;
++        let w = Module.PThread.pthreads[thread_id].worker;
++        function handleFileRequest(e) {
++            const msg = e.data;
++            if (msg.type === "requestFile") {
++                w.removeEventListener('message', handleFileRequest);
++                if (Module.vlc_access_file === undefined) {
++                    console.error("vlc_access_file property missing!");
++                    w.postMessage({ cmd: "customCmd",
++                            type: "ErrorVLCAccessFileUndefined"
++                        });
++                    return ;
++                }
++                if (msg.id === undefined) {
++                    console.error("id property missing in requestFile message!");
++                    w.postMessage({ cmd: "customCmd",
++                            type: "ErrorRequestFileMessageWithoutId"
++                        });
++                    return ;
++                }
++                if (Module.vlc_access_file[msg.id] === undefined) {
++                    console.error("error file missing!");
++                    w.postMessage({ cmd: "customCmd",
++                            type: "ErrorMissingFile"
++                        });
++                    return ;
++                }
++                /*
++                  keeping the File object in the main thread too,
++                  in case we want to reopen the media, without re-selecting
++                  the file.
++                */
++                w.postMessage({ cmd: "customCmd",
++                    type: "FileResult",
++                    file: Module.vlc_access_file[msg.id]
++                });
++            }
++        }
++        w.addEventListener('message', handleFileRequest);
++    }, pthread_self());
++
++    char *endPtr;
++    long id = strtol(p_access->psz_location, &endPtr, 10);
++    if ((endPtr == p_access->psz_location) || (*endPtr != '\0')) {
++        msg_Err(p_access, "error: failed init uri has invalid id!");
++        return VLC_EGENERIC;
++    }
++
++    /*
++      Request the file from the main thread.
++      If it was not selected, it will return an error.
++
++      To open a file, we need to call libvlc_media_new_location with
++      the following uri : emjsfile://<id>
++      To avoid confusion with atoi() return error, id starts at 1.
++    */
++    if (init_js_file(p_access, id)) {
++        msg_Err(p_access, "EMJsFile error: failed init!");
++        return VLC_EGENERIC;
++    }
++
++    access_sys_t *p_sys = vlc_obj_malloc(p_this, sizeof (*p_sys));
++    if (unlikely(p_sys == NULL))
++        return VLC_ENOMEM;
++
++    p_access->pf_read = Read;
++    p_access->pf_block = NULL;
++    p_access->pf_control = Control;
++    p_access->pf_seek = Seek;
++    p_access->p_sys = p_sys;
++    p_sys->js_file_size = 0;
++    p_sys->offset = 0;
++    if (get_js_file_size(p_access, &p_sys->js_file_size)) {
++        msg_Err(p_access, "EMJsFile error: could not get file size!");
++        return VLC_EGENERIC;
++    } 
++
++    return VLC_SUCCESS;
++}
++
++static void EmFileClose (vlc_object_t * p_this) {
++    stream_t *p_access = (stream_t*)p_this;
++    EM_ASM({
++            Module.vlcAccess[$0].worker_js_file = undefined;
++            Module.vlcAccess[$0].reader = undefined;            
++        }, p_access);
++}
++
++vlc_module_begin ()
++    set_description( N_("Emscripten module to allow reading local files from the DOM's <input>") )
++    set_shortname( N_("Emscripten Local File Input") )
++    set_subcategory( SUBCAT_INPUT_ACCESS )
++    set_capability( "access", 0 )
++    add_shortcut( "emjsfile" )
++    set_callbacks( EmFileOpen, EmFileClose )
++vlc_module_end()
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0074-emjsfile-Close-file-access-if-open-failed.patch b/vlc_patches/aug/0074-emjsfile-Close-file-access-if-open-failed.patch
new file mode 100644
index 0000000000000000000000000000000000000000..648eb7e03423c594ae8c953a8a14315b608f5e2e
--- /dev/null
+++ b/vlc_patches/aug/0074-emjsfile-Close-file-access-if-open-failed.patch
@@ -0,0 +1,75 @@
+From 935282fa99a0356589692c3493e143eccb77cd2d Mon Sep 17 00:00:00 2001
+From: Mehdi Sabwat <mehdi@videolabs.io>
+Date: Mon, 8 Aug 2022 13:46:48 +0200
+Subject: [PATCH 74/77] emjsfile: Close file access if open() failed
+
+---
+ modules/access/emjsfile.c | 25 +++++++++++++------------
+ 1 file changed, 13 insertions(+), 12 deletions(-)
+
+diff --git a/modules/access/emjsfile.c b/modules/access/emjsfile.c
+index 139b12b68a..488b6fb449 100644
+--- a/modules/access/emjsfile.c
++++ b/modules/access/emjsfile.c
+@@ -179,6 +179,14 @@ EM_ASYNC_JS(int, init_js_file, (stream_t *p_access, long id), {
+     return return_value;
+ });
+ 
++static void EmFileClose (vlc_object_t * p_this) {
++    stream_t *p_access = (stream_t*)p_this;
++    EM_ASM({
++            Module.vlcAccess[$0].worker_js_file = undefined;
++            Module.vlcAccess[$0].reader = undefined;            
++        }, p_access);
++}
++
+ static int EmFileOpen( vlc_object_t *p_this ) {
+     stream_t *p_access = (stream_t*)p_this;
+ 
+@@ -247,6 +255,10 @@ static int EmFileOpen( vlc_object_t *p_this ) {
+         return VLC_EGENERIC;
+     }
+ 
++    access_sys_t *p_sys = vlc_obj_malloc(p_this, sizeof (*p_sys));
++    if (unlikely(p_sys == NULL))
++        return VLC_ENOMEM;
++    
+     /*
+       Request the file from the main thread.
+       If it was not selected, it will return an error.
+@@ -260,10 +272,6 @@ static int EmFileOpen( vlc_object_t *p_this ) {
+         return VLC_EGENERIC;
+     }
+ 
+-    access_sys_t *p_sys = vlc_obj_malloc(p_this, sizeof (*p_sys));
+-    if (unlikely(p_sys == NULL))
+-        return VLC_ENOMEM;
+-
+     p_access->pf_read = Read;
+     p_access->pf_block = NULL;
+     p_access->pf_control = Control;
+@@ -273,20 +281,13 @@ static int EmFileOpen( vlc_object_t *p_this ) {
+     p_sys->offset = 0;
+     if (get_js_file_size(p_access, &p_sys->js_file_size)) {
+         msg_Err(p_access, "EMJsFile error: could not get file size!");
++        EmFileClose(p_this);
+         return VLC_EGENERIC;
+     } 
+ 
+     return VLC_SUCCESS;
+ }
+ 
+-static void EmFileClose (vlc_object_t * p_this) {
+-    stream_t *p_access = (stream_t*)p_this;
+-    EM_ASM({
+-            Module.vlcAccess[$0].worker_js_file = undefined;
+-            Module.vlcAccess[$0].reader = undefined;            
+-        }, p_access);
+-}
+-
+ vlc_module_begin ()
+     set_description( N_("Emscripten module to allow reading local files from the DOM's <input>") )
+     set_shortname( N_("Emscripten Local File Input") )
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0075-emjsfile-document-js_file_size-struct-field.patch b/vlc_patches/aug/0075-emjsfile-document-js_file_size-struct-field.patch
new file mode 100644
index 0000000000000000000000000000000000000000..c231ebeb954b0ecdb260b4ad0420647b09a9ec1e
--- /dev/null
+++ b/vlc_patches/aug/0075-emjsfile-document-js_file_size-struct-field.patch
@@ -0,0 +1,32 @@
+From e913df9e055f1fee9d17d9b23ed46d59600c89b2 Mon Sep 17 00:00:00 2001
+From: Mehdi Sabwat <mehdi@videolabs.io>
+Date: Mon, 8 Aug 2022 13:49:54 +0200
+Subject: [PATCH 75/77] emjsfile: document js_file_size struct field
+
+---
+ modules/access/emjsfile.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/modules/access/emjsfile.c b/modules/access/emjsfile.c
+index 488b6fb449..245a6a7201 100644
+--- a/modules/access/emjsfile.c
++++ b/modules/access/emjsfile.c
+@@ -27,13 +27,14 @@
+ #include <vlc_plugin.h>
+ #include <vlc_access.h>
+ #include <vlc_threads.h>
++#include <stdalign.h>
+ 
+ #include <emscripten.h>
+ 
+ typedef struct
+ {
+     uint64_t offset;
+-    uint64_t js_file_size;
++    uint64_t alignas(8) js_file_size;
+ } access_sys_t;
+ 
+ static ssize_t Read (stream_t *p_access, void *buffer, size_t size) {
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0076-emjsfile-adapt-to-emscripten-change.patch b/vlc_patches/aug/0076-emjsfile-adapt-to-emscripten-change.patch
new file mode 100644
index 0000000000000000000000000000000000000000..7e7d0a21a364089be1f1fa200a4f6018abff5aa2
--- /dev/null
+++ b/vlc_patches/aug/0076-emjsfile-adapt-to-emscripten-change.patch
@@ -0,0 +1,25 @@
+From 153eecd6159eaae8ab98dd98accc6eb7f033064d Mon Sep 17 00:00:00 2001
+From: Mehdi Sabwat <mehdi@videolabs.io>
+Date: Mon, 8 Aug 2022 16:55:53 +0200
+Subject: [PATCH 76/77] emjsfile: adapt to emscripten change
+
+---
+ modules/access/emjsfile.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/modules/access/emjsfile.c b/modules/access/emjsfile.c
+index 245a6a7201..9123a4f0fc 100644
+--- a/modules/access/emjsfile.c
++++ b/modules/access/emjsfile.c
+@@ -209,7 +209,7 @@ static int EmFileOpen( vlc_object_t *p_this ) {
+     */
+     MAIN_THREAD_EM_ASM({
+         const thread_id = $0;
+-        let w = Module.PThread.pthreads[thread_id].worker;
++        let w = Module.PThread.pthreads[thread_id];
+         function handleFileRequest(e) {
+             const msg = e.data;
+             if (msg.type === "requestFile") {
+-- 
+2.35.1
+
diff --git a/vlc_patches/aug/0077-wip-allow-decoding-NULL-blocks-drain.patch b/vlc_patches/aug/0077-wip-allow-decoding-NULL-blocks-drain.patch
new file mode 100644
index 0000000000000000000000000000000000000000..18abe207b45d787f161fa27a628b7a6d4ac34609
--- /dev/null
+++ b/vlc_patches/aug/0077-wip-allow-decoding-NULL-blocks-drain.patch
@@ -0,0 +1,24 @@
+From c3e109460e95fba32914a1b525a8b3f85b705ff9 Mon Sep 17 00:00:00 2001
+From: Mehdi Sabwat <mehdi@videolabs.io>
+Date: Mon, 8 Aug 2022 16:57:45 +0200
+Subject: [PATCH 77/77] wip: allow decoding NULL blocks (drain ?)
+
+---
+ modules/codec/webcodec.cpp | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp
+index fd93713480..aefe6a6153 100644
+--- a/modules/codec/webcodec.cpp
++++ b/modules/codec/webcodec.cpp
+@@ -396,6 +396,7 @@ EM_ASYNC_JS(void, initDecoderWorkerMessagePort, (decoder_t* dec), {
+ 
+ static int Decode( decoder_t* dec, block_t* block )
+ {
++    if ( !block ) return VLCDEC_SUCCESS;
+     initDecoderWorkerMessagePort(dec);
+     EM_ASM({
+         Module.decoderWorkerPort.postMessage({
+-- 
+2.35.1
+
diff --git a/vlc_patches/demo_alpha/0001-wip-vlc.js-patchset.patch b/vlc_patches/demo_alpha/0001-wip-vlc.js-patchset.patch
new file mode 100644
index 0000000000000000000000000000000000000000..95c65ba823e072c61943b5978a08f704cfa64b12
--- /dev/null
+++ b/vlc_patches/demo_alpha/0001-wip-vlc.js-patchset.patch
@@ -0,0 +1,2990 @@
+From 88e59dbaa68156d0a00c88217a9e1a4c6502b731 Mon Sep 17 00:00:00 2001
+From: Mehdi Sabwat <mehdi@videolabs.io>
+Date: Tue, 27 Apr 2021 15:34:23 +0200
+Subject: [PATCH 1/1] wip: vlc.js patchset
+
+---
+ configure.ac                                  |  10 +-
+ contrib/src/aribb24/rules.mak                 |   4 +
+ contrib/src/ass/rules.mak                     |   5 +
+ ...nitial-support-for-wasm32-emscripten.patch | 105 ++++
+ contrib/src/fontconfig/rules.mak              |   4 +
+ contrib/src/gcrypt/rules.mak                  |   6 +
+ contrib/src/gmp/rules.mak                     |   3 +
+ contrib/src/gnutls/rules.mak                  |   4 +
+ contrib/src/gpg-error/emscripten.patch        |  43 ++
+ contrib/src/gpg-error/rules.mak               |   1 +
+ contrib/src/png/rules.mak                     |   4 +
+ contrib/src/zlib/rules.mak                    |   4 +
+ extras/package/wasm-emscripten/build.sh       |   5 +
+ include/vlc_codec.h                           |   1 +
+ include/vlc_fourcc.h                          |   3 +
+ include/vlc_opengl_filter.h                   | 157 ++++++
+ include/vlc_picture.h                         |   1 +
+ include/vlc_vout_display.h                    |   2 +
+ include/vlc_window.h                          |   3 +
+ modules/Makefile.am                           |   1 +
+ modules/audio_output/Makefile.am              |   5 +
+ modules/audio_output/emscripten.cpp           | 421 ++++++++++++++
+ modules/codec/Makefile.am                     |   6 +
+ modules/codec/webcodec.cpp                    | 518 ++++++++++++++++++
+ modules/demux/Makefile.am                     |   2 +
+ modules/hw/emscripten/Makefile.am             |  10 +
+ modules/hw/emscripten/converter.cpp           | 137 +++++
+ modules/video_output/Makefile.am              |  14 +
+ modules/video_output/emscripten.cpp           | 257 +++++++++
+ modules/video_output/emscripten/common.cpp    |  82 +++
+ modules/video_output/emscripten/common.h      |  57 ++
+ modules/video_output/opengl/Makefile.am       |   6 +
+ .../video_output/opengl/egl_display_generic.c |   4 +-
+ modules/video_output/opengl/filter.c          |   1 +
+ modules/video_output/opengl/filter.h          | 137 +----
+ modules/video_output/opengl/filter_mock.c     |   5 +
+ modules/video_output/opengl/filter_priv.h     |   1 +
+ modules/video_output/opengl/filters.c         |   1 +
+ .../opengl/interop_emscripten.cpp             | 287 ++++++++++
+ modules/video_output/opengl/renderer.c        |   1 +
+ modules/video_output/opengl/sampler.c         |  27 +-
+ modules/video_output/opengl/sampler.h         |   9 +
+ src/misc/fourcc.c                             |   2 +
+ src/video_output/display.c                    |   2 +-
+ src/video_output/video_output.c               |  31 +-
+ 45 files changed, 2234 insertions(+), 155 deletions(-)
+ create mode 100644 contrib/src/fontconfig/add-initial-support-for-wasm32-emscripten.patch
+ create mode 100644 contrib/src/gpg-error/emscripten.patch
+ create mode 100644 include/vlc_opengl_filter.h
+ create mode 100644 modules/audio_output/emscripten.cpp
+ create mode 100644 modules/codec/webcodec.cpp
+ create mode 100644 modules/hw/emscripten/Makefile.am
+ create mode 100644 modules/hw/emscripten/converter.cpp
+ create mode 100644 modules/video_output/emscripten.cpp
+ create mode 100644 modules/video_output/emscripten/common.cpp
+ create mode 100644 modules/video_output/emscripten/common.h
+ create mode 100644 modules/video_output/opengl/interop_emscripten.cpp
+
+diff --git a/configure.ac b/configure.ac
+index d0e5f8e216..9ef4138d9f 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -3122,13 +3122,17 @@ have_gl="no"
+ PKG_CHECK_MODULES([GL], [gl], [
+   have_gl="yes"
+ ], [
+-  AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
++  AC_MSG_CHECKING([for OpenGL])
++  AC_LINK_IFELSE([AC_LANG_PROGRAM([[
+ #ifdef _WIN32
+ # include <GL/glew.h>
+ #endif
+ #include <GL/gl.h>
+-]], [
+-    [int t0 = GL_TEXTURE0;]])
++]], [[
++	int t0 = GL_TEXTURE0;
++	// glColorMaterial is unavailable in webgl, and emscripten
++	glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
++	]])
+   ], [
+     GL_CFLAGS=""
+     AS_IF([test "${SYS}" != "mingw32"], [
+diff --git a/contrib/src/aribb24/rules.mak b/contrib/src/aribb24/rules.mak
+index 0c0ba9ff91..ba8a70b6d5 100644
+--- a/contrib/src/aribb24/rules.mak
++++ b/contrib/src/aribb24/rules.mak
+@@ -25,6 +25,10 @@ aribb24: aribb24-$(ARIBB24_VERSION).tar.gz .sum-aribb24
+ 
+ DEPS_aribb24 = png
+ 
++ifdef HAVE_EMSCRIPTEN
++CFLAGS+="-pthread"
++endif
++
+ .aribb24: aribb24
+ 	$(REQUIRE_GPL)
+ 	$(REQUIRE_GNUV3)
+diff --git a/contrib/src/ass/rules.mak b/contrib/src/ass/rules.mak
+index d5fd21ff29..f70216f028 100644
+--- a/contrib/src/ass/rules.mak
++++ b/contrib/src/ass/rules.mak
+@@ -25,6 +25,11 @@ WITH_DWRITE = 1
+ else
+ WITH_FONTCONFIG = 1
+ WITH_HARFBUZZ = 1
++ifdef HAVE_EMSCRIPTEN
++WITH_FONTCONFIG = 1
++WITH_HARFBUZZ = 1
++WITH_ASS_ASM = 0
++endif
+ endif
+ endif
+ endif
+diff --git a/contrib/src/fontconfig/add-initial-support-for-wasm32-emscripten.patch b/contrib/src/fontconfig/add-initial-support-for-wasm32-emscripten.patch
+new file mode 100644
+index 0000000000..b1308fb19e
+--- /dev/null
++++ b/contrib/src/fontconfig/add-initial-support-for-wasm32-emscripten.patch
+@@ -0,0 +1,105 @@
++From b7f21ca85efd78c8034223c63786a0c01b8378fe Mon Sep 17 00:00:00 2001
++From: Mehdi Sabwat <mehdi@videolabs.io>
++Date: Wed, 9 Jun 2021 03:42:51 +0200
++Subject: [PATCH 1/1] add initial support for wasm32-emscripten
++
++This commit adds a check for uuid_generate_random which is not supported for now, and fixes a failing test.
++It also handles a case where F_FSTYPENAME field is not present in statfs struct.
++---
++ configure.ac               | 1 +
++ src/Makefile.am            | 1 +
++ src/fcint.h                | 5 +++++
++ src/fcstat.c               | 2 +-
++ src/uuid_generate_random.c | 9 +++++++++
++ test/test-hash.c           | 5 ++++-
++ 6 files changed, 21 insertions(+), 2 deletions(-)
++ create mode 100644 src/uuid_generate_random.c
++
++diff --git a/configure.ac b/configure.ac
++index fb8af46..018cfc1 100644
++--- a/configure.ac
+++++ b/configure.ac
++@@ -171,2 +171,3 @@ AC_FUNC_VPRINTF
++ AC_FUNC_MMAP
++ AC_CHECK_FUNCS([link mkstemp mkostemp _mktemp_s mkdtemp getopt getopt_long getprogname getexecname rand random lrand48 random_r rand_r readlink fstatvfs fstatfs lstat strerror strerror_r]) 
+++AC_REPLACE_FUNCS([uuid_generate_random])
++
++ dnl AC_CHECK_FUNCS doesn't check for header files.
++ dnl posix_fadvise() may be not available in older libc.
++ AC_CHECK_SYMBOL([posix_fadvise], [fcntl.h], [fc_func_posix_fadvise=1], [fc_func_posix_fadvise=0])
++diff --git a/src/Makefile.am b/src/Makefile.am
++index 7b414df..de1d785 100644
++--- a/src/Makefile.am
+++++ b/src/Makefile.am
++@@ -127,6 +127,7 @@ EXTRA_DIST += \
++ 	fcobjshash.gperf.h
++ 
++ libfontconfig_la_SOURCES = \
+++	uuid_generate_random.c \
++ 	fcarch.h \
++ 	fcatomic.c \
++ 	fcatomic.h \
++diff --git a/src/fcint.h b/src/fcint.h
++index a9d075a..d8fdbfd 100644
++--- a/src/fcint.h
+++++ b/src/fcint.h
++@@ -598,6 +598,11 @@ struct _FcValuePromotionBuffer {
++ FcPrivate FcCache *
++ FcDirCacheScan (const FcChar8 *dir, FcConfig *config);
++ 
+++#ifndef HAVE_UUID_GENERATE_RANDOM
+++#include <uuid/uuid.h>
+++void uuid_generate_random(uuid_t out);
+++#endif
+++
++ FcPrivate FcCache *
++ FcDirCacheBuild (FcFontSet *set, const FcChar8 *dir, struct stat *dir_stat, FcStrSet *dirs);
++ 
++diff --git a/src/fcstat.c b/src/fcstat.c
++index 5aa1643..d1240c5 100644
++--- a/src/fcstat.c
+++++ b/src/fcstat.c
++@@ -384,7 +384,7 @@ FcFStatFs (int fd, FcStatFS *statb)
++ #  endif
++ #  if defined(HAVE_STRUCT_STATFS_F_FSTYPENAME)
++ 	p = buf.f_fstypename;
++-#  elif defined(__linux__)
+++#  elif defined(__linux__) || defined(__EMSCRIPTEN__)
++ 	switch (buf.f_type)
++ 	{
++ 	case 0x6969: /* nfs */
++diff --git a/src/uuid_generate_random.c b/src/uuid_generate_random.c
++new file mode 100644
++index 0000000..c17a58d
++--- /dev/null
+++++ b/src/uuid_generate_random.c
++@@ -0,0 +1,9 @@
+++// compat function for uuid_generate_random
+++#include "fcint.h"
+++
+++#ifndef HAVE_UUID_GENERATE_RANDOM
+++void uuid_generate_random(uuid_t out)
+++{
+++    uuid_generate(out);
+++}
+++#endif
++diff --git a/test/test-hash.c b/test/test-hash.c
++index 7530e82..221029d 100644
++--- a/test/test-hash.c
+++++ b/test/test-hash.c
++@@ -51,8 +51,11 @@ test_add (Test *test, FcChar8 *key, FcBool replace)
++     void *u;
++     FcBool (*hash_add) (FcHashTable *, void *, void *);
++     FcBool ret = FcFalse;
++-
+++#ifdef HAVE_UUID_GENERATE_RANDOM
++     uuid_generate_random (uuid);
+++#else
+++    uuid_generate(uuid);
+++#endif
++     if (replace)
++ 	hash_add = FcHashTableReplace;
++     else
++-- 
++2.32.0
++
+diff --git a/contrib/src/fontconfig/rules.mak b/contrib/src/fontconfig/rules.mak
+index 7f60dcad6c..8a72d05288 100644
+--- a/contrib/src/fontconfig/rules.mak
++++ b/contrib/src/fontconfig/rules.mak
+@@ -23,6 +23,10 @@ ifdef HAVE_WIN32
+ endif
+ 	$(RM) $(UNPACK_DIR)/src/fcobjshash.gperf
+ 	$(call pkg_static, "fontconfig.pc.in")
++ifdef HAVE_EMSCRIPTEN
++	$(APPLY) $(SRC)/fontconfig/add-initial-support-for-wasm32-emscripten.patch
++	$(UPDATE_AUTOCONFIG)
++endif
+ 	$(MOVE)
+ 
+ FONTCONFIG_CONF := $(HOSTCONF) \
+diff --git a/contrib/src/gcrypt/rules.mak b/contrib/src/gcrypt/rules.mak
+index c9c9be3efa..0275e21fe8 100644
+--- a/contrib/src/gcrypt/rules.mak
++++ b/contrib/src/gcrypt/rules.mak
+@@ -72,6 +72,12 @@ ifeq ($(ARCH),aarch64)
+ GCRYPT_CONF += --disable-arm-crypto-support
+ endif
+ endif
++ifdef HAVE_EMSCRIPTEN
++GCRYPT_CONF += --disable-asm --disable-aesni-support ac_cv_func_syslog=no --disable-sse41-support
++GCRYPT_CONF += --disable-avx-support --disable-avx2-support --disable-padlock-support
++GCRYPT_CONF += --disable-amd64-as-feature-detection --disable-drng-support
++GCRYPT_CONF += --disable-pclmul-support
++endif
+ 
+ .gcrypt: gcrypt
+ 	# Reconfiguring this requires a git repo to be available, to
+diff --git a/contrib/src/gmp/rules.mak b/contrib/src/gmp/rules.mak
+index 09604e2a89..42bb60f3fe 100644
+--- a/contrib/src/gmp/rules.mak
++++ b/contrib/src/gmp/rules.mak
+@@ -13,6 +13,9 @@ ifeq ($(ARCH),mips64el)
+ GMP_CONF += --disable-assembly
+ endif
+ endif
++ifdef HAVE_EMSCRIPTEN
++GMP_CONF += --disable-assembly
++endif
+ 
+ ifdef HAVE_WIN32
+ ifeq ($(ARCH),arm)
+diff --git a/contrib/src/gnutls/rules.mak b/contrib/src/gnutls/rules.mak
+index edec0ad9ee..fb372f9da1 100644
+--- a/contrib/src/gnutls/rules.mak
++++ b/contrib/src/gnutls/rules.mak
+@@ -82,6 +82,10 @@ ifeq ($(ARCH),aarch64)
+ endif
+ endif
+ 
++ifdef HAVE_EMSCRIPTEN
++	GNUTLS_CONF += --disable-hardware-acceleration
++endif
++
+ .gnutls: gnutls
+ 	cd $< && $(GNUTLS_ENV) ./configure $(GNUTLS_CONF)
+ 	$(call pkg_static,"lib/gnutls.pc")
+diff --git a/contrib/src/gpg-error/emscripten.patch b/contrib/src/gpg-error/emscripten.patch
+new file mode 100644
+index 0000000000..f60695c513
+--- /dev/null
++++ b/contrib/src/gpg-error/emscripten.patch
+@@ -0,0 +1,43 @@
++From 63aa1523659914acd6c84229fb31ff9b712fbf8b Mon Sep 17 00:00:00 2001
++From: Mehdi Sabwat <mehdi@videolabs.io>
++Date: Wed, 2 Jun 2021 11:42:46 +0200
++Subject: [PATCH 1/1] emscripten
++
++---
++ .../lock-obj-pub.wasm32-unknown-emscripten.h  | 24 +++++++++++++++++++
++ 1 file changed, 24 insertions(+)
++ create mode 100644 src/syscfg/lock-obj-pub.wasm32-unknown-emscripten.h
++
++diff --git a/src/syscfg/lock-obj-pub.wasm32-unknown-emscripten.h b/src/syscfg/lock-obj-pub.wasm32-unknown-emscripten.h
++new file mode 100644
++index 0000000..1651518
++--- /dev/null
+++++ b/src/syscfg/lock-obj-pub.wasm32-unknown-emscripten.h
++@@ -0,0 +1,24 @@
+++## lock-obj-pub.wasm32-unknown-emscripten.h
+++## File created by gen-posix-lock-obj - DO NOT EDIT
+++## To be included by mkheader into gpg-error.h
+++
+++typedef struct
+++{
+++  long _vers;
+++  union {
+++    volatile char _priv[28];
+++    long _x_align;
+++    long *_xp_align;
+++  } u;
+++} gpgrt_lock_t;
+++
+++#define GPGRT_LOCK_INITIALIZER {1,{{0,0,0,0,0,0,0,0, \
+++                                    0,0,0,0,0,0,0,0, \
+++                                    0,0,0,0,0,0,0,0, \
+++                                    0,0,0,0}}}
+++##
+++## Local Variables:
+++## mode: c
+++## buffer-read-only: t
+++## End:
+++##
++-- 
++2.31.1
++
+diff --git a/contrib/src/gpg-error/rules.mak b/contrib/src/gpg-error/rules.mak
+index 0ceb75d7a3..5d4236b77f 100644
+--- a/contrib/src/gpg-error/rules.mak
++++ b/contrib/src/gpg-error/rules.mak
+@@ -25,6 +25,7 @@ endif
+ 	$(APPLY) $(SRC)/gpg-error/version-bump-gawk-5.patch
+ 	$(APPLY) $(SRC)/gpg-error/win32-extern-struct.patch
+ 	$(APPLY) $(SRC)/gpg-error/darwin-triplet.patch
++	$(APPLY) $(SRC)/gpg-error/emscripten.patch
+ ifndef HAVE_WIN32
+ 	cp -f -- "$(SRC)/gpg-error/lock-obj-pub.posix.h" \
+ 		"$(UNPACK_DIR)/src/lock-obj-pub.native.h"
+diff --git a/contrib/src/png/rules.mak b/contrib/src/png/rules.mak
+index 7a8db11d5a..f1c93b835b 100644
+--- a/contrib/src/png/rules.mak
++++ b/contrib/src/png/rules.mak
+@@ -7,6 +7,10 @@ ifeq ($(call need_pkg,"libpng >= 1.5.4"),)
+ PKGS_FOUND += png
+ endif
+ 
++ifdef HAVE_EMSCRIPTEN
++HOSTVARS += CFLAGS="$(CFLAGS) -pthread"
++endif
++
+ $(TARBALLS)/libpng-$(PNG_VERSION).tar.xz:
+ 	$(call download_pkg,$(PNG_URL),png)
+ 
+diff --git a/contrib/src/zlib/rules.mak b/contrib/src/zlib/rules.mak
+index 17688df984..e3ae815dfc 100644
+--- a/contrib/src/zlib/rules.mak
++++ b/contrib/src/zlib/rules.mak
+@@ -7,6 +7,10 @@ ifeq ($(call need_pkg,"zlib"),)
+ PKGS_FOUND += zlib
+ endif
+ 
++ifdef HAVE_EMSCRIPTEN
++CFLAGS+="-pthread"
++endif
++
+ ifeq ($(shell uname),Darwin) # zlib tries to use libtool on Darwin
+ ifdef HAVE_CROSS_COMPILE
+ ZLIB_CONFIG_VARS=CHOST=$(HOST)
+diff --git a/extras/package/wasm-emscripten/build.sh b/extras/package/wasm-emscripten/build.sh
+index 69d6b98c47..acd74591fc 100755
+--- a/extras/package/wasm-emscripten/build.sh
++++ b/extras/package/wasm-emscripten/build.sh
+@@ -164,6 +164,11 @@ if [ $BUILD_MODE -eq 1 ]; then
+                         --disable-sout --disable-vlm --disable-a52 --disable-xcb --disable-lua \
+                         --disable-addonmanagermodules --disable-ssp --disable-nls \
+                         --enable-gles2 \
++                        ac_cv_func_if_nameindex=yes ac_cv_header_sys_shm_h=no \
++                        ac_cv_func_accept4=no \
++                        ac_cv_func_pipe2=no \
++                        --enable-gles2 \
++                        --disable-nvdec \
+                         --with-contrib="$VLC_SRCPATH"/contrib/wasm32-unknown-emscripten
+ fi
+ 
+diff --git a/include/vlc_codec.h b/include/vlc_codec.h
+index f68da3bde4..baa6977bbd 100644
+--- a/include/vlc_codec.h
++++ b/include/vlc_codec.h
+@@ -583,6 +583,7 @@ enum vlc_decoder_device_type
+     VLC_DECODER_DEVICE_AWINDOW,
+     VLC_DECODER_DEVICE_NVDEC,
+     VLC_DECODER_DEVICE_MMAL,
++    VLC_DECODER_DEVICE_WEBCODEC,
+ };
+ 
+ struct vlc_decoder_device_operations
+diff --git a/include/vlc_fourcc.h b/include/vlc_fourcc.h
+index 6ed227d2c6..10f82ff535 100644
+--- a/include/vlc_fourcc.h
++++ b/include/vlc_fourcc.h
+@@ -439,6 +439,9 @@
+ #define VLC_CODEC_CVPX_BGRA       VLC_FOURCC('C','V','P','B')
+ #define VLC_CODEC_CVPX_P010       VLC_FOURCC('C','V','P','P')
+ 
++/* Webcodec opaque VideoFrame types */
++#define VLC_CODEC_WEBCODEC_OPAQUE VLC_FOURCC('W','C','O','P')
++
+ /* Image codec (video) */
+ #define VLC_CODEC_PNG             VLC_FOURCC('p','n','g',' ')
+ #define VLC_CODEC_PPM             VLC_FOURCC('p','p','m',' ')
+diff --git a/include/vlc_opengl_filter.h b/include/vlc_opengl_filter.h
+new file mode 100644
+index 0000000000..3a1f18197d
+--- /dev/null
++++ b/include/vlc_opengl_filter.h
+@@ -0,0 +1,157 @@
++/*****************************************************************************
++ * vlc_opengl_filter.h
++ *****************************************************************************
++ * Copyright (C) 2020 VLC authors and VideoLAN
++ * Copyright (C) 2020 Videolabs
++ *
++ * 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.
++ *****************************************************************************/
++
++#ifndef VLC_OPENGL_FILTER_H
++#define VLC_OPENGL_FILTER_H
++
++#include <vlc_tick.h>
++
++struct vlc_gl_filter;
++struct vlc_gl_picture;
++struct vlc_gl_format;
++
++#ifdef __cplusplus
++extern "C"
++{
++#endif
++
++struct vlc_gl_tex_size {
++    unsigned width;
++    unsigned height;
++};
++
++struct vlc_gl_input_meta {
++    vlc_tick_t pts;
++    unsigned plane;
++};
++
++typedef int
++vlc_gl_filter_open_fn(struct vlc_gl_filter *filter,
++                      const config_chain_t *config,
++                      const struct vlc_gl_format *glfmt,
++                      struct vlc_gl_tex_size *size_out);
++
++#define set_callback_opengl_filter(open) \
++    { \
++        vlc_gl_filter_open_fn *fn = open; \
++        (void) fn; \
++        set_callback(fn); \
++    }
++
++struct vlc_gl_filter_ops {
++    /**
++     * Draw the result of the filter to the current framebuffer
++     */
++    int (*draw)(struct vlc_gl_filter *filter, const struct vlc_gl_picture *pic,
++                const struct vlc_gl_input_meta *meta);
++
++    /**
++     * Free filter resources
++     */
++    void (*close)(struct vlc_gl_filter *filter);
++
++    /**
++     * Request a (responsive) filter to adapt its output size (optional)
++     *
++     * A responsive filter is a filter for which the size of the produced
++     * pictures depends on the output (e.g. display) size rather than the
++     * input. This is for example the case for a renderer.
++     *
++     * A new output size is requested (size_out). The filter is authorized to
++     * change the size_out to enforce its own constraints.
++     *
++     * In addition, it may request to the previous filter (if any) an optimal
++     * size it wants to receive. If set to non-zero value, this previous filter
++     * will receive this size as its requested size (and so on).
++     *
++     * \retval true if the resize is accepted (possibly with a modified
++     *              size_out)
++     * \retval false if the resize is rejected (included on error)
++     */
++    int (*request_output_size)(struct vlc_gl_filter *filter,
++                               struct vlc_gl_tex_size *size_out,
++                               struct vlc_gl_tex_size *optimal_in);
++
++    /**
++     * Callback to notify input size changes
++     *
++     * When a filter changes its output size as a result of
++     * request_output_size(), the next filter is notified by this callback.
++     */
++    void (*on_input_size_change)(struct vlc_gl_filter *filter,
++                                 const struct vlc_gl_tex_size *size);
++};
++
++/**
++ * OpenGL filter, in charge of a rendering pass.
++ */
++struct vlc_gl_filter {
++    vlc_object_t obj;
++    module_t *module;
++
++    struct vlc_gl_t *gl;
++    const struct vlc_gl_api *api;
++    const struct vlc_gl_format *glfmt_in;
++
++    struct {
++        /**
++         * An OpenGL filter may either operate on the input RGBA picture, or on
++         * individual input planes (without chroma conversion) separately.
++         *
++         * In practice, this is useful for deinterlace filters.
++         *
++         * This flag must be set by the filter module (default is false).
++         */
++        bool filter_planes;
++
++        /**
++         * A blend filter draws over the input picture (without reading it).
++         *
++         * Meaningless if filter_planes is true.
++         *
++         * This flag must be set by the filter module (default is false).
++         */
++        bool blend;
++
++        /**
++         * Request MSAA level.
++         *
++         * This value must be set by the filter module (default is 0, which
++         * means disabled).
++         *
++         * Meaningless if filter_planes is true.
++         *
++         * The actual MSAA level may be overwritten to 0 if multisampling is
++         * not supported, or to a higher value if another filter rendering on
++         * the same framebuffer requested a higher MSAA level.
++         */
++        unsigned msaa_level;
++    } config;
++
++    const struct vlc_gl_filter_ops *ops;
++    void *sys;
++};
++
++#ifdef __cplusplus
++}
++#endif
++
++#endif
+diff --git a/include/vlc_picture.h b/include/vlc_picture.h
+index c37abed150..fc3b757d14 100644
+--- a/include/vlc_picture.h
++++ b/include/vlc_picture.h
+@@ -98,6 +98,7 @@ enum vlc_video_context_type
+     VLC_VIDEO_CONTEXT_NVDEC,     //!< empty
+     VLC_VIDEO_CONTEXT_CVPX,      //!< private: cvpx_video_context*
+     VLC_VIDEO_CONTEXT_MMAL,      //!< empty
++    VLC_VIDEO_CONTEXT_WEBCODEC,
+ };
+ 
+ VLC_API vlc_video_context * vlc_video_context_Create(vlc_decoder_device *,
+diff --git a/include/vlc_vout_display.h b/include/vlc_vout_display.h
+index 4220793ae0..1b2f79b3b4 100644
+--- a/include/vlc_vout_display.h
++++ b/include/vlc_vout_display.h
+@@ -383,6 +383,8 @@ vout_display_t *vout_display_New(vlc_object_t *,
+     const video_format_t *, vlc_video_context *,
+     const vout_display_cfg_t *, const char *module,
+     const vout_display_owner_t *);
++#define vout_display_New(O, ...) \
++    (vout_display_New)(VLC_OBJECT(O), __VA_ARGS__)
+ 
+ /**
+  * Destroys a video output display.
+diff --git a/include/vlc_window.h b/include/vlc_window.h
+index 9c8596ddaf..2124cdd028 100644
+--- a/include/vlc_window.h
++++ b/include/vlc_window.h
+@@ -65,6 +65,7 @@ enum vlc_window_type {
+     VLC_WINDOW_TYPE_WAYLAND /**< Wayland surface */,
+     VLC_WINDOW_TYPE_DCOMP /**< Win32 DirectComposition */,
+     VLC_WINDOW_TYPE_KMS /**< DRM KMS CRTC */,
++    VLC_WINDOW_TYPE_EMSCRIPTEN_WEBGL /**< Emscripten surface */,
+ };
+ 
+ /**
+@@ -385,6 +386,8 @@ typedef struct vlc_window {
+         struct wl_surface *wl;   /**< Wayland surface (client pointer) */
+         void     *dcomp_visual;  /**<  Win32 direct composition visual */
+         uint32_t crtc;           /**< KMS CRTC identifier */
++        uint32_t em_context;     /* Emscripten webgl context */
++        const char *canvas;
+     } handle;
+ 
+     /** Display server (mandatory)
+diff --git a/modules/Makefile.am b/modules/Makefile.am
+index 333acb0586..0c931d51ca 100644
+--- a/modules/Makefile.am
++++ b/modules/Makefile.am
+@@ -31,6 +31,7 @@ include hw/d3d11/Makefile.am
+ include hw/vaapi/Makefile.am
+ include hw/vdpau/Makefile.am
+ include hw/mmal/Makefile.am
++include hw/emscripten/Makefile.am
+ include isa/aarch64/Makefile.am
+ include isa/arm/Makefile.am
+ include isa/riscv/Makefile.am
+diff --git a/modules/audio_output/Makefile.am b/modules/audio_output/Makefile.am
+index 38e47bc838..290b724983 100644
+--- a/modules/audio_output/Makefile.am
++++ b/modules/audio_output/Makefile.am
+@@ -116,3 +116,8 @@ endif
+ if HAVE_TVOS
+ aout_LTLIBRARIES += libaudiounit_ios_plugin.la
+ endif
++
++libemworklet_audio_plugin_la_SOURCES = audio_output/emscripten.cpp
++if HAVE_EMSCRIPTEN
++aout_LTLIBRARIES += libemworklet_audio_plugin.la
++endif
+diff --git a/modules/audio_output/emscripten.cpp b/modules/audio_output/emscripten.cpp
+new file mode 100644
+index 0000000000..a7e9fb0d4d
+--- /dev/null
++++ b/modules/audio_output/emscripten.cpp
+@@ -0,0 +1,421 @@
++/*****************************************************************************
++ * emscripten.c: audio output module using audio worklets
++ *****************************************************************************
++ * Copyright © 2020 VLC authors and VideoLAN
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 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 General Public License for more details.
++ *
++ * You should have received a copy of the GNU 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 <atomic>
++
++#include <assert.h>
++#include <vlc_common.h>
++#include <vlc_plugin.h>
++#include <vlc_aout.h>
++
++#include <emscripten.h>
++#include <emscripten/val.h>
++#include <emscripten/bind.h>
++#include <emscripten/html5.h>
++
++#include <cstdint>
++#include <stdlib.h>
++
++#define STORAGE_SIZE 1024 * 1024
++// Sample rate might change, and it would be good to be able to change it during playback.
++#define AUDIO_WORKLET_SAMPLE_RATE 44100
++// Don't know any way to get the browser's supported number of channels.
++#define AUDIO_WORKLET_NB_CHANNELS 2
++
++using namespace emscripten;
++namespace {
++	EM_BOOL requestAnimationFrame_cb( double time, void *userData );
++
++	typedef struct sound_buffer_t
++	{
++    // TODO - should be bool?
++    std::atomic<uint32_t> is_paused;
++    std::atomic<uint32_t> head;
++    std::atomic<uint32_t> tail;
++    std::atomic<uint32_t> can_write;
++    std::atomic<uint32_t> volume;
++    std::atomic<uint32_t> is_muted;
++    int8_t storage[STORAGE_SIZE];
++
++	} sound_buffer_t;
++
++	class AWNodeWrapper {
++	public:
++		val context = val::undefined();
++		val getCtx() const { return context; };
++		void setCtx(val v_context) { context = v_context; };
++
++		uintptr_t sab_ptr;
++		uintptr_t getSabPtr() const { return sab_ptr; };
++		void setSabPtr(uintptr_t p_sab) { sab_ptr = p_sab; };
++
++		int8_t channels;
++		int8_t getChannels() const { return channels; };
++		void setChannels(int8_t chan) { channels = chan; };
++
++		AWNodeWrapper(int sample_rate) {
++			// Prepare audio context options
++			val audio_ctx_options = val::object();
++			audio_ctx_options.set("sampleRate", sample_rate);
++
++			context = val::global("AudioContext").new_(audio_ctx_options);
++			context.call<void>("suspend");
++		}
++
++		val operator()( val undefined_promise_argument ) {
++			(val)undefined_promise_argument;
++
++			// Prepare AWN Options
++			val awn_options = val::object();
++			val awn_opt_outputChannelCount = val::array();
++			awn_opt_outputChannelCount.call<val>("push", channels);
++			awn_options.set("outputChannelCount", awn_opt_outputChannelCount);
++			awn_options.set("numberOfInputs", 0);
++			awn_options.set("numberOfOutputs", 1);
++
++			val AudioNode = val::global("AudioWorkletNode").new_(context, std::string("worklet-processor"), awn_options);
++			AudioNode.set("channelCount", channels);
++
++      val Uint32Array = val::global("Uint32Array");
++      val Int32Array = val::global("Int32Array");
++      val Float32Array = val::global("Float32Array");
++
++      auto wasm_mem = val::module_property("wasmMemory")["buffer"];
++
++			//Prepare postMessage message
++			val msg = val::object();
++			msg.set("type", std::string("recv-audio-queue"));
++
++      msg.set("is_paused",
++        Uint32Array.new_(wasm_mem, sab_ptr + offsetof(sound_buffer_t, is_paused), 1));
++      msg.set("head",
++        Uint32Array.new_(wasm_mem, sab_ptr + offsetof(sound_buffer_t, head), 1));
++      msg.set("tail",
++        Uint32Array.new_(wasm_mem, sab_ptr + offsetof(sound_buffer_t, tail), 1));
++      msg.set("can_write",
++        Int32Array.new_(wasm_mem, sab_ptr + offsetof(sound_buffer_t, can_write), 1));
++      msg.set("volume",
++        Int32Array.new_(wasm_mem, sab_ptr + offsetof(sound_buffer_t, volume), 1));
++      msg.set("is_muted",
++        Uint32Array.new_(wasm_mem, sab_ptr + offsetof(sound_buffer_t, is_muted), 1));
++
++      uint32_t storage_capacity = STORAGE_SIZE / 4;
++      msg.set("storage",
++        Float32Array.new_(wasm_mem, sab_ptr + offsetof(sound_buffer_t, storage), storage_capacity));
++
++			AudioNode["port"].call<val>("postMessage", msg);
++			AudioNode.call<val>("connect", context["destination"]);
++
++			emscripten_request_animation_frame_loop(requestAnimationFrame_cb, this);
++
++			return val::undefined();
++		}
++	};
++
++	EMSCRIPTEN_BINDINGS(AWWSCOPE) {
++		class_<AWNodeWrapper>("awn_cb_wrapper")
++			.constructor<int>()
++			.property("context", &AWNodeWrapper::getCtx, &AWNodeWrapper::setCtx)
++			.property("sab_ptr", &AWNodeWrapper::getSabPtr, &AWNodeWrapper::setSabPtr)
++			.property("channels", &AWNodeWrapper::getChannels, &AWNodeWrapper::setChannels)
++			.function("awn_call", &AWNodeWrapper::operator());
++	};
++
++	typedef struct aout_sys_t
++	{
++		sound_buffer_t *sab; // TODO - rename to sound_buff
++		AWNodeWrapper *awn_inst;
++
++	} aout_sys_t;
++
++	EM_BOOL requestAnimationFrame_cb( double time, void *userData ) {
++		VLC_UNUSED(time);
++    // FIXME - this function seems to mix two different views on the
++    // same memory, not sure why
++		AWNodeWrapper *inst = reinterpret_cast<AWNodeWrapper *>(userData);
++		uint32_t *sab = reinterpret_cast<uint32_t *>(inst->getSabPtr());
++		val view = val(typed_memory_view(sizeof(sound_buffer_t), sab));
++		val context = inst->getCtx();
++		if ( view[0].as<int>() == 1 ) {
++			context.call<val>("resume");
++			sab[0] = 0;
++			return EM_FALSE;
++		}
++		return EM_TRUE;
++	}
++
++	// careful when calling this, you cannot wait on any index
++	// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait
++	void js_index_wait(sound_buffer_t *sab_ptr, int8_t index) {
++		int32_t *buffer_view = reinterpret_cast<int32_t *>(sab_ptr);
++		val buffer = val(typed_memory_view(STORAGE_SIZE, buffer_view));
++
++		val::global("Atomics").call<val>("wait", buffer, index, 0);
++	}
++
++	void Flush( audio_output_t *aout )
++	{
++		aout_sys_t * sys = reinterpret_cast<aout_sys_t *>(aout->sys);
++		bzero(&sys->sab->storage, sizeof(sys->sab->storage));
++	}
++
++	int Start( audio_output_t *aout, audio_sample_format_t *restrict fmt )
++	{
++		aout_sys_t *sys = reinterpret_cast<aout_sys_t *>(aout->sys);
++		unsigned nbChannels = aout_FormatNbChannels(fmt);
++
++		if (( nbChannels == 0 ) || !AOUT_FMT_LINEAR(fmt))
++			return VLC_EGENERIC;
++		fmt->i_format = VLC_CODEC_FL32;
++		fmt->i_channels = AUDIO_WORKLET_NB_CHANNELS;
++		fmt->i_rate = AUDIO_WORKLET_SAMPLE_RATE;
++
++		// resume audio context (first start, it is paused when initialized)
++		sys->sab->is_paused.store(1);
++
++		return VLC_SUCCESS;
++	}
++
++	void Stop (audio_output_t *aout)
++	{
++		Flush(aout);
++	}
++
++	int audio_worklet_push (audio_output_t *aout, const int8_t *data, uint32_t data_size) {
++		aout_sys_t *sys = reinterpret_cast<aout_sys_t *>(aout->sys);
++		int8_t *sab_view = sys->sab->storage;
++		uint32_t head = sys->sab->head.load();
++
++		// TODO: check that we do not write on unconsumed data.
++		if (head + data_size > STORAGE_SIZE) {
++			// Copy the part of the data at the buffer end
++			unsigned data_size_copy_end = STORAGE_SIZE - head;
++			memcpy(sab_view + head, data, data_size_copy_end);
++			head = 0;
++
++			// Copy the part of the data at the buffer start
++			unsigned data_size_copy_start = data_size - data_size_copy_end;
++			memcpy(sab_view + head, data, data_size_copy_start);
++			head = data_size_copy_start;
++		}
++		else {
++			memcpy(sab_view + head, data, data_size);
++			head += data_size;
++		}
++		sys->sab->head.store(head);
++		return 0;  // return success to indicate successful push.
++	}
++
++	void Play( audio_output_t *aout, block_t *block, vlc_tick_t date)
++	{
++		VLC_UNUSED(date);
++		aout_sys_t *sys = reinterpret_cast<aout_sys_t *>(aout->sys);
++		const int8_t* data = (int8_t *)block->p_buffer;
++		size_t data_size = block->i_buffer;
++
++		uint32_t head = sys->sab->head.load();
++		uint32_t tail = sys->sab->tail.load();
++		uint32_t new_head = (head + data_size) % STORAGE_SIZE;
++		if (new_head > tail)
++		{
++			// the worklet processor keeps rendering  until tail matches head
++			// it will be notified by an Atomics.notify() from the process() callback
++			// FIXME - This is layout-dependent, which isn't ideal
++			js_index_wait(sys->sab, 3);
++		}
++
++		audio_worklet_push(aout, data, data_size);
++		block_Release(block);
++	}
++
++	void Pause( audio_output_t *aout, bool paused, vlc_tick_t date )
++	{
++		VLC_UNUSED(date);
++		aout_sys_t * sys = reinterpret_cast<aout_sys_t *>(aout->sys);
++		if (paused == false) {
++			sys->sab->is_paused.store(0);
++		}
++		else {
++			sys->sab->is_paused.store(1);
++		}
++		Flush(aout);
++	}
++
++	int Time_Get( audio_output_t *aout, vlc_tick_t *delay)
++	{
++		return aout_TimeGetDefault(aout, delay);
++	}
++
++	void Close( vlc_object_t *obj )
++	{
++		audio_output_t *aout = (audio_output_t *)obj;
++		struct aout_sys_t *sys = reinterpret_cast<struct aout_sys_t *>(aout->sys);
++
++    // FIXME
++		delete sys->awn_inst;
++		free(sys->sab);
++		free(sys);
++	}
++
++	int Volume_Set( audio_output_t *aout, float volume)
++	{
++		struct aout_sys_t *sys = reinterpret_cast<struct aout_sys_t *>(aout->sys);
++
++		if (volume > 1.0f)
++			volume = 1.0f;
++		else if (volume < 0.0f)
++			volume = 0.0f;
++		// TODO: implement gain
++		// Note: We store volume as an integer between 0..100 because
++		// for some reason Float32Array doesn't allow atomic operations
++		sys->sab->volume.store((int)(volume * 100));
++		aout_VolumeReport(aout, volume);
++
++		return 0;
++	}
++
++	int Mute_Set( audio_output_t *aout, bool mute)
++	{
++		struct aout_sys_t *sys = reinterpret_cast<struct aout_sys_t *>(aout->sys);
++
++		sys->sab->is_muted.store(mute);
++		aout_MuteReport(aout, mute);
++
++		return 0;
++	}
++
++
++	int Open( vlc_object_t *obj )
++	{
++		audio_output_t * aout = (audio_output_t *) obj;
++
++		/* Allocate structures */
++		aout_sys_t *sys = reinterpret_cast<aout_sys_t *>(malloc( sizeof( *sys ) ));
++		if( unlikely(sys == NULL) )
++			return VLC_ENOMEM;
++
++		aout->sys = sys;
++		aout->start = Start;
++		aout->stop = Stop;
++		aout->play = Play;
++		aout->pause = Pause;
++		aout->flush = Flush;
++		aout->time_get = Time_Get;
++		aout->volume_set = Volume_Set;
++		aout->mute_set = Mute_Set;
++
++		sys->awn_inst = new AWNodeWrapper(AUDIO_WORKLET_SAMPLE_RATE);
++		sys->sab = (sound_buffer_t*)malloc(sizeof(sound_buffer_t));
++
++		if ( unlikely(sys->sab == NULL) )
++			return VLC_ENOMEM;
++		bzero(sys->sab, sizeof(sound_buffer_t));
++		sys->sab->volume = 100;
++
++		val webaudio_context = sys->awn_inst->getCtx();
++
++		// Prepare audioWorkletProcessor blob
++		val document = val::global("document");
++		val script = document.call<val>("createElement", std::string("SCRIPT"));
++		script.set("type", std::string("worklet"));
++		std::string processorStr = "class Processor extends AudioWorkletProcessor { \
++	constructor() { \
++		super(); \
++		this.port.onmessage = e => { \
++			if (e.data.type === 'recv-audio-queue') { \
++				this.is_paused = e.data.is_paused; \
++				this.head = e.data.head; \
++				this.tail = e.data.tail; \
++				this.can_write = e.data.can_write; \
++				this.volume = e.data.volume; \
++				this.is_muted = e.data.is_muted; \
++				this.storage = e.data.storage; \
++			} else { \
++				throw 'unexpected.'; \
++			} \
++		}; \
++	} \
++	process(inputs, outputs, parameters) { \
++		const output = outputs[0]; \
++		const nbChannels = output.length; \
++		const nbSamples = output[0].length; \
++		if (this.head.buffer.byteLength == 0) { \
++			throw new Error('wasmMemory grew'); \
++		} \
++		var head = Atomics.load(this.head, 0) / 4; \
++		var tail = Atomics.load(this.tail, 0) / 4; \
++		var i = 0; \
++		var volume = Atomics.load(this.volume, 0) / 100; \
++		if (Atomics.load(this.is_paused, 0) != 0 || Atomics.load(this.is_muted, 0) != 0) { \
++			volume = 0; \
++		} \
++		while (tail != head && i < nbSamples) \
++		{ \
++			for (let c = 0; c < nbChannels; ++c) { \
++				output[c][i] = this.storage[tail] * volume; \
++				tail++; \
++				if (tail == this.storage.length) { \
++					tail = 0; \
++				} \
++			} \
++			i++; \
++		} \
++		Atomics.store(this.tail, 0, tail * 4); \
++		Atomics.store(this.can_write, 0, 1); \
++		Atomics.notify(this.can_write, 0);   \
++		return true; \
++	} \
++} \
++registerProcessor('worklet-processor', Processor);";
++		script.set("innerText", processorStr);
++		val ProcessorTextArray = val::array();
++		ProcessorTextArray.call<val>("push", script["innerText"]);
++		val BlobObject = val::object();
++		BlobObject.set("type", std::string("application/javascript"));
++		val WorkletModuleUrl = val::global("URL").call<val>("createObjectURL", val::global("Blob").new_(ProcessorTextArray, BlobObject));
++
++		// Prepare audioWorkletProcessor callback
++		val cb_caller = val::module_property("awn_cb_wrapper").new_(AUDIO_WORKLET_SAMPLE_RATE);
++		cb_caller.set("context", val(webaudio_context));
++		cb_caller.set("sab_ptr", val(reinterpret_cast<uintptr_t>(sys->sab)));
++		cb_caller.set("channels", val(AUDIO_WORKLET_NB_CHANNELS));
++		val awn_caller = cb_caller["awn_call"];
++		val awn_cb = awn_caller.call<val>("bind", cb_caller);
++
++		// start audio worklet (since the context is suspended, sound won't start now
++		// Since the WebAudio Context cannot be created in a worker, we create
++		// it in the main_thread and use the SAB to signal it when we want it to start
++		webaudio_context["audioWorklet"].call<val>("addModule", WorkletModuleUrl).call<val>("then", awn_cb);
++
++		return VLC_SUCCESS;
++	}
++}
++
++vlc_module_begin ()
++	set_description( N_("Emscripten Worklet audio output") )
++	set_shortname( "emworklet" )
++	set_capability( "audio output", 100 )
++	set_subcategory( SUBCAT_AUDIO_AOUT )
++	set_callbacks( Open, Close )
++vlc_module_end ()
+diff --git a/modules/codec/Makefile.am b/modules/codec/Makefile.am
+index 20a9606fd8..13ec8fa952 100644
+--- a/modules/codec/Makefile.am
++++ b/modules/codec/Makefile.am
+@@ -644,3 +644,9 @@ noinst_LTLIBRARIES += libhxxxhelper_testdec_plugin.la
+ libvlc_vtutils_la_SOURCES = codec/vt_utils.c codec/vt_utils.h
+ libvlc_vtutils_la_LDFLAGS = -static -no-undefined
+ EXTRA_LTLIBRARIES += libvlc_vtutils.la
++
++libwebcodec_plugin_la_SOURCES = codec/webcodec.cpp video_output/emscripten/common.cpp
++libwebcodec_plugin_la_LDFLAGS = $(AM_LDFLAGS)
++if HAVE_EMSCRIPTEN
++codec_LTLIBRARIES += libwebcodec_plugin.la
++endif
+diff --git a/modules/codec/webcodec.cpp b/modules/codec/webcodec.cpp
+new file mode 100644
+index 0000000000..4d1fa5d932
+--- /dev/null
++++ b/modules/codec/webcodec.cpp
+@@ -0,0 +1,518 @@
++/*****************************************************************************
++ * webcodec.cpp: Decoder module using browser provided codec implementations
++ *****************************************************************************
++ * Copyright © 2021 VLC authors and VideoLAN
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 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 General Public License for more details.
++ *
++ * You should have received a copy of the GNU 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 <vlc_common.h>
++#include <vlc_plugin.h>
++#include <vlc_codec.h>
++#include <vlc_threads.h>
++#include <vlc_cxx_helpers.hpp>
++#include <vlc_block.h>
++#include <vlc_tick.h>
++#include <vlc_picture.h>
++
++#include "../video_output/emscripten/common.h"
++
++#include <emscripten/emscripten.h>
++#include <emscripten/val.h>
++#include <emscripten/bind.h>
++#include <emscripten/em_js.h>
++#include <emscripten/wire.h>
++#include <memory>
++#include <functional>
++#include <cstdint>
++#include <queue>
++
++using emval = emscripten::val;
++
++struct decoder_sys_t
++{
++    vlc_thread_t th;
++    std::queue<block_t*> blocks;
++    vlc::threads::mutex mutex;
++
++    vlc_video_context* vctx;
++};
++
++extern "C"
++{
++EMSCRIPTEN_KEEPALIVE picture_t* tryGetPictureFromPool(decoder_t* dec)
++{
++    auto sys = static_cast<decoder_sys_t*>( dec->p_sys );
++    auto vctx = static_cast<webcodec_context*>(
++                vlc_video_context_GetPrivate(sys->vctx, VLC_VIDEO_CONTEXT_WEBCODEC) );
++    return picture_pool_Get(vctx->pool);
++}
++
++EMSCRIPTEN_KEEPALIVE bool updateVideoOutput(decoder_t* dec)
++{
++    auto sys = static_cast<decoder_sys_t*>( dec->p_sys );
++    if ( decoder_UpdateVideoOutput( dec, sys->vctx ) )
++    {
++        msg_Err( dec, "Failure during UpdateVideoOutput! FIXME" );
++        return false;
++    }
++    return true;
++}
++
++EMSCRIPTEN_KEEPALIVE int32_t queuePicture(void* ctx, picture_t* pic, int64_t timestamp)
++{
++    auto dec = static_cast<decoder_t*>(ctx);
++    pic->date = VLC_TICK_FROM_US(timestamp);
++    pic->b_progressive = true;
++    decoder_QueueVideo(dec, pic);
++    return PictureContextPrivate(pic->context)->pictureIdx;
++}
++
++EMSCRIPTEN_KEEPALIVE pthread_t getVlcDecoderWorkerThread(decoder_t* dec)
++{
++    auto sys = static_cast<decoder_sys_t*>( dec->p_sys );
++    return sys->th.handle;
++}
++
++EMSCRIPTEN_KEEPALIVE void releaseBlock( block_t* block )
++{
++    block_Release( block );
++}
++
++EMSCRIPTEN_KEEPALIVE void decodeBlock( block_t* block )
++{
++    auto chunkType = emval::global("EncodedVideoChunk");
++    auto chunkCfg = emval::object();
++
++    static bool first = true;
++
++    if ( first == true )
++    {
++        chunkCfg.set( "type", "key" );
++        first = false;
++    }
++    else
++        chunkCfg.set( "type", (block->i_flags & BLOCK_FLAG_TYPE_I) ? "key" : "delta" );
++    auto timestamp = block->i_pts ? block->i_pts : block->i_dts;
++    chunkCfg.set( "timestamp", (long int)US_FROM_VLC_TICK( timestamp ) );
++    //if ( block->i_length > 0 )
++    //    chunkCfg.set( "duration", (long int)US_FROM_VLC_TICK( block->i_length ) );
++    chunkCfg.set( "data", emscripten::typed_memory_view( block->i_buffer, block->p_buffer ) );
++    auto chunk = chunkType.new_( std::move( chunkCfg ) );
++
++    /* We can't return a javascript object from the stack back to the JS caller
++     * as the object would be destroyed when falling out of scope.
++     * We can't also pass an object by reference as the only way to get a
++     * emscripten::val instance is to take ownership from the javascript object
++     * so we couldn't use it back in the JS
++     * As a work around, we access the JS decoder instance straight from the C++ code
++     */
++    EM_ASM({Module.decoder.decode( Emval.toValue( $0 ) ); }, chunk.as_handle());
++}
++
++}
++
++EM_ASYNC_JS(bool, probeConfig, (emscripten::EM_VAL cfg), {
++    var decoderCfg = Emval.toValue(cfg);
++    var res = await VideoDecoder.isConfigSupported(decoderCfg).catch((err) => {
++        console.log(err);
++        return {'supported': false};
++    });
++    return res['supported'];
++});
++
++static emval getDecoderConfig( decoder_t* dec, bool includeExtraData )
++{
++    auto decoderConfig = emval::object();
++    switch( dec->fmt_in.i_codec )
++    {
++    case VLC_CODEC_VP8:
++        decoderConfig.set( "codec", "vp8" );
++        break;
++    case VLC_CODEC_VP9:
++        decoderConfig.set( "codec", "vp09.*" );
++        break;
++    case VLC_CODEC_H264:
++    {
++        char codec[12];
++        snprintf(codec, sizeof(codec), "avc1.%.2X%.2X%.2X", dec->fmt_in.i_profile,
++                 0, dec->fmt_in.i_level);
++        decoderConfig.set( "codec", codec );
++
++        //decoderConfig.set("codec", "avc1.64000b" );
++        break;
++    }
++    case VLC_CODEC_AV1:
++        decoderConfig.set( "codec", "av01" );
++        break;
++    default:
++        return emval::undefined();
++    }
++    decoderConfig.set( "codedWidth", dec->fmt_in.video.i_width );
++    decoderConfig.set( "codedHeight", dec->fmt_in.video.i_height );
++    decoderConfig.set( "displayAspectWidth", dec->fmt_in.video.i_visible_width );
++    decoderConfig.set( "displayAspectHeight", dec->fmt_in.video.i_visible_height );
++    decoderConfig.set( "optimizeForLatency", false );
++    if ( includeExtraData )
++    {
++        if ( dec->fmt_in.i_extra > 0 )
++        {
++            decoderConfig.set( "description",
++                               emscripten::typed_memory_view(
++                                    dec->fmt_in.i_extra,
++                                   static_cast<uint8_t*>( dec->fmt_in.p_extra ) )
++                               );
++        }
++    }
++    return decoderConfig;
++}
++
++EM_JS(emscripten::EM_VAL, initDecoderJS, (void* decoder, emscripten::EM_VAL decCfgHandle), {
++    function onInteropMessage(msg) {
++        let data = msg['data'];
++        if (data['customCmd'] == 'sendFrame') {
++            let picIdx = data['pictureIdx'];
++            let frame = Module.framesReady[picIdx];
++            Module.framesReady[picIdx] = undefined;
++            if (!frame) {
++                /* We may receive a late message after a flush, in which case
++                 * we still need to reply to the interop, but we don't have
++                 * anything to provide to it
++                 */
++                Module.voutPort.postMessage({
++                    customCmd: 'displayFrame',
++                    frame: undefined,
++                    pictureId: picIdx
++                });
++                return;
++            }
++            Module.voutPort.postMessage({
++                customCmd: 'displayFrame',
++                frame: frame,
++                pictureId: picIdx,
++              }, [frame]);
++        }
++    }
++
++    async function getVoutMessagePort() {
++        let p = new Promise((resolve, reject) => {
++            self.addEventListener('message', function(e) {
++                let msg = e['data'];
++                if (msg.customCmd == 'transferMessagePort') {
++                    let port = msg['transferList'][0];
++                    if (!port)
++                        reject();
++                    Module.voutPort = port;
++                    Module.voutPort.onmessage = onInteropMessage;
++                    resolve();
++                }
++            });
++        });
++        await p;
++    }
++
++    async function onDecoderWorkerMessage(msg) {
++        const data = msg['data'];
++        if (data['customCmd'] == 'decode') {
++            let block = data['block'];
++            _decodeBlock( block );
++            _releaseBlock(block);
++        } else if ( data['customCmd'] == 'flush' ) {
++            await Module.decoder.flush();
++            for ( let i = 0; i < Module.framesReady.length; ++i ) {
++                if (Module.framesReady[i]) {
++                    Module.framesReady[i].close();
++                    Module.framesReady[i] = undefined;
++                }
++            }
++            Module.msgChannel.port1.postMessage({
++                customCmd: 'onFlushCompleted'
++            });
++        } else if ( data['customCmd'] == 'close' ) {
++            Module.decoder.close();
++        }
++    }
++
++    async function getPictureAsync(dec) {
++        function getPicture(dec, resolve) {
++            let pic = _tryGetPictureFromPool(dec);
++            if (!pic) {
++                setTimeout( function() {
++                      getPicture(dec, resolve);
++                  }, 1);
++                return;
++            }
++            resolve(pic);
++        }
++        let p = new Promise((resolve) => {
++            getPicture(dec, resolve);
++        });
++        return await p;
++    }
++
++    self.addEventListener('message', (e) => {
++        let msg = e['data'];
++
++        console.log('Received message in decoder worker');
++        console.dir(msg);
++
++        if (msg['customCmd'] == 'getDecoderWorkerMessagePort') {
++            Module.msgChannel = new MessageChannel();
++            Module.msgChannel.port1.onmessage = onDecoderWorkerMessage;
++            self.postMessage({
++                customCmd: 'transferMessagePort',
++                targetThread: msg['replyTo'],
++                transferList: [Module.msgChannel.port2],
++            }, [Module.msgChannel.port2]);
++        }
++    });
++
++    /* Store a handle to our decoder_t for later invocations */
++    Module.webCodecCtx = decoder;
++
++    /* Prepare an array containing the decoded frames, so the interop can query
++     * then later */
++    Module.framesReady = [];
++
++    let initCfg = {
++        'output': async function (frame) {
++            /* Always update the video output format so we can ensure the vout
++             * is created when we try to acquire its message port */
++            if ( !_updateVideoOutput(Module.webCodecCtx) )
++            {
++                frame.close();
++                return;
++            }
++            if (Module.voutPort === undefined)
++                await getVoutMessagePort();
++            let p = await getPictureAsync(Module.webCodecCtx);
++            let picIdx = _queuePicture(Module.webCodecCtx, p, frame.timestamp);
++            if ( Module.framesReady[picIdx] ) {
++                /*
++                 *  If we end up overriding a frame, it means it was dropped
++                 *  by the vout before the interop asked for it, so we can close
++                 *  it safely.
++                 */
++                console.log('Discarding dropped frame ', picIdx);
++                Module.framesReady[picIdx].close();
++            }
++            Module.framesReady[picIdx] = frame;
++        },
++        'error': function(err) {
++            console.log('Error while decoding: ');
++            console.log(err);
++        }
++    };
++    Module.decoder = new VideoDecoder( initCfg );
++    let decCfg = Emval.toValue( decCfgHandle );
++    Module.decoder.configure( decCfg );
++});
++
++static bool initDecoder( decoder_t* dec )
++{
++    auto decCfg = getDecoderConfig( dec, true );
++    initDecoderJS( dec, decCfg.as_handle() );
++
++    return true;
++}
++
++static void mainloop_tick()
++{
++}
++
++static void* WebcodecDecodeWorker( void* arg )
++{
++    /*
++     * We need to be able to yield back to the browser main loop after we
++     * queue some blocks for decoding, otherwise the callback will never be
++     * invoked.
++     * In order to do so, we spawn a dedidated thread that will run a "main loop"
++     * which consist of dequeuing a single block and passing it to webcodec.
++     * After doing so, it yields back to the message loop and so on. If no
++     * blocks are queued for decoding, we can block as there nothing else being
++     * done from this worker.
++     */
++    auto dec = static_cast<decoder_t*>( arg );
++    if ( !initDecoder( dec ) )
++    {
++        msg_Err( dec, "Failed to initialize decoder: FIXME" );
++        return NULL;
++    }
++    auto sys = static_cast<decoder_sys_t*>( dec->p_sys );
++    auto vctxPrivate = static_cast<webcodec_context*>(
++            vlc_video_context_GetPrivate(sys->vctx, VLC_VIDEO_CONTEXT_WEBCODEC));
++    vctxPrivate->decoder_worker = pthread_self();
++    emscripten_set_main_loop(mainloop_tick, 1, true);
++    return NULL;
++}
++
++EM_ASYNC_JS(void, initDecoderWorkerMessagePort, (decoder_t* dec), {
++    if (Module.decoderWorkerPort !== undefined) {
++        return;
++    }
++    let workerMessagePortPromise = new Promise((resolve, reject) => {
++        self.addEventListener('message', function(e) {
++            let msg = e['data'];
++            if (msg.customCmd == 'transferMessagePort') {
++                let port = msg['transferList'][0];
++                if (!port) {
++                    console.log('No port provided, rejecting');
++                    reject();
++                }
++                Module.decoderWorkerPort = port;
++                resolve();
++            }
++        });
++        self.postMessage({
++            customCmd: 'getDecoderWorkerMessagePort',
++            targetThread: _getVlcDecoderWorkerThread(dec),
++            replyTo: _pthread_self()
++        });
++    });
++    await workerMessagePortPromise;
++    Module.decoderWorkerPort.onmessage = (e) => {
++        let data = e['data'];
++        if (data['customCmd'] == 'onFlushCompleted') {
++            Module.flushPromiseResolver();
++        }
++    };
++})
++
++static int Decode( decoder_t* dec, block_t* block )
++{
++    initDecoderWorkerMessagePort(dec);
++    EM_ASM({
++        Module.decoderWorkerPort.postMessage({
++            customCmd: 'decode',
++            block: $0
++        });
++    }, block);
++    return VLCDEC_SUCCESS;
++}
++
++EM_ASYNC_JS(void, flushAsync, (), {
++    let p = new Promise((r) => {
++        Module.flushPromiseResolver = r;
++        Module.decoderWorkerPort.postMessage({
++            customCmd: 'flush'
++        });
++    });
++    await p;
++    Module.flushPromiseResolver = undefined;
++});
++
++static void Flush( decoder_t* dec )
++{
++    initDecoderWorkerMessagePort(dec);
++    flushAsync();
++}
++
++static int Open( vlc_object_t* obj )
++{
++    auto dec = reinterpret_cast<decoder_t*>(obj);
++
++    if ( dec->fmt_in.i_cat != VIDEO_ES )
++        return VLC_EGENERIC;
++
++    auto decoderType = emval::global("VideoDecoder");
++    if ( !decoderType.as<bool>() )
++    {
++        msg_Err( obj, "Can't get VideoDecoder type, webcodec is probably not "
++                      "supported on this browser" );
++        return VLC_EGENERIC;
++    }
++    auto sys = std::make_unique<decoder_sys_t>();
++    dec->p_sys = sys.get();
++
++    auto decoderConfig = getDecoderConfig( dec, false );
++    if ( decoderConfig.isUndefined() )
++        return VLC_EGENERIC;
++    auto isSupported = probeConfig(decoderConfig.as_handle());
++
++    if ( isSupported == false )
++    {
++        msg_Err( dec, "VideoDecoder doesn't support this configuration" );
++        return VLC_EGENERIC;
++    }
++
++    if ( es_format_Copy( &dec->fmt_out, &dec->fmt_in ) != VLC_SUCCESS )
++        return VLC_ENOMEM;
++    dec->fmt_out.i_codec = dec->fmt_out.video.i_chroma = VLC_CODEC_WEBCODEC_OPAQUE;
++
++    auto dec_dev = decoder_GetDecoderDevice(dec);
++    sys->vctx = vlc_video_context_Create(dec_dev, VLC_VIDEO_CONTEXT_WEBCODEC,
++                                         sizeof(webcodec_context), nullptr);
++    auto vctxPrivate = static_cast<webcodec_context*>(
++            vlc_video_context_GetPrivate(sys->vctx, VLC_VIDEO_CONTEXT_WEBCODEC));
++    new (vctxPrivate) webcodec_context();
++
++    if ( webcodec_CreatePool(sys->vctx, &dec->fmt_out.video) != VLC_SUCCESS )
++        return VLC_EGENERIC;
++
++    if ( vlc_clone( &sys->th, &WebcodecDecodeWorker, dec ) != VLC_SUCCESS )
++    {
++        msg_Err( obj, "Failed to create webcodec thread" );
++        return VLC_EGENERIC;
++    }
++
++    dec->pf_decode = &Decode;
++    dec->pf_flush = &Flush;
++
++    sys.release();
++    return VLCDEC_SUCCESS;
++}
++
++static void Close( decoder_t* dec )
++{
++    auto sys = static_cast<decoder_sys_t*>( dec->p_sys );
++    auto vctx = static_cast<webcodec_context*>(
++                vlc_video_context_GetPrivate(sys->vctx, VLC_VIDEO_CONTEXT_WEBCODEC ) );
++    picture_pool_Release(vctx->pool);
++    EM_ASM({
++        Module.decoderWorkerPort.postMessage({
++            customCmd: 'close'
++        });
++    });
++    delete sys;
++}
++
++static int
++OpenDecDevice(vlc_decoder_device *device, vlc_window_t *)
++{
++    static const struct vlc_decoder_device_operations ops =
++    {
++        nullptr,
++    };
++    device->ops = &ops;
++    device->type = VLC_DECODER_DEVICE_WEBCODEC;
++
++    return VLC_SUCCESS;
++}
++
++vlc_module_begin ()
++    set_description("Video decoder using browser provided implementation")
++    set_subcategory(SUBCAT_INPUT_VCODEC)
++    set_section(N_("Decoding"), NULL)
++    set_capability("video decoder", 100)
++    set_callbacks(Open, Close)
++    add_shortcut("webcodec")
++
++    add_submodule()
++        set_callback_dec_device(OpenDecDevice, 1)
++vlc_module_end ()
+diff --git a/modules/demux/Makefile.am b/modules/demux/Makefile.am
+index 802ce9038b..171a9ce8e3 100644
+--- a/modules/demux/Makefile.am
++++ b/modules/demux/Makefile.am
+@@ -543,4 +543,6 @@ libvlc_json_la_SOURCES = \
+ libvlc_json_la_CPPFLAGS = $(AM_CPPFLAGS) -I$(srcdir)/demux/json
+ libvlc_json_la_LIBADD = $(LTLIBVLCCORE) ../compat/libcompat.la $(LIBM)
+ libvlc_json_la_LDFLAGS = -static
++if !HAVE_EMSCRIPTEN
+ noinst_LTLIBRARIES += libvlc_json.la
++endif
+diff --git a/modules/hw/emscripten/Makefile.am b/modules/hw/emscripten/Makefile.am
+new file mode 100644
+index 0000000000..3f2a118bc1
+--- /dev/null
++++ b/modules/hw/emscripten/Makefile.am
+@@ -0,0 +1,10 @@
++emscriptendir = $(pluginsdir)/emscripten
++
++libglconverter_emscripten_plugin_la_SOURCES = hw/emscripten/converter.cpp video_output/emscripten/common.cpp
++libglconverter_emscripten_plugin_la_CPPFLAGS = $(AM_CPPFLAGS)
++libglconverter_emscripten_plugin_la_CXXFLAGS = $(AM_CXXFLAGS)
++libglconverter_emscripten_plugin_la_LIBADD = $(AM_LIBADD) 
++
++if HAVE_EMSCRIPTEN
++emscripten_LTLIBRARIES = libglconverter_emscripten_plugin.la
++endif
+diff --git a/modules/hw/emscripten/converter.cpp b/modules/hw/emscripten/converter.cpp
+new file mode 100644
+index 0000000000..edc8d93bc0
+--- /dev/null
++++ b/modules/hw/emscripten/converter.cpp
+@@ -0,0 +1,137 @@
++/*****************************************************************************
++ * webcodec.cpp: VLC picture to WebCodec VideoFrame
++ *****************************************************************************
++ * Copyright (C) 2022 VLC authors, VideoLAN and VideoLabs
++ *
++ * 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 <vlc_common.h>
++#include <vlc_filter.h>
++#include <vlc_plugin.h>
++
++#include "../../video_output/emscripten/common.h"
++
++#include <emscripten/emscripten.h>
++#include <emscripten/em_js.h>
++#include <emscripten/val.h>
++
++struct chroma_sys_t
++{
++};
++
++static picture_t* UploadSurface(filter_t* filter, picture_t* src)
++{
++    auto vctx = static_cast<webcodec_context*>(
++            vlc_video_context_GetPrivate( filter->vctx_out, VLC_VIDEO_CONTEXT_WEBCODEC) );
++    auto dst = picture_pool_Wait( vctx->pool );
++    if (dst == nullptr)
++    {
++        picture_Release(src);
++        return nullptr;
++    }
++
++    auto size = 0u;
++    for ( auto i = 0; i < src->i_planes; ++i )
++        size += src->p[i].i_lines * src->p[i].i_pitch;
++    auto buffer = emscripten::val::global("Uint8Array").new_( size );
++    auto planesLayout = emscripten::val::array();
++    auto offset = 0u;
++    for ( auto i = 0; i < src->i_planes; ++i )
++    {
++        auto planeSize = src->p[i].i_lines * src->p[i].i_pitch;
++        auto p = emscripten::typed_memory_view( planeSize, src->p[i].p_pixels );
++        buffer.call<void>("set", p, offset);
++        auto layout = emscripten::val::object();
++        layout.set("offset", offset);
++        layout.set("stride", src->p[i].i_pitch);
++        planesLayout.call<void>( "push", layout );
++        offset += planeSize;
++    }
++
++    auto init = emscripten::val::object();
++    init.set( "format", "I420" );
++    init.set( "codedWidth", src->format.i_width );
++    init.set( "codedHeight", src->format.i_height );
++    init.set( "timestamp", (long)US_FROM_VLC_TICK( src->date ) );
++    init.set( "layout", planesLayout );
++    init.set( "displayWidth", src->format.i_visible_width);
++    init.set( "displayHeight", src->format.i_visible_height );
++
++    auto visibleRect = emscripten::val::object();
++    visibleRect.set( "x", src->format.i_x_offset );
++    visibleRect.set( "y", src->format.i_y_offset );
++    visibleRect.set( "width", src->format.i_visible_width);
++    visibleRect.set( "height", src->format.i_visible_height );
++    init.set( "visibleRect", visibleRect );
++
++    auto frame = emscripten::val::global("VideoFrame").new_( buffer, init );
++    auto pictureIdx = PictureContextPrivate(dst->context)->pictureIdx;
++    EM_ASM({
++        let frame = Emval.toValue($0);
++        Module.msgChannel.port2.postMessage({
++            customCmd: 'displayFrame',
++            pictureId: $1,
++            frame: frame
++        });
++    }, frame.as_handle(), pictureIdx);
++
++
++    picture_CopyProperties( dst, src );
++    picture_Release(src);
++    return dst;
++}
++
++static void vlc_webcodec_CloseChroma(filter_t* filter)
++{
++    auto vctx = static_cast<webcodec_context*>(
++                vlc_video_context_GetPrivate(filter->vctx_out, VLC_VIDEO_CONTEXT_WEBCODEC ) );
++    picture_pool_Release(vctx->pool);
++}
++
++static const struct vlc_filter_operations filter_ops = {
++    .filter_video = UploadSurface,   .close = vlc_webcodec_CloseChroma,
++};
++
++static int vlc_webcodec_OpenChroma(filter_t* filter)
++{
++    if (filter->fmt_in.video.i_height != filter->fmt_out.video.i_height
++     || filter->fmt_in.video.i_width != filter->fmt_out.video.i_width
++     || filter->fmt_in.video.orientation != filter->fmt_out.video.orientation)
++    {
++        return VLC_EGENERIC;
++    }
++
++    if (filter->fmt_out.video.i_chroma != VLC_CODEC_WEBCODEC_OPAQUE)
++        return VLC_EGENERIC;
++    if (filter->fmt_in.video.i_chroma != VLC_CODEC_I420)
++        return VLC_EGENERIC;
++
++    filter->ops = &filter_ops;
++    return webcodec_CreatePool(filter->vctx_out, &filter->fmt_out.video);
++}
++
++vlc_module_begin()
++    set_shortname(N_("WebCodec filters"))
++    set_description(N_("WebCodec filters"))
++    set_subcategory(SUBCAT_VIDEO_VFILTER)
++
++    add_submodule()
++    set_callback_video_converter(vlc_webcodec_OpenChroma, 10)
++vlc_module_end()
+diff --git a/modules/video_output/Makefile.am b/modules/video_output/Makefile.am
+index 381c9cd0eb..a3104c5e63 100644
+--- a/modules/video_output/Makefile.am
++++ b/modules/video_output/Makefile.am
+@@ -303,6 +303,13 @@ vout_LTLIBRARIES += libegl_android_plugin.la libglinterop_android_plugin.la
+ endif
+ endif
+ 
++### Emscripten
++libglinterop_emscripten_plugin_la_SOURCES = video_output/opengl/interop_emscripten.cpp \
++	video_output/opengl/interop.h
++libglinterop_emscripten_plugin_la_CXXFLAGS = $(AM_CXXFLAGS) -DUSE_OPENGL_ES2
++if HAVE_EMSCRIPTEN
++vout_LTLIBRARIES += libglinterop_emscripten_plugin.la
++endif
+ 
+ ### Direct Rendering Manager (DRM) ###
+ 
+@@ -339,6 +346,13 @@ libcaca_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(voutdir)'
+ EXTRA_LTLIBRARIES += libcaca_plugin.la
+ vout_LTLIBRARIES += $(LTLIBcaca)
+ 
++### Emscripten ###
++libemscripten_window_plugin_la_SOURCES = video_output/emscripten.cpp
++
++if HAVE_EMSCRIPTEN
++vout_LTLIBRARIES += libemscripten_window_plugin.la
++endif
++
+ ### Common ###
+ 
+ libflaschen_plugin_la_SOURCES = video_output/flaschen.c
+diff --git a/modules/video_output/emscripten.cpp b/modules/video_output/emscripten.cpp
+new file mode 100644
+index 0000000000..e054826263
+--- /dev/null
++++ b/modules/video_output/emscripten.cpp
+@@ -0,0 +1,257 @@
++/**
++ * @file emscripten.c
++ * @brief Emscripten webgl video output for VLC media player
++ */
++/*****************************************************************************
++ * Copyright © 2020 VLC authors and VideoLAN
++ *
++ *
++ * 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 <cstdarg>
++#include <new>
++#include <string>
++
++#include <vlc_common.h>
++#include <vlc_plugin.h>
++#include <vlc_window.h>
++#include <vlc_vout_display.h>
++#include <vlc_opengl.h>
++
++#include "./opengl/vout_helper.h"
++#include "opengl/renderer.h"
++
++#include "emscripten/common.h"
++
++#include <emscripten.h>
++#include <emscripten/html5.h>
++#include <emscripten/val.h>
++#include <webgl/webgl2.h>
++// eglGetProcAddress
++#include <EGL/egl.h>
++
++static const struct vlc_window_operations ops = {
++    //TODO: Implement canvas operations
++    //vlc_window_ReportSize() should be called from here
++};
++
++//MAIN_THREAD_
++//EM_JS(emscripten::EM_VAL, GetCanvasSize, (), {
++//    let canvas = document.getElementById('canvas');
++//    if (!canvas)
++//        return Emval.bindValue([]);
++//    return Emval.bindValue([canvas.width, canvas.height]);
++//});
++
++typedef struct emscripten_gl_sys_t
++{
++    EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context;
++} emscripten_gl_sys_t;
++
++static int OpenWindow(vlc_window_t *wnd)
++{
++    wnd->type = VLC_WINDOW_TYPE_EMSCRIPTEN_WEBGL;
++    wnd->ops = &ops;
++    wnd->handle.canvas = "canvas";
++
++    //emscripten::val size = GetCanvasSize();
++    //std::vector<int> vec_size = emscripten::vecFromJSArray(size);
++    //if (vec_size.size() != 2)
++    //    return VLC_EGENERIC;
++
++    //vlc_window_ReportSize(wnd, vec_size[0], vec_size[1]);
++    vlc_window_ReportSize(wnd, 1280, 720);
++    return VLC_SUCCESS;
++}
++
++static void *GetProcAddress(vlc_gl_t *gl, const char *name)
++{
++    VLC_UNUSED(gl);
++
++    return reinterpret_cast<void*>(eglGetProcAddress(name));
++}
++
++static int MakeCurrent(vlc_gl_t *gl)
++{
++    auto sys = static_cast<emscripten_gl_sys_t*>( gl->sys );
++
++    if (emscripten_webgl_make_context_current(sys->context) != EMSCRIPTEN_RESULT_SUCCESS)
++        return VLC_EGENERIC;
++    return VLC_SUCCESS;
++}
++
++static void ReleaseCurrent(vlc_gl_t *gl)
++{
++    VLC_UNUSED(gl);
++    emscripten_webgl_make_context_current(0);
++}
++
++static void Swap(vlc_gl_t *gl)
++{
++    /**
++     * There is no way to commit/swapbuffer from an offscreen canvas, so instead
++     * we render the offscreen canvas onto a bitmap and let the actual canvas
++     * display that bitmap from the main thread.
++     */
++    VLC_UNUSED(gl);
++    EM_ASM({
++        let bitmap = Module.offscreenCanvas.transferToImageBitmap();
++        Module.voutMsgPort.postMessage({
++            customCmd: 'commitFrame',
++            bitmap: bitmap,
++        }, [bitmap]);
++    });
++}
++
++static void Resize(vlc_gl_t *gl, unsigned w, unsigned h)
++{
++    VLC_UNUSED(gl);
++    VLC_UNUSED(w);
++    VLC_UNUSED(h);
++}
++
++EM_ASYNC_JS(void, getVoutMessagePort, (), {
++    let p = new Promise((resolve, reject) => {
++        let listener = function(e) {
++            let msg = e.data;
++            if (msg.customCmd == 'getVoutMessagePortResult') {
++                Module.voutMsgPort = msg['msgPort'];
++                resolve();
++                self.removeEventListener('message', listener);
++            };
++        };
++        self.addEventListener('message', listener);
++        self.postMessage({
++            customCmd: 'getVoutMessagePort'
++        });
++    });
++    await p;
++});
++
++EM_JS(int, createGlContext, (int width, int height), {
++    Module.offscreenCanvas = new OffscreenCanvas(width, height);
++    Module.glCtx = Module.offscreenCanvas.getContext('webgl2');
++
++    return GL.registerContext(Module.glCtx, {
++        antialias: false
++    });
++});
++
++static void Close (vlc_gl_t *gl)
++{
++    auto sys = static_cast<emscripten_gl_sys_t*>( gl->sys );
++    delete sys;
++}
++
++static int Open (vlc_gl_t *gl, unsigned width, unsigned height)
++{
++    VLC_UNUSED(width), VLC_UNUSED(height);
++
++    vlc_window_t *wnd = gl->surface;
++
++    if (wnd->type != VLC_WINDOW_TYPE_EMSCRIPTEN_WEBGL)
++        goto error;
++
++    emscripten_gl_sys_t *sys;
++
++    gl->sys = sys = new (std::nothrow)emscripten_gl_sys_t;
++    if (!sys)
++        return VLC_ENOMEM;
++
++    MAIN_THREAD_EM_ASM({
++        if (Module.canvasCtx === undefined) {
++            let canvasName = UTF8ToString($1);
++            Module.canvasCtx = document.getElementById(canvasName).getContext('2d');
++        }
++        function onVoutMessage(msg) {
++            let data = msg['data'];
++            if (data.customCmd == 'commitFrame') {
++                let bmp = data['bitmap'];
++                Module.canvasCtx.drawImage(bmp, 0, 0);
++                bmp.close();
++            }
++        }
++        let w = Module.PThread.pthreads[$0].worker;
++        w.addEventListener('message', function (e) {
++            let msg = e['data'];
++            if (msg.customCmd == 'getVoutMessagePort') {
++                let msgChannel = new MessageChannel();
++                msgChannel.port1.onmessage = onVoutMessage;
++                w.postMessage({
++                    customCmd: 'getVoutMessagePortResult',
++                    msgPort: msgChannel.port2,
++                }, [msgChannel.port2]);
++            }
++        });
++    }, pthread_self(), wnd->handle.canvas ? wnd->handle.canvas : "canvas");
++
++    getVoutMessagePort();
++    sys->context = createGlContext(width, height);
++
++    if (!sys->context) {
++        goto error;
++    }
++    // Check that the WebGL context is valid
++    if (emscripten_webgl_make_context_current(sys->context) != EMSCRIPTEN_RESULT_SUCCESS) {
++        emscripten_log(EM_LOG_CONSOLE, "failed to make context current");
++        goto error;
++    }
++
++    // Release the context
++    emscripten_webgl_make_context_current(0);
++    wnd->handle.em_context = sys->context;
++
++    static const struct vlc_gl_operations gl_ops =
++    {
++        .make_current = MakeCurrent,
++        .release_current = ReleaseCurrent,
++        .resize = Resize,
++        .swap = Swap,
++        .get_proc_address = GetProcAddress,
++        .close = Close,
++    };
++    gl->ops = &gl_ops;
++    
++    return VLC_SUCCESS;
++
++error:
++    Close(gl);
++    return VLC_EGENERIC;
++}
++
++/*
++ * Module descriptor
++ */
++vlc_module_begin()
++    set_shortname(N_("Emscripten Window"))
++    set_description(N_("Emscripten drawing area"))
++    set_subcategory(SUBCAT_VIDEO_VOUT)
++    set_capability("vout window", 10)
++    set_callbacks(OpenWindow, nullptr)
++
++    add_submodule ()
++    set_shortname("Emscripten GL")
++    set_description(N_("Emscripten extension for OpenGL"))
++    set_subcategory(SUBCAT_VIDEO_VOUT)
++    set_capability("opengl es2", 50)
++    set_callback(Open)
++    add_shortcut("em_webgl")
++vlc_module_end()
++
+diff --git a/modules/video_output/emscripten/common.cpp b/modules/video_output/emscripten/common.cpp
+new file mode 100644
+index 0000000000..b2065be3b4
+--- /dev/null
++++ b/modules/video_output/emscripten/common.cpp
+@@ -0,0 +1,82 @@
++/*****************************************************************************
++ * common.h: Emscripten decoder/vout common code
++ *****************************************************************************
++ * Copyright (C) 2021 VLC authors and VideoLAN
++ *
++ * 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 <vlc_common.h>
++#include <vlc_decoder.h>
++#include <vlc_picture_pool.h>
++
++#include "common.h"
++
++static picture_context_t* PictureContextCopy(picture_context_t* picCtx)
++{
++    vlc_video_context_Hold( picCtx->vctx );
++    return picCtx;
++}
++
++static void PictureContextDestroy( picture_context_t* )
++{
++    /* Context is already released by picture_Release() */
++}
++
++static picture_context_t* webcodec_CreatePictureContext( vlc_video_context* vctx, uint32_t picIdx )
++{
++    auto picCtx = static_cast<webcodec_picture_ctx*>(
++                malloc( sizeof(  webcodec_picture_ctx ) ) );
++    if ( picCtx == nullptr )
++        return nullptr;
++    picCtx->context.copy = PictureContextCopy;
++    picCtx->context.destroy = PictureContextDestroy;
++    picCtx->context.vctx = vctx;
++    picCtx->pictureIdx = picIdx;
++    return &picCtx->context;
++}
++
++
++int webcodec_CreatePool( vlc_video_context* vctx, const video_format_t* fmt )
++{
++    auto ctx = static_cast<webcodec_context*>(
++        vlc_video_context_GetPrivate( vctx, VLC_VIDEO_CONTEXT_WEBCODEC ) );
++
++    for ( auto i = 0u; i < ARRAY_SIZE(ctx->pictures); ++i )
++    {
++        auto pic = picture_NewFromFormat(fmt);
++        if ( pic != nullptr )
++            pic->context = webcodec_CreatePictureContext( vctx, i );
++        if ( pic == nullptr || pic->context == nullptr )
++        {
++            while ( i-- )
++                picture_Release( ctx->pictures[i] );
++            return VLC_EGENERIC;
++        }
++        ctx->pictures[i] = pic;
++    }
++    ctx->pool = picture_pool_New(ARRAY_SIZE(ctx->pictures), ctx->pictures);
++    if ( ctx->pool == nullptr )
++    {
++        for ( auto i = 0u; i < ARRAY_SIZE(ctx->pictures); ++i )
++            picture_Release( ctx->pictures[i] );
++        return VLC_EGENERIC;
++    }
++    return VLC_SUCCESS;
++}
+diff --git a/modules/video_output/emscripten/common.h b/modules/video_output/emscripten/common.h
+new file mode 100644
+index 0000000000..08967d0703
+--- /dev/null
++++ b/modules/video_output/emscripten/common.h
+@@ -0,0 +1,57 @@
++/*****************************************************************************
++ * common.h: Emscripten decoder/vout common code
++ *****************************************************************************
++ * Copyright (C) 2021 VLC authors and VideoLAN
++ *
++ * 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.
++ *****************************************************************************/
++
++#ifndef EMSCRIPTEN_COMMON_H
++#define EMSCRIPTEN_COMMON_H
++
++#ifndef __cplusplus
++# error This only supports C++
++#endif
++
++#include <vlc_threads.h>
++#include <vlc_picture.h>
++#include <vlc_picture_pool.h>
++
++#include <emscripten/html5_webgl.h>
++//#include <emscripten/val.h>
++
++#define WEBCODEC_MAX_PICTURES 32
++
++struct webcodec_context
++{
++    pthread_t decoder_worker;
++    picture_pool_t* pool;
++    picture_t* pictures[WEBCODEC_MAX_PICTURES];
++};
++
++struct webcodec_picture_ctx
++{
++    picture_context_t context;
++    uint32_t pictureIdx;
++};
++
++int webcodec_CreatePool( vlc_video_context* vctx, const video_format_t* fmt );
++
++static inline webcodec_picture_ctx* PictureContextPrivate( picture_context_t* picCtx )
++{
++    return container_of( picCtx, webcodec_picture_ctx, context );
++}
++
++#endif // EMSCRIPTEN_COMMON_H
+diff --git a/modules/video_output/opengl/Makefile.am b/modules/video_output/opengl/Makefile.am
+index f95a1effaf..e2587f3706 100644
+--- a/modules/video_output/opengl/Makefile.am
++++ b/modules/video_output/opengl/Makefile.am
+@@ -112,6 +112,12 @@ libglfilter_mock_plugin_la_LIBADD += libvlc_opengl.la $(GL_LIBS)
+ noinst_LTLIBRARIES += libglfilter_mock_plugin.la
+ endif
+ 
++if HAVE_EMSCRIPTEN
++libglfilter_mock_plugin_la_LIBADD += libvlc_opengles.la $(GL_LIBS)
++libglfilter_mock_plugin_la_CFLAGS = -DUSE_OPENGL_ES2=1
++noinst_LTLIBRARIES += libglfilter_mock_plugin.la
++endif
++
+ if HAVE_IOS
+ libglfilter_mock_plugin_la_LIBADD += libvlc_opengles.la $(GLES2_LIBS)
+ libglfilter_mock_plugin_la_CFLAGS = -DUSE_OPENGL_ES2=1
+diff --git a/modules/video_output/opengl/egl_display_generic.c b/modules/video_output/opengl/egl_display_generic.c
+index dfb841feef..730fb381d2 100644
+--- a/modules/video_output/opengl/egl_display_generic.c
++++ b/modules/video_output/opengl/egl_display_generic.c
+@@ -52,8 +52,8 @@ static vlc_egl_display_open_fn Open;
+ static int
+ Open(struct vlc_egl_display *display)
+ {
+-#ifdef __ANDROID__
+-    /* The default display is refcounted on Android */
++#if defined(__ANDROID__) || defined(__EMSCRIPTEN__)
++    /* The default display is refcounted on Android and Emscripten */
+     display->display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ #elif defined(EGL_KHR_display_reference)
+     const char *extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
+diff --git a/modules/video_output/opengl/filter.c b/modules/video_output/opengl/filter.c
+index 7f0aa73e7c..491ce88dd5 100644
+--- a/modules/video_output/opengl/filter.c
++++ b/modules/video_output/opengl/filter.c
+@@ -31,6 +31,7 @@
+ #include <vlc_modules.h>
+ 
+ #include "gl_api.h"
++#include "picture.h"
+ 
+ struct vlc_gl_filter *
+ vlc_gl_filter_New(struct vlc_gl_t *gl, const struct vlc_gl_api *api)
+diff --git a/modules/video_output/opengl/filter.h b/modules/video_output/opengl/filter.h
+index dc81b69d8a..b8272a5115 100644
+--- a/modules/video_output/opengl/filter.h
++++ b/modules/video_output/opengl/filter.h
+@@ -19,139 +19,4 @@
+  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+  *****************************************************************************/
+ 
+-#ifndef VLC_GL_FILTER_H
+-#define VLC_GL_FILTER_H
+-
+-#include <vlc_tick.h>
+-
+-#include "picture.h"
+-
+-struct vlc_gl_filter;
+-
+-#ifdef __cplusplus
+-extern "C"
+-{
+-#endif
+-
+-struct vlc_gl_tex_size {
+-    unsigned width;
+-    unsigned height;
+-};
+-
+-struct vlc_gl_input_meta {
+-    vlc_tick_t pts;
+-    unsigned plane;
+-};
+-
+-typedef int
+-vlc_gl_filter_open_fn(struct vlc_gl_filter *filter,
+-                      const config_chain_t *config,
+-                      const struct vlc_gl_format *glfmt,
+-                      struct vlc_gl_tex_size *size_out);
+-
+-#define set_callback_opengl_filter(open) \
+-    { \
+-        vlc_gl_filter_open_fn *fn = open; \
+-        (void) fn; \
+-        set_callback(fn); \
+-    }
+-
+-struct vlc_gl_filter_ops {
+-    /**
+-     * Draw the result of the filter to the current framebuffer
+-     */
+-    int (*draw)(struct vlc_gl_filter *filter, const struct vlc_gl_picture *pic,
+-                const struct vlc_gl_input_meta *meta);
+-
+-    /**
+-     * Free filter resources
+-     */
+-    void (*close)(struct vlc_gl_filter *filter);
+-
+-    /**
+-     * Request a (responsive) filter to adapt its output size (optional)
+-     *
+-     * A responsive filter is a filter for which the size of the produced
+-     * pictures depends on the output (e.g. display) size rather than the
+-     * input. This is for example the case for a renderer.
+-     *
+-     * A new output size is requested (size_out). The filter is authorized to
+-     * change the size_out to enforce its own constraints.
+-     *
+-     * In addition, it may request to the previous filter (if any) an optimal
+-     * size it wants to receive. If set to non-zero value, this previous filter
+-     * will receive this size as its requested size (and so on).
+-     *
+-     * \retval true if the resize is accepted (possibly with a modified
+-     *              size_out)
+-     * \retval false if the resize is rejected (included on error)
+-     */
+-    int (*request_output_size)(struct vlc_gl_filter *filter,
+-                               struct vlc_gl_tex_size *size_out,
+-                               struct vlc_gl_tex_size *optimal_in);
+-
+-    /**
+-     * Callback to notify input size changes
+-     *
+-     * When a filter changes its output size as a result of
+-     * request_output_size(), the next filter is notified by this callback.
+-     */
+-    void (*on_input_size_change)(struct vlc_gl_filter *filter,
+-                                 const struct vlc_gl_tex_size *size);
+-};
+-
+-/**
+- * OpenGL filter, in charge of a rendering pass.
+- */
+-struct vlc_gl_filter {
+-    vlc_object_t obj;
+-    module_t *module;
+-
+-    struct vlc_gl_t *gl;
+-    const struct vlc_gl_api *api;
+-    const struct vlc_gl_format *glfmt_in;
+-
+-    struct {
+-        /**
+-         * An OpenGL filter may either operate on the input RGBA picture, or on
+-         * individual input planes (without chroma conversion) separately.
+-         *
+-         * In practice, this is useful for deinterlace filters.
+-         *
+-         * This flag must be set by the filter module (default is false).
+-         */
+-        bool filter_planes;
+-
+-        /**
+-         * A blend filter draws over the input picture (without reading it).
+-         *
+-         * Meaningless if filter_planes is true.
+-         *
+-         * This flag must be set by the filter module (default is false).
+-         */
+-        bool blend;
+-
+-        /**
+-         * Request MSAA level.
+-         *
+-         * This value must be set by the filter module (default is 0, which
+-         * means disabled).
+-         *
+-         * Meaningless if filter_planes is true.
+-         *
+-         * The actual MSAA level may be overwritten to 0 if multisampling is
+-         * not supported, or to a higher value if another filter rendering on
+-         * the same framebuffer requested a higher MSAA level.
+-         */
+-        unsigned msaa_level;
+-    } config;
+-
+-    const struct vlc_gl_filter_ops *ops;
+-    void *sys;
+-};
+-
+-#ifdef __cplusplus
+-}
+-#endif
+-
+-#endif
++#include <vlc_opengl_filter.h>
+diff --git a/modules/video_output/opengl/filter_mock.c b/modules/video_output/opengl/filter_mock.c
+index 64a73b303e..3915456d7e 100644
+--- a/modules/video_output/opengl/filter_mock.c
++++ b/modules/video_output/opengl/filter_mock.c
+@@ -73,6 +73,7 @@
+ #include "gl_common.h"
+ #include "gl_util.h"
+ #include "sampler.h"
++#include "picture.h"
+ 
+ #define MOCK_CFG_PREFIX "mock-"
+ 
+@@ -101,6 +102,7 @@ struct sys {
+ 
+     float rotation_matrix[16];
+     float ar;
++    struct vlc_gl_api api;
+ };
+ 
+ static void
+@@ -586,6 +588,9 @@ Open(struct vlc_gl_filter *filter, const config_chain_t *config,
+ 
+     sys->sampler = NULL;
+ 
++    vlc_gl_api_Init(&sys->api, filter->gl);
++    filter->api = &sys->api;
++
+     int ret;
+     if (plane)
+         ret = InitPlane(filter, glfmt);
+diff --git a/modules/video_output/opengl/filter_priv.h b/modules/video_output/opengl/filter_priv.h
+index 7b702ddb17..cd0e3084fa 100644
+--- a/modules/video_output/opengl/filter_priv.h
++++ b/modules/video_output/opengl/filter_priv.h
+@@ -27,6 +27,7 @@
+ #include <vlc_picture.h>
+ 
+ #include "filter.h"
++#include "picture.h"
+ 
+ struct vlc_gl_filter_priv {
+     struct vlc_gl_filter filter;
+diff --git a/modules/video_output/opengl/filters.c b/modules/video_output/opengl/filters.c
+index b93c1ee2fe..abd00b755b 100644
+--- a/modules/video_output/opengl/filters.c
++++ b/modules/video_output/opengl/filters.c
+@@ -183,6 +183,7 @@ vlc_gl_filters_Append(struct vlc_gl_filters *filters, const char *name,
+     struct vlc_gl_filter *filter = vlc_gl_filter_New(filters->gl, filters->api);
+     if (!filter)
+         return NULL;
++    filter->gl = filters->gl;
+ 
+     struct vlc_gl_filter_priv *priv = vlc_gl_filter_PRIV(filter);
+ 
+diff --git a/modules/video_output/opengl/interop_emscripten.cpp b/modules/video_output/opengl/interop_emscripten.cpp
+new file mode 100644
+index 0000000000..f745fcfef4
+--- /dev/null
++++ b/modules/video_output/opengl/interop_emscripten.cpp
+@@ -0,0 +1,287 @@
++/*****************************************************************************
++ * interop_emscripten.cpp: OpenGL Emscripten/Webcodec opaque converter
++ *****************************************************************************
++ * Copyright (C) 2021 VLC authors and VideoLAN
++ *
++ * 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
++
++//#ifndef __EMSCRIPTEN__
++//# error this file must be built with emscripten
++//#endif
++
++#include <vlc_common.h>
++#include <vlc_plugin.h>
++#include <vlc_filter.h>
++
++#include "interop.h"
++#include "gl_common.h"
++#include <cassert>
++#include "../emscripten/common.h"
++
++#include <emscripten.h>
++#include <emscripten/em_js.h>
++#include <emscripten/val.h>
++#include <emscripten/html5_webgl.h>
++
++struct EmscriptenInterop
++{
++    struct
++    {
++        PFNGLBINDTEXTUREPROC BindTexture;
++    } gl;
++};
++
++static int
++tc_emscripten_op_allocate_textures(const struct vlc_gl_interop *interop, GLuint *textures,
++                          const GLsizei *tex_width, const GLsizei *tex_height)
++{
++    (void) tex_width; (void) tex_height;
++    assert(textures[0] != 0);
++
++    return VLC_SUCCESS;
++}
++
++EM_ASYNC_JS(void, bindVideoFrame, (int pictureIdx), {
++    let frame = await Module.awaitFrame(pictureIdx);
++    if (!frame)
++        return;
++
++    let glCtx = Module.glCtx;
++    glCtx.texImage2D(glCtx.TEXTURE_2D, 0, glCtx.RGBA, frame.codedWidth, frame.codedHeight, 0,
++                glCtx.RGBA, glCtx.UNSIGNED_BYTE, frame);
++})
++
++static int
++tc_emscripten_op_update(const struct vlc_gl_interop *interop, GLuint *textures,
++               const GLsizei *tex_width, const GLsizei *tex_height,
++               picture_t *pic, const size_t *plane_offset)
++{
++    auto sys = static_cast<EmscriptenInterop *>( interop->priv );
++
++    sys->gl.BindTexture(interop->tex_target, textures[0]);
++
++    auto picCtx = PictureContextPrivate(pic->context);
++    bindVideoFrame(picCtx->pictureIdx);
++
++    return VLC_SUCCESS;
++}
++
++extern "C"
++{
++EMSCRIPTEN_KEEPALIVE int getDecoderWorker(vlc_video_context* vctx)
++{
++    auto wcCtx = static_cast<webcodec_context*>(
++                vlc_video_context_GetPrivate(vctx, VLC_VIDEO_CONTEXT_WEBCODEC));
++    if (!vctx)
++        return 0;
++    return wcCtx->decoder_worker;
++}
++}
++
++EM_JS(void, setupMessagePort, (vlc_video_context* vctx), {
++    function onDecoderMessage(msg) {
++        let data = msg['data'];
++        if (data.customCmd == 'displayFrame') {
++            let pictureIdx = data.pictureId;
++            let frame = data['frame'];
++            Module.glConv.frameResolver( frame );
++        }
++    };
++    Module.msgChannel = new MessageChannel();
++    Module.msgChannel.port1.onmessage = onDecoderMessage;
++    let workerId = _getDecoderWorker(vctx);
++    if (workerId == 0)
++        return;
++    self.postMessage({
++        customCmd: 'transferMessagePort',
++        targetThread: workerId,
++        transferList: [Module.msgChannel.port2],
++    }, [Module.msgChannel.port2]);
++});
++
++EM_JS(void, initGlConvWorker, (int maxPictures), {
++    Module.glConv = {};
++    Module.glConv.lastFrame = {
++        pictureIdx: -1,
++        frame: undefined
++    };
++
++    Module.awaitFrame = async function(pictureIdx) {
++        if (Module.glConv.lastFrame.pictureIdx == pictureIdx) {
++            return Module.glConv.lastFrame.frame;
++        }
++        let p = new Promise((resolve, reject) => {
++            Module.glConv.frameResolver = resolve;
++            Module.msgChannel.port1.postMessage({
++                'customCmd': 'sendFrame',
++                'pictureIdx': pictureIdx,
++            });
++
++        });
++        let frame = await p;
++        Module.glConv.frameResolver = undefined;
++        if (Module.glConv.lastFrame.frame)
++            Module.glConv.lastFrame.frame.close();
++        Module.glConv.lastFrame.frame = frame;
++        Module.glConv.lastFrame.pictureIdx = pictureIdx;
++        return frame;
++    }
++})
++
++EM_JS(void, closeMessagePort, (void), {
++    if (Module.msgChannel)
++        delete Module.msgChannel;
++});
++
++static void
++Close(struct vlc_gl_interop *)
++{
++    closeMessagePort();
++}
++
++static int
++Open(vlc_object_t *obj)
++{
++    auto interop = reinterpret_cast<vlc_gl_interop*>( obj );
++
++    if (interop->fmt_in.i_chroma != VLC_CODEC_WEBCODEC_OPAQUE)
++        return VLC_EGENERIC;
++
++    EmscriptenInterop *sys = (decltype(sys))
++        vlc_obj_malloc(VLC_OBJECT(interop), sizeof *sys);
++    if (sys == NULL)
++        return VLC_EGENERIC;
++    sys->gl.BindTexture = (decltype(sys->gl.BindTexture))
++        vlc_gl_GetProcAddress(interop->gl, "glBindTexture");
++    if (sys->gl.BindTexture == NULL)
++        return VLC_EGENERIC;
++
++    static const struct vlc_gl_interop_ops ops = {
++        .allocate_textures = tc_emscripten_op_allocate_textures,
++        .update_textures = tc_emscripten_op_update,
++        .close = Close,
++    };
++    interop->ops = &ops;
++    initGlConvWorker(WEBCODEC_MAX_PICTURES);
++    setupMessagePort(interop->vctx);
++
++    interop->tex_target = GL_TEXTURE_2D;
++    interop->fmt_out.i_chroma = VLC_CODEC_RGBA;
++    interop->fmt_out.space = COLOR_SPACE_UNDEF;
++    interop->tex_count = 1;
++    interop->texs[0] = vlc_gl_interop::vlc_gl_tex_cfg{
++            /*.w =*/ { 1, 1 },
++            /*.h =*/ { 1, 1 },
++            /*.internal =*/ GL_RGBA,
++            /*.format = */GL_RGBA,
++            /*.type = */GL_UNSIGNED_BYTE,
++    };
++    interop->fmt_out.i_rmask = 0xFF0000;
++    interop->fmt_out.i_gmask = 0x00FF00;
++    interop->fmt_out.i_bmask = 0x0000FF;
++    interop->priv = sys;
++
++    interop->fmt_out.orientation = ORIENT_VFLIPPED;
++
++    return VLC_SUCCESS;
++}
++
++EM_ASYNC_JS(void, CopyFrameToBuffer, (int pictureIdx, emscripten::EM_VAL infoHandle), {
++    let info = Emval.toValue(infoHandle);
++    let frame = await Module.awaitFrame(pictureIdx);
++    let copyOpts = {
++        rect: frame.codedRect,
++        layout: info.layout
++    };
++    await frame.copyTo(info.buffer, copyOpts);
++});
++
++static picture_t* vlc_webcodec_UploadVideoFrame(filter_t* filter, picture_t* src)
++{
++    auto pic = filter_NewPicture(filter);
++    if (unlikely(pic == nullptr))
++    {
++        picture_Release(src);
++        return nullptr;
++    }
++
++    auto pictureIdx = PictureContextPrivate(src->context)->pictureIdx;
++
++    size_t picSize = 0;
++    auto info = emscripten::val::object();
++    auto planesLayout = emscripten::val::array();
++    for ( auto i = 0; i < pic->i_planes; ++i )
++    {
++        auto p = pic->p[i];
++        auto layout = emscripten::val::object();
++        layout.set( "offset", picSize );
++        layout.set( "stride", p.i_pitch );
++        planesLayout.call<void>( "push", std::move( layout ) );
++        picSize += p.i_lines * p.i_pitch;
++    }
++
++    info.set( "buffer", emscripten::typed_memory_view( picSize, pic->p[0].p_pixels ) );
++    info.set( "layout", std::move( planesLayout ) );
++
++    CopyFrameToBuffer( pictureIdx, info.as_handle() );
++
++    pic->date = src->date;
++    picture_Release(src);
++    return pic;
++}
++
++static void vlc_webcodec_CloseConverter(filter_t*)
++{
++    closeMessagePort();
++}
++
++static const struct vlc_filter_operations video_frame_to_sw_ops = {
++    .filter_video = vlc_webcodec_UploadVideoFrame,
++    .close = vlc_webcodec_CloseConverter,
++};
++
++static int OpenConverter(filter_t* filter)
++{
++    if (filter->fmt_in.video.i_height != filter->fmt_out.video.i_height
++     || filter->fmt_in.video.i_width != filter->fmt_out.video.i_width
++     || filter->fmt_in.video.orientation != filter->fmt_out.video.orientation
++     || filter->fmt_in.video.i_chroma != VLC_CODEC_WEBCODEC_OPAQUE
++     || filter->fmt_out.video.i_chroma != VLC_CODEC_I420 )
++    {
++        return VLC_EGENERIC;
++    }
++    initGlConvWorker(WEBCODEC_MAX_PICTURES);
++    setupMessagePort(filter->vctx_in);
++
++    filter->ops = &video_frame_to_sw_ops;
++
++    return VLC_SUCCESS;
++}
++
++vlc_module_begin ()
++    set_description("Emscripten OpenGL SurfaceTexture converter")
++    set_capability("glinterop", 1)
++    set_callback(Open)
++    set_subcategory(SUBCAT_VIDEO_VOUT)
++
++    add_submodule()
++        set_subcategory(SUBCAT_VIDEO_VFILTER)
++        set_callback_video_converter(OpenConverter, 10)
++vlc_module_end ()
+diff --git a/modules/video_output/opengl/renderer.c b/modules/video_output/opengl/renderer.c
+index aebbb84e67..092fc7b7e4 100644
+--- a/modules/video_output/opengl/renderer.c
++++ b/modules/video_output/opengl/renderer.c
+@@ -41,6 +41,7 @@
+ #include "gl_util.h"
+ #include "vout_helper.h"
+ #include "sampler.h"
++#include "picture.h"
+ 
+ #define SPHERE_RADIUS 1.f
+ 
+diff --git a/modules/video_output/opengl/sampler.c b/modules/video_output/opengl/sampler.c
+index 846cbc75ab..80f9d43e10 100644
+--- a/modules/video_output/opengl/sampler.c
++++ b/modules/video_output/opengl/sampler.c
+@@ -37,6 +37,7 @@
+ #include "gl_api.h"
+ #include "gl_common.h"
+ #include "gl_util.h"
++#include "picture.h"
+ 
+ struct vlc_gl_sampler_priv {
+     struct vlc_gl_sampler sampler;
+@@ -292,25 +293,30 @@ sampler_base_load(struct vlc_gl_sampler *sampler)
+     struct vlc_gl_format *glfmt = &sampler->glfmt;
+     struct vlc_gl_picture *pic = &priv->pic;
+ 
+-    if (priv->yuv_color)
++    if (priv->yuv_color && priv->uloc.ConvMatrix != -1)
+         vt->UniformMatrix4fv(priv->uloc.ConvMatrix, 1, GL_FALSE,
+                              priv->conv_matrix);
+ 
+     for (unsigned i = 0; i < glfmt->tex_count; ++i)
+     {
++        if (priv->uloc.Textures[i] == -1)
++            continue;
+         vt->Uniform1i(priv->uloc.Textures[i], i);
+ 
+         assert(pic->textures[i] != 0);
+         vt->ActiveTexture(GL_TEXTURE0 + i);
+         vt->BindTexture(glfmt->tex_target, pic->textures[i]);
+-
+     }
+ 
+     if (glfmt->tex_target == GL_TEXTURE_RECTANGLE)
+     {
+         for (unsigned i = 0; i < glfmt->tex_count; ++i)
++        {
++            if (priv->uloc.TexSizes[i] == -1)
++                continue;
+             vt->Uniform2f(priv->uloc.TexSizes[i], glfmt->tex_widths[i],
+                           glfmt->tex_heights[i]);
++        }
+     }
+ 
+ #ifdef HAVE_LIBPLACEBO
+@@ -365,6 +371,9 @@ sampler_xyz12_load(struct vlc_gl_sampler *sampler)
+     struct vlc_gl_format *glfmt = &sampler->glfmt;
+     struct vlc_gl_picture *pic = &priv->pic;
+ 
++    if (priv->uloc.Textures[0] == -1)
++        return;
++
+     vt->Uniform1i(priv->uloc.Textures[0], 0);
+ 
+     assert(pic->textures[0] != 0);
+@@ -537,13 +546,17 @@ sampler_planes_load(struct vlc_gl_sampler *sampler)
+     struct vlc_gl_format *glfmt = &sampler->glfmt;
+     struct vlc_gl_picture *pic = &priv->pic;
+ 
+-    vt->Uniform1i(priv->uloc.Textures[0], 0);
++    if (priv->uloc.Textures[0] != -1)
++    {
++        vt->Uniform1i(priv->uloc.Textures[0], 0);
+ 
+-    assert(pic->textures[plane] != 0);
+-    vt->ActiveTexture(GL_TEXTURE0);
+-    vt->BindTexture(glfmt->tex_target, pic->textures[plane]);
++        assert(pic->textures[plane] != 0);
++        vt->ActiveTexture(GL_TEXTURE0);
++        vt->BindTexture(glfmt->tex_target, pic->textures[plane]);
++    }
+ 
+-    if (glfmt->tex_target == GL_TEXTURE_RECTANGLE)
++    if (glfmt->tex_target == GL_TEXTURE_RECTANGLE &&
++        priv->uloc.TexSizes[0] != -1)
+     {
+         vt->Uniform2f(priv->uloc.TexSizes[0], glfmt->tex_widths[plane],
+                       glfmt->tex_heights[plane]);
+diff --git a/modules/video_output/opengl/sampler.h b/modules/video_output/opengl/sampler.h
+index c779f2a9ba..2a5f0ef063 100644
+--- a/modules/video_output/opengl/sampler.h
++++ b/modules/video_output/opengl/sampler.h
+@@ -38,6 +38,11 @@ extern "C"
+ {
+ #endif
+ 
++#ifdef __cplusplus
++extern "C"
++{
++#endif
++
+ /**
+  * The purpose of a sampler is to provide pixel values of a VLC input picture,
+  * stored in any format.
+@@ -199,4 +204,8 @@ vlc_gl_sampler_SelectPlane(struct vlc_gl_sampler *sampler, unsigned plane);
+ }
+ #endif
+ 
++#ifdef __cplusplus
++}
++#endif
++
+ #endif
+diff --git a/src/misc/fourcc.c b/src/misc/fourcc.c
+index a0cf8cc3a8..212d07360f 100644
+--- a/src/misc/fourcc.c
++++ b/src/misc/fourcc.c
+@@ -838,6 +838,8 @@ static const struct
+     { { VLC_CODEC_VAAPI_420, VLC_CODEC_VAAPI_420_10BPP },
+                                                FAKE_FMT() },
+ 
++    { { VLC_CODEC_WEBCODEC_OPAQUE }, FAKE_FMT() },
++
+     { { 0 },                                   FAKE_FMT() }
+ };
+ 
+diff --git a/src/video_output/display.c b/src/video_output/display.c
+index aae3a26976..43a77c2387 100644
+--- a/src/video_output/display.c
++++ b/src/video_output/display.c
+@@ -629,7 +629,7 @@ void vout_SetDisplayViewpoint(vout_display_t *vd,
+     }
+ }
+ 
+-vout_display_t *vout_display_New(vlc_object_t *parent,
++vout_display_t *(vout_display_New)(vlc_object_t *parent,
+                                  const video_format_t *source,
+                                  vlc_video_context *vctx,
+                                  const vout_display_cfg_t *cfg,
+diff --git a/src/video_output/video_output.c b/src/video_output/video_output.c
+index 2a18e01ba7..3b35b2bf69 100644
+--- a/src/video_output/video_output.c
++++ b/src/video_output/video_output.c
+@@ -102,6 +102,13 @@ typedef struct vout_thread_sys_t
+     vout_control_t  control;
+     atomic_bool     control_is_terminated; // shutdown the vout thread
+     vlc_thread_t    thread;
++    vlc_sem_t       thread_ready_sem;
++    bool            thread_success;
++
++    // Begin lazy ass chouquette test:
++    vlc_video_context* vctx_vout_start;
++    const vout_configuration_t *cfg_vout_start;
++    //end of lazy ass chouquette test. don't use this for anything else than vout_start
+ 
+     struct {
+         vlc_tick_t  date;
+@@ -1725,6 +1732,8 @@ error:
+     return VLC_EGENERIC;
+ }
+ 
++
++static void vout_DisableWindow(vout_thread_sys_t *sys);
+ /*****************************************************************************
+  * Thread: video output thread
+  *****************************************************************************
+@@ -1739,6 +1748,17 @@ static void *Thread(void *object)
+ 
+     vlc_thread_set_name("vlc-vout");
+ 
++    if (vout_Start(vout, sys->vctx_vout_start, sys->cfg_vout_start))
++    {
++        sys->thread_success = false;
++        msg_Err(&vout->obj, "video output display creation failed");
++        vout_DisableWindow(vout);
++        vlc_sem_post(&sys->thread_ready_sem);
++        return NULL;
++    }
++    sys->thread_success = true;
++    vlc_sem_post(&sys->thread_ready_sem);
++
+     vlc_tick_t deadline = VLC_TICK_INVALID;
+ 
+     for (;;) {
+@@ -2129,19 +2149,18 @@ int vout_Request(const vout_configuration_t *cfg, vlc_video_context *vctx, input
+     sys->str_id = cfg->str_id;
+     sys->clock = cfg->clock;
+     sys->delay = 0;
++    vlc_sem_init(&sys->thread_ready_sem, 0);
++
++    sys->cfg_vout_start = cfg;
++    sys->vctx_vout_start = vctx;
+ 
+-    if (vout_Start(vout, vctx, cfg))
+-    {
+-        msg_Err(cfg->vout, "video output display creation failed");
+-        vout_DisableWindow(vout);
+-        return -1;
+-    }
+     atomic_store(&sys->control_is_terminated, false);
+     if (vlc_clone(&sys->thread, Thread, vout)) {
+         vout_ReleaseDisplay(vout);
+         vout_DisableWindow(vout);
+         return -1;
+     }
++    vlc_sem_wait(&sys->thread_ready_sem);
+ 
+     if (input != NULL && sys->spu)
+         spu_Attach(sys->spu, input);
+-- 
+2.35.1
+
diff --git a/vlc_patches/demo_alpha/0002-wip-prepare-access-module.patch b/vlc_patches/demo_alpha/0002-wip-prepare-access-module.patch
new file mode 100644
index 0000000000000000000000000000000000000000..16a47e6c398bccda3096f9f148b17b1c56174ed4
--- /dev/null
+++ b/vlc_patches/demo_alpha/0002-wip-prepare-access-module.patch
@@ -0,0 +1,238 @@
+From 635cea37412f500c29fad197fa5a1ba99b7b7e0a Mon Sep 17 00:00:00 2001
+From: Mehdi Sabwat <mehdi@videolabs.io>
+Date: Thu, 9 Jun 2022 21:05:04 +0200
+Subject: [PATCH 1/2] wip: prepare access module
+
+---
+ modules/access/Makefile.am |   5 +
+ modules/access/emjsfile.c  | 202 +++++++++++++++++++++++++++++++++++++
+ 2 files changed, 207 insertions(+)
+ create mode 100644 modules/access/emjsfile.c
+
+diff --git a/modules/access/Makefile.am b/modules/access/Makefile.am
+index d4f6b5f10a..cc827027df 100644
+--- a/modules/access/Makefile.am
++++ b/modules/access/Makefile.am
+@@ -26,6 +26,11 @@ libfilesystem_plugin_la_LIBADD = -lshlwapi
+ endif
+ access_LTLIBRARIES += libfilesystem_plugin.la
+ 
++if HAVE_EMSCRIPTEN
++libemjsfile_plugin_la_SOURCES = access/emjsfile.c
++access_LTLIBRARIES += libemjsfile_plugin.la
++endif
++
+ libidummy_plugin_la_SOURCES = access/idummy.c
+ access_LTLIBRARIES += libidummy_plugin.la
+ 
+diff --git a/modules/access/emjsfile.c b/modules/access/emjsfile.c
+new file mode 100644
+index 0000000000..f0736bc34c
+--- /dev/null
++++ b/modules/access/emjsfile.c
+@@ -0,0 +1,202 @@
++/*****************************************************************************
++ * emjsfile.c: emscripten js file access plugin
++ *****************************************************************************
++ * Copyright (C) 2022 VLC authors Videolabs, and VideoLAN
++ *
++ *
++ * 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 <vlc_common.h>
++#include <vlc_plugin.h>
++#include <vlc_access.h>
++#include <vlc_threads.h>
++
++#include <emscripten.h>
++
++typedef struct
++{
++    size_t offset;
++    size_t js_file_size;
++} access_sys_t;
++
++static ssize_t Read (stream_t *p_access, void *buffer, size_t size) {
++    access_sys_t *p_sys = p_access->p_sys;
++
++    size_t offset = p_sys->offset;
++    size_t js_file_size = p_sys->js_file_size; 
++
++    if (offset == js_file_size)
++        return 0;
++    if (size > offset + js_file_size) {
++        size = js_file_size - offset;
++    }
++    EM_ASM({
++            const offset = $0;
++            const buffer = $1;
++            const size = $2;
++            var blob = Module.worker_js_file.slice(offset, offset + size);
++            HEAPU8.set(new Uint8Array(Module.reader.readAsArrayBuffer(blob)), buffer);
++        }, offset, buffer, size);
++    p_sys->offset += size;
++    return size;
++}
++
++static int FileSeek (stream_t *p_access, uint64_t offset) {
++    access_sys_t *p_sys = p_access->p_sys;
++    size_t js_file_size = p_sys->js_file_size;
++
++    if (offset > js_file_size) {
++        msg_Err(p_access, "emjsfile: could not seek!");
++        return VLC_EGENERIC;
++    }
++    p_sys->offset = offset;
++    return VLC_SUCCESS;
++}
++
++static int FileControl( stream_t *p_access, int i_query, va_list args )
++{
++    bool    *pb_bool;
++    vlc_tick_t *pi_64;
++
++    switch( i_query )
++    {
++        case STREAM_CAN_SEEK:
++        case STREAM_CAN_FASTSEEK:
++            pb_bool = va_arg( args, bool * );
++            *pb_bool = (p_access->pf_seek != NULL);
++            break;
++
++        case STREAM_CAN_PAUSE:
++        case STREAM_CAN_CONTROL_PACE:
++            pb_bool = va_arg( args, bool * );
++            *pb_bool = 1;
++            break;
++
++        case STREAM_GET_SIZE:
++        {            
++            *va_arg( args, uint64_t * ) = EM_ASM_DOUBLE({ return Module.worker_js_file.size });
++            break;
++        }
++
++        case STREAM_GET_PTS_DELAY:
++            pi_64 = va_arg( args, vlc_tick_t * );
++            *pi_64 = VLC_TICK_FROM_MS(
++                var_InheritInteger (p_access, "file-caching") );
++            break;
++
++        case STREAM_SET_PAUSE_STATE:
++            break;
++
++        default:
++            return VLC_EGENERIC;
++
++    }
++    return VLC_SUCCESS;
++}
++
++EM_ASYNC_JS(int, init_js_file, (), {
++        let p = new Promise((resolve, reject) => {
++                self.addEventListener('message', function(e) {
++                        let msg = e['data'];
++                        if (msg.type === 'FileResult') {
++                            if (msg.file !== undefined) {
++                                Module.worker_js_file = msg.file;
++                                Module.reader = new FileReaderSync();
++                                resolve();
++                            }
++                            else {
++                                reject();
++                            }
++                        }
++                    });
++            });
++        self.postMessage({ cmd: "customCmd", type: "requestFile"});
++        try {
++            await p;
++        }
++        catch (error) {
++            console.error("vlc_access error in init_js_file(): ", error);
++            return 1;
++        }
++        return 0;
++    });
++
++int EmFileOpen( vlc_object_t *p_this ) {
++    stream_t *p_access = (stream_t*)p_this;
++    /*
++      This block will run in the main thread, to access the DOM.
++      When the user selects a file, it is assigned to the Module.vlc_access_file
++      array.
++      
++      We listen to 'message' events so that when the file is requested by the
++      input thread, we can answer and send the File object from the main thread.
++    */
++    MAIN_THREAD_EM_ASM({
++            const thread_id = $0;
++            let w = Module.PThread.pthreads[thread_id].worker;
++            w.addEventListener('message', function (e) {
++                    var msg = e.data;
++                    if (msg.type === "requestFile") {
++                        w.postMessage({ cmd: "customCmd",
++                                type: "FileResult",
++                                file: Module.vlc_access_file.pop()
++                            });
++                    }
++                });
++        }, pthread_self());
++
++    /*
++      Request the file from the main thread.
++    */
++    if (init_js_file()) {
++        return VLC_EGENERIC;
++    }
++
++    access_sys_t *p_sys = vlc_obj_malloc(p_this, sizeof (*p_sys));
++    if (unlikely(p_sys == NULL))
++        return VLC_ENOMEM;
++
++    p_access->pf_read = Read;
++    p_access->pf_block = NULL;
++    p_access->pf_control = FileControl;
++    p_access->pf_seek = FileSeek;
++    p_access->p_sys = p_sys;
++    p_sys->js_file_size = EM_ASM_DOUBLE({ return Module.worker_js_file.size });
++    p_sys->offset = 0;
++
++    return VLC_SUCCESS;
++}
++
++void EmFileClose (vlc_object_t * p_this) {
++    VLC_UNUSED(p_this);
++    EM_ASM({
++            Module.worker_js_file = undefined;
++            Module.reader = undefined;            
++        });
++}
++
++vlc_module_begin ()
++    set_description( N_("Emscripten module to allow reading local files from the DOM's <input>") )
++    set_shortname( N_("Emscripten Local File Input") )
++    set_subcategory( SUBCAT_INPUT_ACCESS )
++    set_capability( "access", 50 )
++    add_shortcut( "emjsfile" )
++    set_callbacks( EmFileOpen, EmFileClose )
++vlc_module_end()
+-- 
+2.35.1
+
diff --git a/vlccore_opengl.html b/vlccore_opengl.html
new file mode 100644
index 0000000000000000000000000000000000000000..80ab11c03cad9de8b5ebe76849bffa8a33bbc85b
--- /dev/null
+++ b/vlccore_opengl.html
@@ -0,0 +1,222 @@
+<!doctype html>
+<html lang="en-us">
+<head>
+    <meta charset="utf-8">
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+    <title>VLC.js: OpenGL test suite</title>
+    <style>
+        .emscripten {
+            text-align: center;
+        }
+        body {
+            background-color: #343a40;
+        }
+        #canvas {
+            border:0 !important;
+            display: flex;
+            margin: 0 auto;
+            background-color: #000;
+        }
+        #overlay {
+            border:0 !important;
+            display: flex;
+            margin: 0 auto;
+        }
+        #stack {
+            display: grid;
+        }
+        #stack > canvas {
+            grid-column: 1;
+            grid-row: 1;
+        }
+        #spinner {
+            display: none;
+        }
+        #status {
+            display: none;
+        }
+        #progress {
+            margin-left: auto;
+            margin-right: auto;
+        }
+        #logo {
+            width: 64px;
+            height: 64px;
+        }
+        #em_logo {
+            position: absolute;
+            width: 200px;
+            height: 80px;
+        }
+        #point {
+            color: #fff;
+            font-size: 60px;
+        }
+        #js {
+            color: #fff;
+            font-size: 60px;
+            font-weight: bold;
+        }
+        .banner {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+        }
+        .vlc_head {
+            flex: 1 1 0px;
+        }
+    </style>
+</head>
+<body>
+    <div class="emscripten_border">
+    <div class="emscripten">
+        <div class="banner">
+            <img src="./assets/emscripten.svg" id ="em_logo">
+            <div class="vlc_head">
+                <img src="./assets/VLC_Icon.svg" id="logo">
+                <span id="point">.</span>
+                <span id="js">JS</span>
+            </div>
+        </div>
+        <progress id="progress" value="0" max="100"></progress>
+        <div class="spinner" id='spinner'></div>
+        <div class="emscripten" id="status">Downloading...</div>
+    </div>
+    </div>
+
+
+    <h3> 1/ Checking that we can setup OpenGL correctly </h3>
+    <p>
+        As a first step, we'll need to setup a vlc_gl_t instance on a canvas.
+        The test succeeds if we can set the color to red.
+    </p>
+    <div>
+        <canvas
+          class="emscripten" id="canvas_step1"
+          oncontextmenu="event.preventDefault()" tabindex=-1
+          width=400 height=300
+        ></canvas>
+    </div>
+
+    <h3> 2/ Checking that we can setup different canvas</h3>
+    <p>
+        As a second step, we check whether we can setup multiple OpenGL
+        contexts. The canvas should appear green.
+    </p>
+
+    <div>
+        <canvas
+          class="emscripten" id="canvas_step2"
+          oncontextmenu="event.preventDefault()" tabindex=-1
+          width=400 height=300
+        ></canvas>
+    </div>
+
+    <h3> 3/ Checking that canvas update is smooth</h3>
+    <p>
+        As a third step, we check how much the canvas update can be smooth.
+    </p>
+
+    <div>
+        <canvas
+          class="emscripten" id="canvas_step3"
+          oncontextmenu="event.preventDefault()" tabindex=-1
+          width=400 height=300
+        ></canvas>
+    </div>
+
+    <h3> 4/ Checking that coordinates are correct with glmock</h3>
+    <p>
+    </p>
+
+    <div>
+        <canvas
+          class="emscripten" id="canvas_step4"
+          oncontextmenu="event.preventDefault()" tabindex=-1
+          width=400 height=300
+        ></canvas>
+    </div>
+
+    <h3> 5/ Check interop behaviour </h3>
+    <p>
+    </p>
+
+    <div>
+        <canvas
+          class="emscripten" id="canvas_step5"
+          oncontextmenu="event.preventDefault()" tabindex=-1
+          width=400 height=300
+        ></canvas>
+    </div>
+
+    <h3> 7/ Check display behaviour </h3>
+    <p>
+    </p>
+
+    <div>
+        <canvas
+          class="emscripten" id="canvas_step7"
+          oncontextmenu="event.preventDefault()" tabindex=-1
+          width=400 height=300
+        ></canvas>
+    </div>
+
+
+    <script src="./opengl.js"></script>
+
+    <script type="module">
+      // VlcModule is a function generated by emscripten in experimental.js,
+      // that loads the wasm file and generates a module object from it.
+      // VlcModuleExt is an object defined in 'lib/module-loader.js' with a
+      // bunch of options; also, all the fields of VlcModuleExt are added to
+      // the returned Module.
+      const defaultModule = {
+          'onCustomMessage': function(msg) {
+            console.log('Received custom message');
+            console.log(msg);
+              if (msg.customCmd == 'getCanvas') {
+                 let name = 'canvas';
+                 if (msg.canvasId != undefined)
+                   name = msg.canvasId
+                  console.log('Replying to getCanvas("'+name+'")');
+                  let w = Module.PThread.pthreads[msg.threadId].worker;
+                  let canvas = document.getElementById(name);
+                  let offscreen = canvas.transferControlToOffscreen();
+                  // Ensure emscripten won't try to re-transfer canvas control
+                  // to offscreen
+                  console.log(offscreen);
+                  offscreen.controlTransferredOffscreen = true;
+                  w.postMessage({
+                      cmd: 'custom',
+                      customCmd: 'getCanvasResult',
+                      canvas: offscreen
+                  }, [offscreen]);
+              }
+          }
+      };
+      globalThis.Module = await initModule(defaultModule);
+//      globalThis.Module.postRun += [
+//        function() {
+//            // This should run after the wasm module is instantiated
+//            // before, the Pthread object won't be available
+//            PThread.receiveObjectTransfer = function (data) {
+//              // Transfer messages from worker threads to the main window.
+//              let event = new CustomEvent('worker_message', {
+//                detail: data.msg
+//              });
+//              window.dispatchEvent(event);
+//            };
+//        }];
+      //window.onerror = function(event) {
+      //  // TODO: do not warn on ok events like simulating an infinite loop or exitStatus
+      //  Module.setStatus('Exception thrown, see JavaScript console');
+      //  spinnerElement.style.display = 'none';
+      //  Module.setStatus = function(text) {
+      //    if (text) Module.printErr('[post-exception status] ' + text);
+      //  };
+      //};
+    </script>
+
+
+</body>
+</html>