Skip to content

Commit

Permalink
Expose methods to load OTIO files in WASM through Javascript (#70)
Browse files Browse the repository at this point in the history
* Add LoadString function
* Add LoadUrl function
* Export LoadUrl and LoadString through emscriptem
* Update html template to add OTIO load helpers
* Document WASM load methods

Signed-off-by: Austin Witherspoon <46503991+austinwitherspoon@users.noreply.github.com>
  • Loading branch information
austinwitherspoon authored Sep 28, 2024
1 parent ebbf128 commit b72b4de
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 6 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ elseif(EMSCRIPTEN)
target_sources(raven PUBLIC main_emscripten.cpp)
set(LIBS ${CMAKE_DL_LIBS} SDL2)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s USE_SDL=2 -s DISABLE_EXCEPTION_CATCHING=1")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s WASM=1 -s NO_EXIT_RUNTIME=0 -s ASSERTIONS=1 -s NO_FILESYSTEM=1 -s USE_PTHREADS=1 -s ALLOW_MEMORY_GROWTH=1 -Wl,--shared-memory,--no-check-features --shell-file ${CMAKE_CURRENT_LIST_DIR}/shell_minimal.html")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s WASM=1 -s NO_EXIT_RUNTIME=0 -s ASSERTIONS=1 -s FETCH -s USE_PTHREADS=1 -s ALLOW_MEMORY_GROWTH=1 -s EXPORTED_RUNTIME_METHODS=ccall,cwrap -Wl,--shared-memory,--no-check-features --shell-file ${CMAKE_CURRENT_LIST_DIR}/shell_minimal.html")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DIMGUI_DISABLE_FILE_FUNCTIONS")
set_target_properties(raven PROPERTIES SUFFIX .html)
target_link_libraries(raven PUBLIC ${LIBS})
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ You will need to install the [Emscripten toolchain](https://emscripten.org) firs
See also: `serve.py` as an alternative to `emrun`, and as
a reference for which HTTP headers are needed to host the WASM build.

You can load a file into WASM Raven a few ways:
- Add a JSON string to Module.otioLoadString in the HTML file
- Add a URL to Module.otioLoadURL in the HTML file
- Call Module.LoadString(otio_data) at runtime
- Call Module.LoadURL(otio_url) at runtime

Note: The WASM build of raven is missing some features - see the Help Wanted section below.

## Troubleshooting
Expand Down
26 changes: 26 additions & 0 deletions app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,32 @@ void LoadTimeline(otio::Timeline* timeline) {
SelectObject(timeline);
}

void LoadString(std::string json) {
auto start = std::chrono::high_resolution_clock::now();

otio::ErrorStatus error_status;
auto timeline = dynamic_cast<otio::Timeline*>(
otio::Timeline::from_json_string(json, &error_status));
if (!timeline || otio::is_error(error_status)) {
Message(
"Error loading JSON: %s",
otio_error_string(error_status).c_str());
return;
}

LoadTimeline(timeline);

appState.file_path = timeline->name().c_str();

auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> elapsed = (end - start);
double elapsed_seconds = elapsed.count();
Message(
"Loaded \"%s\" in %.3f seconds",
timeline->name().c_str(),
elapsed_seconds);
}

void LoadFile(std::string path) {
auto start = std::chrono::high_resolution_clock::now();

Expand Down
2 changes: 2 additions & 0 deletions app.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ void Log(const char* format, ...);
void Message(const char* format, ...);
std::string Format(const char* format, ...);

void LoadString(std::string json);

std::string otio_error_string(otio::ErrorStatus const& error_status);

void SelectObject(
Expand Down
7 changes: 7 additions & 0 deletions main.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,10 @@ void MainInit(int argc, char** argv, int initial_width, int initial_height);
void MainGui();
void MainCleanup();
void FileDropCallback(int count, const char** paths);

#ifdef EMSCRIPTEN
extern "C" {
void js_LoadUrl(char* url);
void js_LoadString(char* json);
}
#endif
48 changes: 45 additions & 3 deletions main_emscripten.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@
// See https://github.com/ocornut/imgui/pull/2492 as an example on how to do just that.

#include "imgui.h"
#include "imgui_impl_sdl2.h"
#include "imgui_impl_opengl3.h"
#include <stdio.h>
#include <emscripten.h>
#include "imgui_impl_sdl2.h"
#include <SDL.h>
#include <SDL_opengles2.h>
#include <emscripten.h>
#include <emscripten/fetch.h>
#include <iostream>
#include <stdio.h>

#include "app.h"
#include "main.h"

// Emscripten requires to have full control over the main loop. We're going to store our SDL book-keeping variables globally.
Expand Down Expand Up @@ -149,3 +152,42 @@ static void main_loop(void* arg)
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
SDL_GL_SwapWindow(g_Window);
}

void LoadUrlSuccess(emscripten_fetch_t* fetch) {
printf("Finished downloading %llu bytes from URL %s.\n", fetch->numBytes, fetch->url);

std::string otio_string = std::string(fetch->data, fetch->numBytes);
emscripten_fetch_close(fetch);

LoadString(otio_string);

appState.file_path = fetch->url;
}

void LoadUrlFailure(emscripten_fetch_t* fetch) {
printf("Downloading %s failed, HTTP failure status code: %d.\n", fetch->url, fetch->status);
emscripten_fetch_close(fetch);
}

void LoadUrl(std::string url) {
printf("Downloading %s...\n", url.c_str());
emscripten_fetch_attr_t attr;
emscripten_fetch_attr_init(&attr);
strcpy(attr.requestMethod, "GET");
attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY;
// This is async, so we can provide callbacks to handle the result
attr.onsuccess = LoadUrlSuccess;
attr.onerror = LoadUrlFailure;
emscripten_fetch(&attr, url.c_str());
}

extern "C" {
EMSCRIPTEN_KEEPALIVE
void js_LoadUrl(char* url) {
LoadUrl(std::string(url));
}
EMSCRIPTEN_KEEPALIVE
void js_LoadString(char* json) {
LoadString(std::string(json));
}
}
37 changes: 35 additions & 2 deletions shell_minimal.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,42 @@
<body>
<canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()"></canvas>
<script type='text/javascript'>

/**
* Wrap and expose functions from Raven/emscripten to the browser.
*/
function exposeRavenFunctions() {
Module.LoadString = function (str) {
// This is annoying, but we need to allocate memory manually on the wasm heap
// to avoid a stack overflow if you pass in a very large string.
var lengthBytes = lengthBytesUTF8(str) + 1;
var stringOnWasmHeap = _malloc(lengthBytes);
stringToUTF8(str, stringOnWasmHeap, lengthBytes);

Module.ccall("js_LoadString", "number", ["number"], [stringOnWasmHeap]);

// ... And free it when we're done.
_free(stringOnWasmHeap);
}
Module.LoadUrl = Module.cwrap("js_LoadUrl", "number", ["string"]);
}

/**
* Once raven loads, load any OTIO timeline that was passed in.
*/
function ravenPostRun() {
if (Module.otioLoadUrl) {
Module.LoadUrl(Module.otioLoadUrl);
} else if (Module.otioLoadString) {
Module.LoadString(Module.otioLoadString);
}
}

var Module = {
preRun: [],
postRun: [],
// otioLoadUrl: "",
// otioLoadString: "",
preRun: [exposeRavenFunctions],
postRun: [ravenPostRun],
print: (function() {
return function(text) {
text = Array.prototype.slice.call(arguments).join(' ');
Expand Down

0 comments on commit b72b4de

Please sign in to comment.