Skip to content

Commit aa0f578

Browse files
authored
Merge pull request #38 from GuiDev1994/release/v1.1.3
Release v1.1.3
2 parents 0efdb0e + a86dae6 commit aa0f578

13 files changed

Lines changed: 342 additions & 90 deletions

File tree

.gitmodules

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
url = https://github.com/ThrowTheSwitch/Unity.git
1313
[submodule "third_party/ss4s"]
1414
path = third_party/ss4s
15-
url = https://github.com/mariotaku/ss4s.git
15+
url = https://github.com/GuiDev1994/ss4s.git
1616
[submodule "third_party/commons"]
1717
path = third_party/commons
1818
url = https://github.com/GuiDev1994/commons-c.git

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.2")
4+
set(MOONLIGHT_VERSION "1.1.3")
55

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

src/app/app_settings.c

Lines changed: 72 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,29 @@ void settings_reconcile_refresh_rate(app_settings_t *config) {
3939
#if defined(TARGET_WEBOS)
4040
int ntsc = settings_ntsc_refresh_rate_x100_for_fps(config->stream.fps);
4141
if (ntsc > 0) {
42-
config->client_refresh_rate_x100 = ntsc;
42+
if (config->use_ntsc_refresh) {
43+
/* Auto NTSC for presets; keep a user Custom FPS fractional rate if present. */
44+
if (config->client_refresh_rate_x100 == 0 ||
45+
config->client_refresh_rate_x100 == ntsc) {
46+
config->client_refresh_rate_x100 = ntsc;
47+
return;
48+
}
49+
int implied = (config->client_refresh_rate_x100 + 50) / 100;
50+
if (implied != config->stream.fps) {
51+
config->client_refresh_rate_x100 = ntsc;
52+
}
53+
return;
54+
}
55+
/* Integer presets: clear auto-NTSC; keep a distinct Custom FPS fractional rate. */
56+
if (config->client_refresh_rate_x100 == 0 ||
57+
config->client_refresh_rate_x100 == ntsc) {
58+
config->client_refresh_rate_x100 = 0;
59+
return;
60+
}
61+
int implied = (config->client_refresh_rate_x100 + 50) / 100;
62+
if (implied != config->stream.fps) {
63+
config->client_refresh_rate_x100 = 0;
64+
}
4365
return;
4466
}
4567
#endif
@@ -77,7 +99,16 @@ int settings_ntsc_refresh_rate_x100_for_fps(int nominal_fps)
7799
void settings_apply_ntsc_preset_refresh(app_settings_t *config, int nominal_fps)
78100
{
79101
#if defined(TARGET_WEBOS)
102+
if (!config) {
103+
return;
104+
}
80105
(void) nominal_fps;
106+
int ntsc = settings_ntsc_refresh_rate_x100_for_fps(config->stream.fps);
107+
if (ntsc > 0) {
108+
/* Preset selection / NTSC checkbox: force auto NTSC or integer (clears Custom). */
109+
config->client_refresh_rate_x100 = config->use_ntsc_refresh ? ntsc : 0;
110+
return;
111+
}
81112
settings_reconcile_refresh_rate(config);
82113
#else
83114
(void) config;
@@ -128,11 +159,17 @@ void settings_initialize(app_settings_t *config, char *conf_dir) {
128159
config->hdr = false;
129160
config->force_full_color_range = false;
130161
config->hevc = true;
131-
config->idr_refresh_interval_sec = 0;
162+
config->idr_refresh_interval_ms = 0;
132163
config->show_stats_on_start = false;
133164
config->show_stats_compact = false;
134165
config->stick_deadzone = 7;
135166
config->client_refresh_rate_x100 = 0;
167+
config->use_ntsc_refresh = false;
168+
#if defined(TARGET_WEBOS)
169+
config->smooth_frame_pacing = true;
170+
#else
171+
config->smooth_frame_pacing = false;
172+
#endif
136173
config->auto_adjust_bitrate = false;
137174
config->abr_mode = 0;
138175

@@ -199,10 +236,12 @@ bool settings_save(app_settings_t *config) {
199236
ini_write_bool(fp, "hdr", config->hdr);
200237
ini_write_bool(fp, "force_full_color_range", config->force_full_color_range);
201238
ini_write_bool(fp, "hevc", config->hevc);
202-
ini_write_int(fp, "idr_refresh_interval_sec", config->idr_refresh_interval_sec);
239+
ini_write_int(fp, "idr_refresh_interval_ms", config->idr_refresh_interval_ms);
203240
ini_write_bool(fp, "show_stats_on_start", config->show_stats_on_start);
204241
ini_write_bool(fp, "show_stats_compact", config->show_stats_compact);
205242
ini_write_int(fp, "client_refresh_rate_x100", config->client_refresh_rate_x100);
243+
ini_write_bool(fp, "use_ntsc_refresh", config->use_ntsc_refresh);
244+
ini_write_bool(fp, "smooth_frame_pacing", config->smooth_frame_pacing);
206245

207246
ini_write_section(fp, "audio");
208247
ini_write_string(fp, "backend", config->audio_backend);
@@ -297,14 +336,33 @@ static int settings_parse(app_settings_t *config, const char *section, const cha
297336
if (config->abr_mode < 0 || config->abr_mode > 2) {
298337
config->abr_mode = 0;
299338
}
339+
} else if (INI_FULL_MATCH("video", "idr_refresh_interval_ms")) {
340+
set_int(&config->idr_refresh_interval_ms, value);
341+
if (config->idr_refresh_interval_ms < 0) {
342+
config->idr_refresh_interval_ms = 0;
343+
} else if (config->idr_refresh_interval_ms > 0 && config->idr_refresh_interval_ms < 500) {
344+
config->idr_refresh_interval_ms = 500;
345+
} else if (config->idr_refresh_interval_ms > 60000) {
346+
config->idr_refresh_interval_ms = 60000;
347+
} else if (config->idr_refresh_interval_ms > 0) {
348+
config->idr_refresh_interval_ms = ((config->idr_refresh_interval_ms + 250) / 500) * 500;
349+
if (config->idr_refresh_interval_ms < 500) {
350+
config->idr_refresh_interval_ms = 500;
351+
}
352+
}
300353
} else if (INI_FULL_MATCH("video", "idr_refresh_interval_sec")) {
301-
set_int(&config->idr_refresh_interval_sec, value);
302-
if (config->idr_refresh_interval_sec < 0) {
303-
config->idr_refresh_interval_sec = 0;
304-
} else if (config->idr_refresh_interval_sec > 60) {
305-
config->idr_refresh_interval_sec = 60;
306-
} else if (config->idr_refresh_interval_sec == 1) {
307-
config->idr_refresh_interval_sec = 2;
354+
/* Legacy seconds key → milliseconds */
355+
int sec = 0;
356+
set_int(&sec, value);
357+
if (sec < 0) {
358+
config->idr_refresh_interval_ms = 0;
359+
} else if (sec == 0) {
360+
config->idr_refresh_interval_ms = 0;
361+
} else {
362+
if (sec > 60) {
363+
sec = 60;
364+
}
365+
config->idr_refresh_interval_ms = sec * 1000;
308366
}
309367
} else if (INI_NAME_MATCH("hevc")) {
310368
config->hevc = INI_IS_TRUE(value);
@@ -325,6 +383,10 @@ static int settings_parse(app_settings_t *config, const char *section, const cha
325383
} else if (config->client_refresh_rate_x100 > 24000) {
326384
config->client_refresh_rate_x100 = 24000;
327385
}
386+
} else if (INI_FULL_MATCH("video", "use_ntsc_refresh")) {
387+
config->use_ntsc_refresh = INI_IS_TRUE(value);
388+
} else if (INI_FULL_MATCH("video", "smooth_frame_pacing")) {
389+
config->smooth_frame_pacing = INI_IS_TRUE(value);
328390
} else if (INI_FULL_MATCH("video", "force_full_color_range")) {
329391
config->force_full_color_range = INI_IS_TRUE(value);
330392
} else if (INI_NAME_MATCH("surround")) {

src/app/app_settings.h

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ typedef struct app_settings_t {
5151
bool hdr; /* HDR10 (PQ) over HEVC Main10 when host and decoder support it */
5252
bool force_full_color_range; /* SDR only: request full-range YUV (0-255) from host. No effect when HDR is on. */
5353
bool hevc;
54-
/** Periodic HEVC IDR refresh interval in seconds (0 = off, min 2 when enabled). */
55-
int idr_refresh_interval_sec;
54+
/** Periodic HEVC IDR refresh interval in ms (0 = off, min 500 when enabled, step 500). */
55+
int idr_refresh_interval_ms;
5656
bool show_stats_on_start;
5757
bool show_stats_compact;
5858
int stick_deadzone;
@@ -61,6 +61,17 @@ typedef struct app_settings_t {
6161
* 0 = omit (host default frame pacing).
6262
*/
6363
int client_refresh_rate_x100;
64+
/**
65+
* When true on webOS, map preset 30/60/120/240 fps to NTSC fractional rates
66+
* (e.g. 120 → 11988). When false, presets use integer fps (client_refresh_rate_x100 = 0).
67+
*/
68+
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;
6475
bool auto_adjust_bitrate;
6576
int abr_mode;
6677
char *conf_dir;
@@ -104,13 +115,13 @@ bool settings_save(app_settings_t *config);
104115
/** Keep stream.fps aligned with client_refresh_rate_x100 when a fractional rate is set. */
105116
void settings_sync_refresh_rate(app_settings_t *config);
106117

107-
/** webOS: apply NTSC x100 only for 30/60/120/240; drop stale x100 for 144/90/etc. */
118+
/** webOS: apply NTSC x100 only when use_ntsc_refresh for 30/60/120/240; else clear for presets. */
108119
void settings_reconcile_refresh_rate(app_settings_t *config);
109120

110121
/** NTSC refresh rate (Hz × 100) for a nominal FPS preset, or 0 if not mapped (e.g. 144). */
111122
int settings_ntsc_refresh_rate_x100_for_fps(int nominal_fps);
112123

113-
/** On webOS, set client_refresh_rate_x100 from a preset FPS (60→5994, 120→11988, …). */
124+
/** On webOS, set or clear client_refresh_rate_x100 for a preset FPS based on use_ntsc_refresh. */
114125
void settings_apply_ntsc_preset_refresh(app_settings_t *config, int nominal_fps);
115126

116127
void settings_clear(app_settings_t *config);

src/app/lvgl/input/lv_sdl_drv_wheel_input.c

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,45 @@ static bool wheel_handle_gridview(app_ui_input_t *input, const SDL_MouseWheelEve
2828
return true;
2929
}
3030

31+
/**
32+
* Scroll the nearest vertical scrollable ancestor of the focused object.
33+
* Prevents Magic Remote / mouse wheel from driving LVGL encoder focus navigation
34+
* on settings panes and host lists.
35+
*/
36+
static bool wheel_handle_scrollable(app_ui_input_t *input, const SDL_MouseWheelEvent *wheel) {
37+
if (wheel->y == 0) {
38+
return false;
39+
}
40+
lv_group_t *group = app_input_get_group(input);
41+
if (group == NULL) {
42+
return false;
43+
}
44+
lv_obj_t *focused = lv_group_get_focused(group);
45+
if (focused == NULL) {
46+
return false;
47+
}
48+
49+
lv_obj_t *scrollable = focused;
50+
while (scrollable != NULL) {
51+
if (lv_obj_has_flag(scrollable, LV_OBJ_FLAG_SCROLLABLE)) {
52+
const lv_dir_t dir = lv_obj_get_scroll_dir(scrollable);
53+
if (dir & LV_DIR_VER) {
54+
const lv_coord_t overflow =
55+
lv_obj_get_scroll_top(scrollable) + lv_obj_get_scroll_bottom(scrollable);
56+
if (overflow > 0) {
57+
/* SDL: y > 0 = away (scroll up). LVGL: positive dy scrolls content down. */
58+
const lv_coord_t step = LV_MAX(lv_dpx(48), lv_obj_get_height(scrollable) / 6);
59+
const lv_coord_t dy = (wheel->y > 0) ? -step : step;
60+
lv_obj_scroll_by_bounded(scrollable, 0, dy, LV_ANIM_ON);
61+
return true;
62+
}
63+
}
64+
}
65+
scrollable = lv_obj_get_parent(scrollable);
66+
}
67+
return false;
68+
}
69+
3170
int lv_sdl_init_wheel(lv_indev_drv_t *drv, app_ui_input_t *input) {
3271
lv_indev_drv_init(drv);
3372
drv->user_data = input;
@@ -48,6 +87,8 @@ static void sdl_input_read(lv_indev_drv_t *drv, lv_indev_data_t *data) {
4887
session_handle_input_event(app->session, &e);
4988
} else if (wheel_handle_gridview(input, &e.wheel)) {
5089
/* Page the game grid; do not move group focus or scroll parents. */
90+
} else if (wheel_handle_scrollable(input, &e.wheel)) {
91+
/* Scroll settings / host lists instead of changing focus. */
5192
} else if (e.wheel.y != 0) {
5293
/* Encoder scroll only: never set PRESSED — that triggers LVGL "encoder button" clicks. */
5394
data->enc_diff = (e.wheel.y > 0) ? -1 : 1;

src/app/stream/session_worker.c

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,36 @@
1212
#include "stream/adaptive_bitrate.h"
1313
#include "app_session.h"
1414
#include "backend/pcmanager/worker/worker.h"
15+
#include "app_settings.h"
16+
17+
#include <stdlib.h>
18+
#include <stdio.h>
19+
20+
static void session_apply_smooth_pacing_env(const session_t *session) {
21+
#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;
30+
if (x100 > 0) {
31+
/* interval_us = 1e6 * 100 / x100 (e.g. 11988 → ~8341 µs) */
32+
long interval_us = (100000000L + (x100 / 2)) / x100;
33+
char buf[32];
34+
snprintf(buf, sizeof(buf), "%ld", interval_us);
35+
setenv("SS4S_SMOOTH_PACING_INTERVAL_US", buf, 1);
36+
setenv("SS4S_NDL_PACING_INTERVAL_US", buf, 1);
37+
} else {
38+
unsetenv("SS4S_SMOOTH_PACING_INTERVAL_US");
39+
unsetenv("SS4S_NDL_PACING_INTERVAL_US");
40+
}
41+
#else
42+
(void) session;
43+
#endif
44+
}
1545

1646
int session_worker(session_t *session) {
1747
app_t *app = session->app;
@@ -73,6 +103,7 @@ int session_worker(session_t *session) {
73103
SS4S_PlayerSetViewportSize(session->player, app->ui.width, app->ui.height);
74104
SS4S_PlayerSetUserdata(session->player, app);
75105

106+
session_apply_smooth_pacing_env(session);
76107
session_video_prepare_stream();
77108

78109
int startResult = LiStartConnection(&server->serverInfo, &session->config.stream,

0 commit comments

Comments
 (0)