mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-09 01:16:15 -08:00
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:
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";
|
||||
|
||||
@@ -194,13 +194,16 @@ export class EmbeddedMpvSessionController {
|
||||
}
|
||||
|
||||
async togglePaused(): Promise<void> {
|
||||
const id = this.sessionId();
|
||||
const session = this.session();
|
||||
if (!session?.id || !window.electron?.setEmbeddedMpvPaused) {
|
||||
if (!id || !session || !window.electron?.setEmbeddedMpvPaused) {
|
||||
return;
|
||||
}
|
||||
const updated = await window.electron.setEmbeddedMpvPaused(
|
||||
session.id,
|
||||
session.status !== 'paused'
|
||||
const updated = await this.guardIpc(() =>
|
||||
window.electron!.setEmbeddedMpvPaused(
|
||||
id,
|
||||
session.status !== 'paused'
|
||||
)
|
||||
);
|
||||
if (updated) {
|
||||
this.session.set(updated);
|
||||
@@ -208,12 +211,15 @@ export class EmbeddedMpvSessionController {
|
||||
}
|
||||
|
||||
async seekBy(deltaSeconds: number): Promise<boolean> {
|
||||
const id = this.sessionId();
|
||||
const session = this.session();
|
||||
if (!session?.id || !window.electron?.seekEmbeddedMpv) {
|
||||
if (!id || !session || !window.electron?.seekEmbeddedMpv) {
|
||||
return false;
|
||||
}
|
||||
const next = Math.max(0, session.positionSeconds + deltaSeconds);
|
||||
const updated = await window.electron.seekEmbeddedMpv(session.id, next);
|
||||
const updated = await this.guardIpc(() =>
|
||||
window.electron!.seekEmbeddedMpv(id, next)
|
||||
);
|
||||
if (updated) {
|
||||
this.session.set(updated);
|
||||
}
|
||||
@@ -221,35 +227,38 @@ export class EmbeddedMpvSessionController {
|
||||
}
|
||||
|
||||
async seekTo(seconds: number): Promise<void> {
|
||||
const session = this.session();
|
||||
if (!session?.id || !window.electron?.seekEmbeddedMpv) {
|
||||
const id = this.sessionId();
|
||||
if (!id || !window.electron?.seekEmbeddedMpv) {
|
||||
return;
|
||||
}
|
||||
const updated = await window.electron.seekEmbeddedMpv(session.id, seconds);
|
||||
const updated = await this.guardIpc(() =>
|
||||
window.electron!.seekEmbeddedMpv(id, seconds)
|
||||
);
|
||||
if (updated) {
|
||||
this.session.set(updated);
|
||||
}
|
||||
}
|
||||
|
||||
async applyVolume(value: number): Promise<void> {
|
||||
const session = this.session();
|
||||
if (!session?.id || !window.electron?.setEmbeddedMpvVolume) {
|
||||
const id = this.sessionId();
|
||||
if (!id || !window.electron?.setEmbeddedMpvVolume) {
|
||||
return;
|
||||
}
|
||||
const updated = await window.electron.setEmbeddedMpvVolume(session.id, value);
|
||||
const updated = await this.guardIpc(() =>
|
||||
window.electron!.setEmbeddedMpvVolume(id, value)
|
||||
);
|
||||
if (updated) {
|
||||
this.session.set(updated);
|
||||
}
|
||||
}
|
||||
|
||||
async setAudioTrack(trackId: number): Promise<void> {
|
||||
const session = this.session();
|
||||
if (!session?.id || !window.electron?.setEmbeddedMpvAudioTrack) {
|
||||
const id = this.sessionId();
|
||||
if (!id || !window.electron?.setEmbeddedMpvAudioTrack) {
|
||||
return;
|
||||
}
|
||||
const updated = await window.electron.setEmbeddedMpvAudioTrack(
|
||||
session.id,
|
||||
trackId
|
||||
const updated = await this.guardIpc(() =>
|
||||
window.electron!.setEmbeddedMpvAudioTrack(id, trackId)
|
||||
);
|
||||
if (updated) {
|
||||
this.session.set(updated);
|
||||
@@ -257,13 +266,12 @@ export class EmbeddedMpvSessionController {
|
||||
}
|
||||
|
||||
async setSubtitleTrack(trackId: number): Promise<void> {
|
||||
const session = this.session();
|
||||
if (!session?.id || !window.electron?.setEmbeddedMpvSubtitleTrack) {
|
||||
const id = this.sessionId();
|
||||
if (!id || !window.electron?.setEmbeddedMpvSubtitleTrack) {
|
||||
return;
|
||||
}
|
||||
const updated = await window.electron.setEmbeddedMpvSubtitleTrack(
|
||||
session.id,
|
||||
trackId
|
||||
const updated = await this.guardIpc(() =>
|
||||
window.electron!.setEmbeddedMpvSubtitleTrack!(id, trackId)
|
||||
);
|
||||
if (updated) {
|
||||
this.session.set(updated);
|
||||
@@ -271,30 +279,43 @@ export class EmbeddedMpvSessionController {
|
||||
}
|
||||
|
||||
async setSpeed(speed: number): Promise<void> {
|
||||
const session = this.session();
|
||||
if (!session?.id || !window.electron?.setEmbeddedMpvSpeed) {
|
||||
const id = this.sessionId();
|
||||
if (!id || !window.electron?.setEmbeddedMpvSpeed) {
|
||||
return;
|
||||
}
|
||||
const updated = await window.electron.setEmbeddedMpvSpeed(session.id, speed);
|
||||
const updated = await this.guardIpc(() =>
|
||||
window.electron!.setEmbeddedMpvSpeed!(id, speed)
|
||||
);
|
||||
if (updated) {
|
||||
this.session.set(updated);
|
||||
}
|
||||
}
|
||||
|
||||
async setAspect(aspect: string): Promise<void> {
|
||||
const session = this.session();
|
||||
if (!session?.id || !window.electron?.setEmbeddedMpvAspect) {
|
||||
const id = this.sessionId();
|
||||
if (!id || !window.electron?.setEmbeddedMpvAspect) {
|
||||
return;
|
||||
}
|
||||
const updated = await window.electron.setEmbeddedMpvAspect(
|
||||
session.id,
|
||||
aspect
|
||||
const updated = await this.guardIpc(() =>
|
||||
window.electron!.setEmbeddedMpvAspect!(id, aspect)
|
||||
);
|
||||
if (updated) {
|
||||
this.session.set(updated);
|
||||
}
|
||||
}
|
||||
|
||||
private async guardIpc<T>(
|
||||
call: () => Promise<T>
|
||||
): Promise<T | null> {
|
||||
try {
|
||||
return await call();
|
||||
} catch {
|
||||
// The session may have been torn down or an addon-side throw
|
||||
// raced the IPC. Swallow — the next snapshot will resync state.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async loadSupport(): Promise<void> {
|
||||
try {
|
||||
this.support.set(await window.electron!.getEmbeddedMpvSupport());
|
||||
|
||||
Reference in new issue
Block a user