From 6d0658b9833b22791ed35506e0db9fbbfed12c11 Mon Sep 17 00:00:00 2001 From: 4gray Date: Sun, 10 May 2026 10:57:24 +0200 Subject: [PATCH] fix(playback): track mpv recording auto-stop replies Entire-Checkpoint: f957cd9849e0 --- .../native/src/embedded_mpv.mm | 20 +++++++- .../embedded-mpv-native-source.spec.ts | 48 +++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 apps/electron-backend/src/app/services/embedded-mpv-native-source.spec.ts diff --git a/apps/electron-backend/native/src/embedded_mpv.mm b/apps/electron-backend/native/src/embedded_mpv.mm index 4bd14100b..d9c7d6e9a 100644 --- a/apps/electron-backend/native/src/embedded_mpv.mm +++ b/apps/electron-backend/native/src/embedded_mpv.mm @@ -1731,14 +1731,32 @@ Napi::Value LoadPlayback(const Napi::CallbackInfo& info) } if (recordingActive) { const char* disabledValue = ""; + const uint64_t stopRecordingRequestId = nextAsyncRequestId(); + { + std::lock_guard lock(session->mutex); + session->pendingRecordingStopRequestId = stopRecordingRequestId; + session->pendingRecordingStopStartedAt = + session->snapshot.recordingStartedAt; + } + const int stopResult = mpv_set_property_async( session->handle, - nextAsyncRequestId(), + stopRecordingRequestId, "stream-record", MPV_FORMAT_STRING, const_cast(&disabledValue) ); if (stopResult < 0) { + { + std::lock_guard lock(session->mutex); + if ( + session->pendingRecordingStopRequestId == + stopRecordingRequestId + ) { + session->pendingRecordingStopRequestId = 0; + session->pendingRecordingStopStartedAt.clear(); + } + } updateSessionError(session, mpv_error_string(stopResult)); throw Napi::Error::New( env, diff --git a/apps/electron-backend/src/app/services/embedded-mpv-native-source.spec.ts b/apps/electron-backend/src/app/services/embedded-mpv-native-source.spec.ts new file mode 100644 index 000000000..6b17a5e6d --- /dev/null +++ b/apps/electron-backend/src/app/services/embedded-mpv-native-source.spec.ts @@ -0,0 +1,48 @@ +import { readFileSync } from 'fs'; +import path from 'path'; + +describe('Embedded MPV native source recording invariants', () => { + const nativeSource = readFileSync( + path.resolve(__dirname, '../../../native/src/embedded_mpv.mm'), + 'utf8' + ); + + function functionBody(name: string): string { + const start = nativeSource.indexOf(`Napi::Value ${name}(`); + expect(start).toBeGreaterThanOrEqual(0); + + const bodyStart = nativeSource.indexOf('{', start); + expect(bodyStart).toBeGreaterThanOrEqual(0); + + let depth = 0; + for (let index = bodyStart; index < nativeSource.length; index += 1) { + if (nativeSource[index] === '{') { + depth += 1; + } + if (nativeSource[index] === '}') { + depth -= 1; + } + if (depth === 0) { + return nativeSource.slice(bodyStart, index + 1); + } + } + + throw new Error(`Unable to read ${name} body.`); + } + + it('tracks LoadPlayback recording auto-stop replies through the reconciler', () => { + const body = functionBody('LoadPlayback'); + + expect(body).toContain( + 'const uint64_t stopRecordingRequestId = nextAsyncRequestId();' + ); + expect(body).toContain( + 'session->pendingRecordingStopRequestId = stopRecordingRequestId;' + ); + expect(body).toContain( + 'session->pendingRecordingStopStartedAt =\n' + + ' session->snapshot.recordingStartedAt;' + ); + expect(body).toContain('stopRecordingRequestId,'); + }); +});