mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-16 22:26:16 -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
@@ -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