feat(playback): add embedded mpv series navigation

This commit is contained in:
4gray committed 2026-06-07 20:57:14 +02:00
1 parent c93b0b4a77
commit 4e30289293
24 files changed
+1045 -13

No files matched your search

@@ -70,6 +70,7 @@ enum class SessionStatus {
Loading,
Playing,
Paused,
Ended,
Error,
Closed,
};
@@ -157,6 +158,8 @@ std::string toStatusString(SessionStatus status)
return "playing";
case SessionStatus::Paused:
return "paused";
case SessionStatus::Ended:
return "ended";
case SessionStatus::Error:
return "error";
case SessionStatus::Closed:
@@ -1128,6 +1131,11 @@ void runEventLoop(const std::shared_ptr<Session>& session)
endFile->error < 0
? mpv_error_string(endFile->error)
: "Playback failed.";
} else if (
endFile &&
endFile->reason == MPV_END_FILE_REASON_EOF &&
session->running.load()) {
session->snapshot.status = SessionStatus::Ended;
} else if (session->running.load()) {
session->snapshot.status = SessionStatus::Idle;
}
@@ -1163,6 +1171,7 @@ void runEventLoop(const std::shared_ptr<Session>& session)
property->data) {
session->paused = *static_cast<int*>(property->data) != 0;
if (session->snapshot.status != SessionStatus::Loading &&
session->snapshot.status != SessionStatus::Ended &&
session->snapshot.status != SessionStatus::Error &&
session->loadedPath) {
session->snapshot.status = session->paused
@@ -1912,6 +1921,7 @@ Napi::Value SetPaused(const Napi::CallbackInfo& info)
std::lock_guard<std::mutex> lock(session->mutex);
session->paused = paused != 0;
if (session->loadedPath &&
session->snapshot.status != SessionStatus::Ended &&
session->snapshot.status != SessionStatus::Error) {
session->snapshot.status = session->paused
? SessionStatus::Paused
@@ -45,4 +45,26 @@ describe('Embedded MPV native source recording invariants', () => {
);
expect(body).toContain('stopRecordingRequestId,');
});
it('maps successful MPV end-file events to an ended session status', () => {
expect(nativeSource).toContain('Ended,');
expect(nativeSource).toContain('case SessionStatus::Ended:');
const eventLoopBodyStart = nativeSource.indexOf('void runEventLoop(');
expect(eventLoopBodyStart).toBeGreaterThanOrEqual(0);
const endFileCaseStart = nativeSource.indexOf(
'case MPV_EVENT_END_FILE',
eventLoopBodyStart
);
expect(endFileCaseStart).toBeGreaterThanOrEqual(0);
const nextCaseStart = nativeSource.indexOf(
'case MPV_EVENT_PROPERTY_CHANGE',
endFileCaseStart
);
expect(nextCaseStart).toBeGreaterThan(endFileCaseStart);
const endFileCase = nativeSource.slice(endFileCaseStart, nextCaseStart);
expect(endFileCase).toContain('SessionStatus::Ended');
expect(endFileCase).toContain('MPV_END_FILE_REASON_EOF');
});
});
@@ -197,6 +197,17 @@ describe('EmbeddedMpvNativeService power blocker', () => {
expect(powerSaveBlockerMock.stop).toHaveBeenCalledWith(1);
});
it('releases the blocker when MPV reports playback ended', () => {
startSession('s1', snapshot('playing'));
expect(powerSaveBlockerMock.start).toHaveBeenCalledTimes(1);
addon.getSessionSnapshot.mockReturnValueOnce(snapshot('ended'));
service.setPaused('s1', false);
expect(powerSaveBlockerMock.stop).toHaveBeenCalledTimes(1);
expect(powerSaveBlockerMock.stop).toHaveBeenCalledWith(1);
});
it('keeps the blocker held while any other session is still playing', () => {
startSession('s1', snapshot('playing'));
startSession('s2', snapshot('playing'));