Skip to content

Commit 9f1c43d

Browse files
Release v1.1.7 — pacing, 3.6K, UI cleanup, surround fix
Ship always-on smooth pacing with panel-refresh anchoring and 0.5-frame drift, restore 3.6K for C5, move About into help, drop soft-recovery / pacing toggles, and keep conditional 5.1 remap from v1.1.6. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent e76ca5c commit 9f1c43d

23 files changed

Lines changed: 133 additions & 287 deletions

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
cmake_minimum_required(VERSION 3.19)
22
set(CMAKE_POLICY_VERSION_MINIMUM 3.5)
33

4-
set(MOONLIGHT_VERSION "1.1.6")
4+
set(MOONLIGHT_VERSION "1.1.7")
55

66
project(moonlight VERSION ${MOONLIGHT_VERSION} LANGUAGES C CXX)
77

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Unofficial fork of [Moonlight TV](https://github.com/mariotaku/moonlight-tv) for
77
## Highlights
88

99
- **AMOLED layout** — pure black background, dark surfaces, violet accent.
10-
- **Native 4K (3840×2160)** streaming with HDR10 when supported.
10+
- **3.6K (3584×2016)** recommended on LG C5 (stable quality without native-4K cumulative delay); **4K** when the set handles it.
1111
- **HDR10 (PQ)** over HEVC Main10 (when supported).
1212
- Bitrate slider up to **300 Mbps**; above **250 Mbps** there is usually no visible gain and packet loss becomes more likely as the link becomes less stable.
1313
- **Performance stats overlay**, **full on-screen keyboard**, and **virtual mouse** during streaming.
@@ -28,7 +28,7 @@ Unofficial fork of [Moonlight TV](https://github.com/mariotaku/moonlight-tv) for
2828

2929
| Setting | Suggestion |
3030
|---------|------------|
31-
| Resolution | **4K** (3840×2160) |
31+
| Resolution | **3.6K** (3584×2016) on LG C5; **4K** if your set is stable at native 4K |
3232
| FPS | 60 or 120 |
3333
| Codec | HEVC (H.265) |
3434
| Bitrate | Start at **120–180 Mbps**; stay at or below **250 Mbps** for a stable link. Higher values rarely help and can increase packet loss. |
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Aggressive pacing + UI cleanup (3.6K, About, stats)
2+
3+
**Date:** 2026-07-20
4+
**Status:** Shipped in v1.1.7
5+
**Pacing choice:** Tight PTS grid (0.5 frame max drift) + panel-refresh interval when near stream fps. P-frame dropping was rejected (HEVC corruption).
6+
7+
## Scope
8+
9+
1. Restore **3.6K (3584×2016)** resolution preset (C5 practical limit).
10+
2. Remove settings: Soft recovery, Pause at decode time, Smooth frame pacing (legacy INI keys ignored).
11+
3. Always-on aggressive frame pacing (NDL webOS5 + SMP): `SS4S_SMOOTH_PACING_MAX_DRIFT_FRAMES=0.5`; interval from panel Hz when within ±2 of stream.
12+
4. Remove controller battery from performance overlay.
13+
5. Move About from settings tabs into launcher **?** help popup.
14+
15+
## Non-goals
16+
17+
- Reintroduce Flush+IDR / SMP force / Starfish A-B-C / destructive P-frame drops.
18+
19+
## Success
20+
21+
3.6K selectable; Video pane without the three dead toggles; less microstutter at 1080p/2K/3.6K; clean stats; About under ?.

src/app/app_settings.c

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -165,15 +165,6 @@ void settings_initialize(app_settings_t *config, char *conf_dir) {
165165
config->stick_deadzone = 7;
166166
config->client_refresh_rate_x100 = 0;
167167
config->use_ntsc_refresh = false;
168-
#if defined(TARGET_WEBOS)
169-
config->smooth_frame_pacing = true;
170-
config->pause_at_decode_time = true;
171-
config->soft_recovery = true;
172-
#else
173-
config->smooth_frame_pacing = false;
174-
config->pause_at_decode_time = true;
175-
config->soft_recovery = false;
176-
#endif
177168
config->auto_adjust_bitrate = false;
178169
config->abr_mode = 0;
179170

@@ -245,9 +236,6 @@ bool settings_save(app_settings_t *config) {
245236
ini_write_bool(fp, "show_stats_compact", config->show_stats_compact);
246237
ini_write_int(fp, "client_refresh_rate_x100", config->client_refresh_rate_x100);
247238
ini_write_bool(fp, "use_ntsc_refresh", config->use_ntsc_refresh);
248-
ini_write_bool(fp, "smooth_frame_pacing", config->smooth_frame_pacing);
249-
ini_write_bool(fp, "pause_at_decode_time", config->pause_at_decode_time);
250-
ini_write_bool(fp, "soft_recovery", config->soft_recovery);
251239

252240
ini_write_section(fp, "audio");
253241
ini_write_string(fp, "backend", config->audio_backend);
@@ -391,12 +379,10 @@ static int settings_parse(app_settings_t *config, const char *section, const cha
391379
}
392380
} else if (INI_FULL_MATCH("video", "use_ntsc_refresh")) {
393381
config->use_ntsc_refresh = INI_IS_TRUE(value);
394-
} else if (INI_FULL_MATCH("video", "smooth_frame_pacing")) {
395-
config->smooth_frame_pacing = INI_IS_TRUE(value);
396-
} else if (INI_FULL_MATCH("video", "pause_at_decode_time")) {
397-
config->pause_at_decode_time = INI_IS_TRUE(value);
398-
} else if (INI_FULL_MATCH("video", "soft_recovery")) {
399-
config->soft_recovery = INI_IS_TRUE(value);
382+
} else if (INI_FULL_MATCH("video", "smooth_frame_pacing") ||
383+
INI_FULL_MATCH("video", "pause_at_decode_time") ||
384+
INI_FULL_MATCH("video", "soft_recovery")) {
385+
/* Legacy keys ignored (removed from settings). */
400386
} else if (INI_FULL_MATCH("video", "force_full_color_range")) {
401387
config->force_full_color_range = INI_IS_TRUE(value);
402388
} else if (INI_NAME_MATCH("surround")) {

src/app/app_settings.h

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -66,22 +66,6 @@ typedef struct app_settings_t {
6666
* (e.g. 120 → 11988). When false, presets use integer fps (client_refresh_rate_x100 = 0).
6767
*/
6868
bool use_ntsc_refresh;
69-
/**
70-
* webOS NDL/SMP: stamp video PTS on a virtual frame grid instead of wall-clock feed time.
71-
* Reduces pan hitching when encode/arrival jitter is high (default true).
72-
* When enabled, host presentationTimeUs is mapped into the player PTS when available.
73-
*/
74-
bool smooth_frame_pacing;
75-
/**
76-
* webOS SMP/Starfish: pass pauseAtDecodeTime in Load payload (default true).
77-
* Independent of smooth_frame_pacing (PTS grid). Set false only for latency experiments.
78-
*/
79-
bool pause_at_decode_time;
80-
/**
81-
* webOS: Phase B soft recovery for ≥4K decode backlog (temporary bitrate drop).
82-
* Independent of Flush+IDR. Default true for 1.1.5 device testing.
83-
*/
84-
bool soft_recovery;
8569
bool auto_adjust_bitrate;
8670
int abr_mode;
8771
char *conf_dir;
@@ -109,6 +93,8 @@ extern const size_t audio_config_len;
10993
#define RES_1080P RES_MERGE(1920, 1080)
11094
#define RES_1440P RES_MERGE(2560, 1440)
11195
#define RES_1800P RES_MERGE(3200, 1800)
96+
/** ~90% of 4K (3584×2016); practical limit on LG C5 without cumulative 4K delay */
97+
#define RES_3_6K RES_MERGE(3584, 2016)
11298
#define RES_4K RES_MERGE(3840, 2160)
11399

114100
/** Fixed decode-unit reassembly buffer (megabytes). */

src/app/stream/session.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,6 @@ void session_config_init(app_t *app, session_config_t *config, const SERVER_DATA
280280
}
281281
config->auto_adjust_bitrate = app_config->auto_adjust_bitrate;
282282
config->abr_mode = app_config->abr_mode;
283-
config->soft_recovery = app_config->soft_recovery;
284283

285284
SS4S_VideoCapabilities video_cap = app->ss4s.video_cap;
286285
SS4S_AudioCapabilities audio_cap = app->ss4s.audio_cap;

src/app/stream/session.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ typedef struct session_config_t {
6868
uint8_t stick_deadzone;
6969
bool auto_adjust_bitrate;
7070
int abr_mode;
71-
bool soft_recovery;
7271
} session_config_t;
7372

7473
extern int streaming_errno;

src/app/stream/session_worker.c

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,30 +17,59 @@
1717
#include <stdlib.h>
1818
#include <stdio.h>
1919

20+
#if TARGET_WEBOS
21+
#include <SDL.h>
22+
#endif
23+
2024
static void session_apply_smooth_pacing_env(const session_t *session) {
2125
#if TARGET_WEBOS
22-
const app_settings_t *cfg = app_configuration;
23-
bool smooth = cfg != NULL ? cfg->smooth_frame_pacing : true;
24-
const char *smooth_val = smooth ? "1" : "0";
25-
/* Shared names for SMP + NDL; keep NDL_* aliases for older module builds. */
26-
setenv("SS4S_SMOOTH_PACING", smooth_val, 1);
27-
setenv("SS4S_NDL_SMOOTH_PACING", smooth_val, 1);
28-
29-
int x100 = session->config.stream.clientRefreshRateX100;
26+
/* Always-on aggressive pacing (no user toggle). */
27+
setenv("SS4S_SMOOTH_PACING", "1", 1);
28+
setenv("SS4S_NDL_SMOOTH_PACING", "1", 1);
29+
/* Historical Starfish default; not exposed in settings. */
30+
setenv("SS4S_PAUSE_AT_DECODE_TIME", "1", 1);
31+
/* Tight drift: 0.5 frame (SS4S reads as percent of interval via maxDrift factor). */
32+
setenv("SS4S_SMOOTH_PACING_MAX_DRIFT_FRAMES", "0.5", 1);
33+
34+
/* Prefer measured panel refresh for the PTS grid so display cadence matches the OLED,
35+
* not only the host's clientRefreshRateX100 / stream fps (common microstutter source). */
36+
int stream_x100 = session->config.stream.clientRefreshRateX100;
37+
if (stream_x100 <= 0 && session->config.stream.fps > 0) {
38+
stream_x100 = session->config.stream.fps * 100;
39+
}
40+
41+
int x100 = stream_x100;
42+
int panel_hz = 0;
43+
const char *source = "stream";
44+
if (SDL_webOSGetRefreshRate(&panel_hz) && panel_hz >= 20 && panel_hz <= 240) {
45+
int panel_x100 = panel_hz * 100;
46+
if (stream_x100 <= 0) {
47+
x100 = panel_x100;
48+
source = "panel";
49+
} else {
50+
int stream_hz = (stream_x100 + 50) / 100;
51+
/* Same ballpark (±2 Hz): anchor PTS to the panel. Far apart: keep stream. */
52+
if (abs(stream_hz - panel_hz) <= 2) {
53+
x100 = panel_x100;
54+
source = "panel";
55+
}
56+
}
57+
}
58+
3059
if (x100 > 0) {
31-
/* interval_us = 1e6 * 100 / x100 (e.g. 11988~8341 µs) */
60+
/* interval_us = 1e6 * 100 / x100 (e.g. 120008333 µs) */
3261
long interval_us = (100000000L + (x100 / 2)) / x100;
3362
char buf[32];
3463
snprintf(buf, sizeof(buf), "%ld", interval_us);
3564
setenv("SS4S_SMOOTH_PACING_INTERVAL_US", buf, 1);
3665
setenv("SS4S_NDL_PACING_INTERVAL_US", buf, 1);
66+
commons_log_info("Session",
67+
"Smooth pacing interval %ld µs from %s (x100=%d, stream_x100=%d, panel=%d Hz)",
68+
interval_us, source, x100, stream_x100, panel_hz);
3769
} else {
3870
unsetenv("SS4S_SMOOTH_PACING_INTERVAL_US");
3971
unsetenv("SS4S_NDL_PACING_INTERVAL_US");
4072
}
41-
42-
bool pause_at_decode = cfg == NULL || cfg->pause_at_decode_time;
43-
setenv("SS4S_PAUSE_AT_DECODE_TIME", pause_at_decode ? "1" : "0", 1);
4473
#else
4574
(void) session;
4675
#endif
@@ -140,13 +169,13 @@ int session_worker(session_t *session) {
140169
}
141170
session_set_state(session, STREAMING_STREAMING);
142171
bus_pushevent(USER_STREAM_OPEN, NULL, NULL);
143-
if (session->config.auto_adjust_bitrate || session->config.soft_recovery) {
172+
if (session->config.auto_adjust_bitrate) {
144173
adaptive_bitrate_config_t abr_config = {
145174
.gs_client = client,
146175
.server = server,
147176
.initial_bitrate = session->config.stream.bitrate,
148177
.mode = (abr_mode_t) session->config.abr_mode,
149-
.recovery_only = session->config.soft_recovery && !session->config.auto_adjust_bitrate,
178+
.recovery_only = false,
150179
};
151180
session->abr = adaptive_bitrate_start(&abr_config);
152181
}

src/app/stream/video/session_video.c

Lines changed: 1 addition & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
#include "ss4s.h"
2020
#include "stream/connection/session_connection.h"
2121
#include "stream/session_priv.h"
22-
#include "stream/adaptive_bitrate.h"
2322
#include "app.h"
2423

2524
#include <SDL.h>
@@ -69,85 +68,6 @@ VIDEO_STATS vdec_summary_stats;
6968
static unsigned vdec_stats_seq;
7069
VIDEO_INFO vdec_stream_info;
7170

72-
#if defined(TARGET_WEBOS)
73-
#define SOFT_REC_SUSTAIN_MS 3000u
74-
#define SOFT_REC_COOLDOWN_MS 20000u
75-
#define SOFT_REC_HOLD_MS 15000
76-
#define SOFT_REC_DROP_PERCENT 75
77-
#define SOFT_REC_RQ_THRESHOLD 2
78-
#define SOFT_REC_LATENCY_MS 80.0f
79-
80-
static Uint32 soft_rec_high_since = 0;
81-
static Uint32 soft_rec_cooldown_until = 0;
82-
83-
static void soft_recovery_reset(void) {
84-
soft_rec_high_since = 0;
85-
soft_rec_cooldown_until = 0;
86-
}
87-
88-
static bool soft_recovery_is_4k(void) {
89-
int w = session != NULL ? session->config.stream.width : 0;
90-
int h = session != NULL ? session->config.stream.height : 0;
91-
if (vdec_stream_info.width > 0 && vdec_stream_info.height > 0) {
92-
w = vdec_stream_info.width;
93-
h = vdec_stream_info.height;
94-
}
95-
return ((int64_t) w * (int64_t) h) >= ((int64_t) 3840 * 2160);
96-
}
97-
98-
static bool soft_recovery_pressure(const struct VIDEO_STATS *dst) {
99-
if (dst->videoRenderQueue >= SOFT_REC_RQ_THRESHOLD) {
100-
return true;
101-
}
102-
float target = (float) vdec_stream_target_fps;
103-
if (target < 1.0f) {
104-
target = 60.0f;
105-
}
106-
if (dst->receivedFps >= target * 0.9f && dst->decodedFps < target * 0.85f && dst->submittedFrames > 0) {
107-
return true;
108-
}
109-
if (vdec_stream_info.has_decoder_latency && dst->avgDecoderLatency > SOFT_REC_LATENCY_MS) {
110-
return true;
111-
}
112-
return false;
113-
}
114-
115-
static void soft_recovery_tick(const struct VIDEO_STATS *dst) {
116-
if (session == NULL || !session->config.soft_recovery || session->abr == NULL) {
117-
return;
118-
}
119-
if (!soft_recovery_is_4k()) {
120-
soft_rec_high_since = 0;
121-
return;
122-
}
123-
Uint32 now = SDL_GetTicks();
124-
if (soft_rec_cooldown_until != 0 && now < soft_rec_cooldown_until) {
125-
return;
126-
}
127-
if (!soft_recovery_pressure(dst)) {
128-
soft_rec_high_since = 0;
129-
return;
130-
}
131-
if (soft_rec_high_since == 0) {
132-
soft_rec_high_since = now;
133-
return;
134-
}
135-
if (now - soft_rec_high_since < SOFT_REC_SUSTAIN_MS) {
136-
return;
137-
}
138-
if (adaptive_bitrate_request_drop(session->abr, SOFT_REC_DROP_PERCENT, SOFT_REC_HOLD_MS,
139-
"4K decode backlog")) {
140-
commons_log_info("Session",
141-
"Soft recovery: sustained 4K backlog (RQ=%d decodedFps=%.1f latency=%.1fms) "
142-
"→ request %d%% bitrate",
143-
dst->videoRenderQueue, dst->decodedFps, dst->avgDecoderLatency,
144-
SOFT_REC_DROP_PERCENT);
145-
soft_rec_cooldown_until = now + SOFT_REC_COOLDOWN_MS;
146-
}
147-
soft_rec_high_since = 0;
148-
}
149-
#endif
150-
15171
static int vdec_delegate_setup(int videoFormat, int width, int height, int redrawRate, void *context, int drFlags);
15272

15373
static void vdec_delegate_cleanup(void);
@@ -230,9 +150,6 @@ int vdec_delegate_setup(int videoFormat, int width, int height, int redrawRate,
230150
frames_since_idr = 0;
231151
vdec_stream_target_fps = redrawRate > 0 ? redrawRate : 60;
232152
vdec_warned_near_buffer_limit = false;
233-
#if defined(TARGET_WEBOS)
234-
soft_recovery_reset();
235-
#endif
236153

237154
if (videoFormat & VIDEO_FORMAT_MASK_AV1) {
238155
vdec_stream_info.width = width;
@@ -299,9 +216,6 @@ void vdec_delegate_cleanup(void) {
299216
buffer = NULL;
300217
buffer_size = 0;
301218
buffer_initial_size = 0;
302-
#if defined(TARGET_WEBOS)
303-
soft_recovery_reset();
304-
#endif
305219
SS4S_PlayerVideoClose(player);
306220
session = NULL;
307221
}
@@ -458,7 +372,7 @@ void vdec_stat_submit(const struct VIDEO_STATS *src, unsigned long now) {
458372
LiGetEstimatedRttInfo(&dst->rtt, &dst->rttVariance);
459373
}
460374

461-
/* Always sample latency/RQ so soft recovery works with the overlay hidden. */
375+
/* Always sample latency/RQ for overlay when shown. */
462376
int latencyUS = 0;
463377
if (player != NULL && SS4S_PlayerGetVideoLatency(player, 0, &latencyUS)) {
464378
dst->avgDecoderLatency = (float) latencyUS / 1000.0f;
@@ -475,10 +389,6 @@ void vdec_stat_submit(const struct VIDEO_STATS *src, unsigned long now) {
475389
}
476390
vdec_stats_write_end();
477391

478-
#if defined(TARGET_WEBOS)
479-
soft_recovery_tick(dst);
480-
#endif
481-
482392
if (show_stats) {
483393
app_bus_post(session->app, (bus_actionfunc) streaming_refresh_stats, NULL);
484394
}

0 commit comments

Comments
 (0)