From 21bf69577947c82497139f72173da365c9fc372d Mon Sep 17 00:00:00 2001 From: 4gray <4gray@users.noreply.github.com> Date: Sat, 13 Jun 2026 12:17:38 +0200 Subject: [PATCH] fix: format mpv numeric ipc values locale-invariantly --- .../native/src/embedded_mpv_wid_common.h | 37 ++++++++++++---- .../embedded-mpv-native-source.spec.ts | 43 +++++++++++++++++++ 2 files changed, 71 insertions(+), 9 deletions(-) diff --git a/apps/electron-backend/native/src/embedded_mpv_wid_common.h b/apps/electron-backend/native/src/embedded_mpv_wid_common.h index 7b2b82dd7..f2995814a 100644 --- a/apps/electron-backend/native/src/embedded_mpv_wid_common.h +++ b/apps/electron-backend/native/src/embedded_mpv_wid_common.h @@ -60,7 +60,9 @@ IPTVNATOR_DECLARE_MPV_DYNAMIC_SYMBOL(mpv_wakeup) #include #include #include +#include #include +#include #include #include #include @@ -196,6 +198,18 @@ double clampVolumePercent(double value) return std::max(0.0, std::min(100.0, value * 100.0)); } +std::string formatInvariantDouble(double value) +{ + if (!std::isfinite(value)) { + return "0"; + } + + std::ostringstream output; + output.imbue(std::locale::classic()); + output << std::setprecision(17) << value; + return output.str(); +} + std::string nowIsoString() { const auto now = std::chrono::system_clock::now(); @@ -547,7 +561,8 @@ std::vector buildLinuxMpvArguments( { std::lock_guard lock(session->mutex); arguments.push_back( - "--volume=" + std::to_string(session->snapshot.volumePercent) + "--volume=" + + formatInvariantDouble(session->snapshot.volumePercent) ); } @@ -562,9 +577,11 @@ std::vector buildLinuxMpvArguments( appendLinuxMpvOption(arguments, "user-agent", userAgent); appendLinuxMpvOption(arguments, "referrer", referer); if (std::isfinite(startTime) && startTime >= 0) { - std::ostringstream startValue; - startValue << startTime; - appendLinuxMpvOption(arguments, "start", startValue.str()); + appendLinuxMpvOption( + arguments, + "start", + formatInvariantDouble(startTime) + ); } if (playback.Has("headers") && playback.Get("headers").IsObject()) { appendLinuxMpvOption( @@ -1540,8 +1557,9 @@ Napi::Value CreateSession(const Napi::CallbackInfo& info) ); } - const auto initialVolume = - std::to_string(session->snapshot.volumePercent); + const auto initialVolume = formatInvariantDouble( + session->snapshot.volumePercent + ); mpv_set_option_string(session->handle, "volume", initialVolume.c_str()); mpv_request_log_messages(session->handle, "warn"); @@ -1807,8 +1825,9 @@ Napi::Value Seek(const Napi::CallbackInfo& info) } const auto session = getSessionOrThrow(env, info[0].As().Utf8Value()); - const std::string seconds = - std::to_string(info[1].As().DoubleValue()); + const std::string seconds = formatInvariantDouble( + info[1].As().DoubleValue() + ); #ifdef __linux__ std::string socketPath; { @@ -1857,7 +1876,7 @@ Napi::Value SetVolume(const Napi::CallbackInfo& info) sendLinuxMpvCommand( socketPath, "{\"command\":[\"set_property\",\"volume\"," + - std::to_string(volume) + "]}\n" + formatInvariantDouble(volume) + "]}\n" ); } return env.Undefined(); 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 index 8e966b66d..b8fc13171 100644 --- 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 @@ -370,6 +370,49 @@ describe('Embedded MPV native source recording invariants', () => { expect(refreshBody).not.toContain('clampVolumePercent(*volume)'); }); + it('formats MPV floating-point values independently from the user locale', () => { + const formatterBody = sourceFunctionBody( + widCommonSource, + 'std::string formatInvariantDouble(', + 'formatInvariantDouble' + ); + expect(formatterBody).toContain('std::locale::classic()'); + expect(formatterBody).toContain('std::setprecision(17)'); + + const linuxArgumentsBody = sourceFunctionBody( + widCommonSource, + 'std::vector buildLinuxMpvArguments(', + 'buildLinuxMpvArguments' + ); + expect(linuxArgumentsBody).toContain( + '"--volume=" +\n' + + ' formatInvariantDouble(session->snapshot.volumePercent)' + ); + expect(linuxArgumentsBody).toContain( + 'appendLinuxMpvOption(\n' + + ' arguments,\n' + + ' "start",\n' + + ' formatInvariantDouble(startTime)\n' + + ' );' + ); + + const seekBody = sourceFunctionBody( + widCommonSource, + 'Napi::Value Seek(', + 'Seek' + ); + expect(seekBody).toContain('formatInvariantDouble('); + expect(seekBody).not.toContain('std::to_string(info[1]'); + + const setVolumeBody = sourceFunctionBody( + widCommonSource, + 'Napi::Value SetVolume(', + 'SetVolume' + ); + expect(setVolumeBody).toContain('formatInvariantDouble(volume)'); + expect(setVolumeBody).not.toContain('std::to_string(volume)'); + }); + it('keeps Linux MPV snapshot IPC off the NAPI snapshot read path', () => { const getSnapshotBody = sourceFunctionBody( widCommonSource,