fix(embedded-mpv): stop crashing on IPC for not-yet-created sessions

The renderer was reading session.id and forwarding it to the addon, but
during the loading window that id is the placeholder
"embedded-mpv-starting" set by createLoadingSession. If the user adjusted
volume, seeked, or toggled audio/subtitle/speed/aspect before the addon's
createSession returned the real id, that placeholder id reached the
addon — and the addon's getSessionOrThrow threw a raw std::runtime_error
which libc++abi terminated the process on.

Two fixes, defense in depth:

1. Renderer (session controller): use the canonical sessionId() signal,
   which is null until the addon hands back a real id, as the gate for all
   IPC calls. Wrap every IPC call in a guardIpc helper that swallows
   addon-side throws so a torn-down session or race won't surface as an
   uncaught promise rejection.

2. Native (embedded_mpv.mm): change getSessionOrThrow to take a
   Napi::Env and throw Napi::Error::New(env, ...) instead of
   std::runtime_error. node-addon-api converts Napi::Error to a JS
   exception cleanly; the previous std::runtime_error escaped the C++
   frame and aborted the process when the addon was built without
   NAPI_CPP_EXCEPTIONS translation. Refactor splits findSession (returns
   nullptr) from getSessionOrThrow (env-aware) so call paths that just
   probe a session's existence don't pay the throw cost.

The native fix needs an addon rebuild to take effect; the renderer fix
prevents the crash trigger immediately.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: f957cd9849e0
This commit is contained in:
4grayandClaude Opus 4.7 committed 2026-05-05 10:29:23 +02:00
1 parent 126025aa41
commit 9424dedafc
2 files changed
+73 -43

No files matched your search

@@ -166,18 +166,27 @@ void runOnMainSync(dispatch_block_t block)
dispatch_sync(dispatch_get_main_queue(), block);
}
std::shared_ptr<Session> getSessionOrThrow(const std::string& sessionId)
std::shared_ptr<Session> findSession(const std::string& sessionId)
{
std::lock_guard<std::mutex> sessionsLock(gSessionsMutex);
const auto iterator = gSessions.find(sessionId);
if (iterator == gSessions.end()) {
throw std::runtime_error("Embedded MPV session not found.");
return nullptr;
}
return iterator->second;
}
std::shared_ptr<Session> getSessionOrThrow(
Napi::Env env,
const std::string& sessionId)
{
auto session = findSession(sessionId);
if (!session) {
throw Napi::Error::New(env, "Embedded MPV session not found.");
}
return session;
}
void scheduleRender(const std::shared_ptr<Session>& session);
void requestRender(const std::shared_ptr<Session>& session);
void updateSessionError(const std::shared_ptr<Session>& session, const std::string& error);
@@ -1604,7 +1613,7 @@ Napi::Value LoadPlayback(const Napi::CallbackInfo& info)
const std::string sessionId = info[0].As<Napi::String>().Utf8Value();
const auto playback = info[1].As<Napi::Object>();
const auto session = getSessionOrThrow(sessionId);
const auto session = getSessionOrThrow(env, sessionId);
const std::string streamUrl = readOptionalString(playback, "streamUrl");
if (streamUrl.empty()) {
throw Napi::Error::New(env, "Embedded MPV playback requires a stream URL.");
@@ -1713,7 +1722,7 @@ Napi::Value SetBounds(const Napi::CallbackInfo& info)
const std::string sessionId = info[0].As<Napi::String>().Utf8Value();
const auto bounds = info[1].As<Napi::Object>();
const auto session = getSessionOrThrow(sessionId);
const auto session = getSessionOrThrow(env, sessionId);
setSessionFrame(
session,
@@ -1734,7 +1743,7 @@ Napi::Value SetPaused(const Napi::CallbackInfo& info)
}
const std::string sessionId = info[0].As<Napi::String>().Utf8Value();
const auto session = getSessionOrThrow(sessionId);
const auto session = getSessionOrThrow(env, sessionId);
int paused = info[1].As<Napi::Boolean>().Value() ? 1 : 0;
const int result = mpv_set_property_async(
session->handle,
@@ -1774,7 +1783,7 @@ Napi::Value Seek(const Napi::CallbackInfo& info)
}
const std::string sessionId = info[0].As<Napi::String>().Utf8Value();
const auto session = getSessionOrThrow(sessionId);
const auto session = getSessionOrThrow(env, sessionId);
const auto target = info[1].As<Napi::Number>().DoubleValue();
const std::string targetValue = std::to_string(target);
const char* command[] = {
@@ -1813,7 +1822,7 @@ Napi::Value SetVolume(const Napi::CallbackInfo& info)
}
const std::string sessionId = info[0].As<Napi::String>().Utf8Value();
const auto session = getSessionOrThrow(sessionId);
const auto session = getSessionOrThrow(env, sessionId);
double volume = clampVolumePercent(
info[1].As<Napi::Number>().DoubleValue()
);
@@ -1849,7 +1858,7 @@ Napi::Value SetAudioTrack(const Napi::CallbackInfo& info)
}
const std::string sessionId = info[0].As<Napi::String>().Utf8Value();
const auto session = getSessionOrThrow(sessionId);
const auto session = getSessionOrThrow(env, sessionId);
int64_t trackId = info[1].As<Napi::Number>().Int64Value();
const int result = mpv_set_property_async(
session->handle,
@@ -1886,7 +1895,7 @@ Napi::Value SetSubtitleTrack(const Napi::CallbackInfo& info)
}
const std::string sessionId = info[0].As<Napi::String>().Utf8Value();
const auto session = getSessionOrThrow(sessionId);
const auto session = getSessionOrThrow(env, sessionId);
int64_t trackId = info[1].As<Napi::Number>().Int64Value();
int result = -1;
@@ -1934,7 +1943,7 @@ Napi::Value SetSpeed(const Napi::CallbackInfo& info)
}
const std::string sessionId = info[0].As<Napi::String>().Utf8Value();
const auto session = getSessionOrThrow(sessionId);
const auto session = getSessionOrThrow(env, sessionId);
double speed = info[1].As<Napi::Number>().DoubleValue();
speed = std::clamp(speed, 0.25, 4.0);
@@ -1973,7 +1982,7 @@ Napi::Value SetAspect(const Napi::CallbackInfo& info)
}
const std::string sessionId = info[0].As<Napi::String>().Utf8Value();
const auto session = getSessionOrThrow(sessionId);
const auto session = getSessionOrThrow(env, sessionId);
std::string aspect = info[1].As<Napi::String>().Utf8Value();
if (aspect.empty()) {
aspect = "no";