[vlc-commits] [Git][videolan/vlc][master] emscripten: add js File access plugin
Hugo Beauzée-Luyssen (@chouquette)
gitlab at videolan.org
Sun Aug 7 11:58:19 UTC 2022
Hugo Beauzée-Luyssen pushed to branch master at VideoLAN / VLC
Commits:
9d2f5d61 by Mehdi Sabwat at 2022-08-07T11:43:42+00:00
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>
```
- - - - -
2 changed files:
- modules/access/Makefile.am
- + modules/access/emjsfile.c
Changes:
=====================================
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
=====================================
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()
View it on GitLab: https://code.videolan.org/videolan/vlc/-/commit/9d2f5d612583c7a781e7160e8d75dcb3f09eb94e
--
View it on GitLab: https://code.videolan.org/videolan/vlc/-/commit/9d2f5d612583c7a781e7160e8d75dcb3f09eb94e
You're receiving this email because of your account on code.videolan.org.
VideoLAN code repository instance
More information about the vlc-commits
mailing list