From 6590871ca86150ac0e21163665b94e152e94be31 Mon Sep 17 00:00:00 2001 From: OSSRS-AI Date: Sat, 25 Oct 2025 21:10:03 -0400 Subject: [PATCH] AI: HLS: Support hls_master_m3u8_path_relative for reverse proxy compatibility. v7.0.104 (#4338) --- trunk/3rdparty/srs-docs/doc/hls.md | 17 +++++++++++++++++ trunk/conf/full.conf | 10 ++++++++++ trunk/doc/CHANGELOG.md | 1 + trunk/src/app/srs_app_config.cpp | 21 ++++++++++++++++++++- trunk/src/app/srs_app_config.hpp | 6 ++++++ trunk/src/app/srs_app_http_static.cpp | 13 ++++++++++++- trunk/src/core/srs_core_version7.hpp | 2 +- trunk/src/utest/srs_utest_manual_mock.cpp | 5 +++++ trunk/src/utest/srs_utest_manual_mock.hpp | 1 + 9 files changed, 73 insertions(+), 3 deletions(-) diff --git a/trunk/3rdparty/srs-docs/doc/hls.md b/trunk/3rdparty/srs-docs/doc/hls.md index 2322e274e..80623844c 100644 --- a/trunk/3rdparty/srs-docs/doc/hls.md +++ b/trunk/3rdparty/srs-docs/doc/hls.md @@ -426,6 +426,23 @@ Then configure `hls_path` or create a soft link to the directory. To deploy an HLS distribution cluster and edge distribution cluster for your own CDN to handle a large number of viewers, please refer to [Nginx for HLS](./nginx-for-hls.md). +## HLS with Reverse Proxy + +When deploying SRS behind a reverse proxy with path rewriting, you may encounter issues where HLS master playlists use absolute paths (e.g., `/live/livestream.m3u8?hls_ctx=xxx`), which can break playback when the proxy rewrites request paths. For example, if the external URL is `http://proxy/srs/live/stream.m3u8` but gets rewritten to `http://origin/live/stream.m3u8`, the absolute path in the master playlist will drop the `/srs` prefix, causing a 404 error. + +SRS (v7.0.104+) provides the `hls_master_m3u8_path_relative` option to use relative paths in master playlists for reverse proxy compatibility: + +```bash +vhost __defaultVhost__ { + hls { + enabled on; + hls_master_m3u8_path_relative on; # Use relative path for reverse proxy + } +} +``` + +When enabled, the master playlist uses relative paths (e.g., `livestream.m3u8?hls_ctx=xxx`) instead of absolute paths, allowing the player to correctly resolve URLs through the reverse proxy. This is useful for deployments with Nginx, Apache, HAProxy, API gateways, or multi-tenant systems with path-based routing. The default is `off` for backward compatibility. + ## HLS Low Latency How to reduce HLS latency? The key is to reduce the number of slices and the number of TS files in the m3u8. SRS's default configuration is 10 seconds per slice and 60 seconds per m3u8, resulting in a latency of about 30 seconds. Some players start requesting slices from the middle position, so there will be a delay of 3 slices. diff --git a/trunk/conf/full.conf b/trunk/conf/full.conf index 1b7595730..9498dcbd8 100644 --- a/trunk/conf/full.conf +++ b/trunk/conf/full.conf @@ -1478,6 +1478,16 @@ vhost hls.srs.com { # Overwrite by env SRS_VHOST_HLS_HLS_TS_CTX for all vhosts. # Default: on hls_ts_ctx on; + # Whether use relative path for media playlist URL in HLS master playlist. + # When on, the master playlist uses relative path like "livestream.m3u8?hls_ctx=xxx". + # When off, the master playlist uses absolute path like "/live/livestream.m3u8?hls_ctx=xxx". + # Relative path is useful for reverse proxy scenarios with path rewriting. + # For example, if external URL is "http://proxy/srs/live/stream.m3u8" and it's rewritten to + # "http://origin/live/stream.m3u8", relative path ensures the player can correctly resolve + # the media playlist URL. + # Overwrite by env SRS_VHOST_HLS_HLS_MASTER_M3U8_PATH_RELATIVE for all vhosts. + # Default: off + hls_master_m3u8_path_relative off; # whether using AES encryption. # Overwrite by env SRS_VHOST_HLS_HLS_KEYS for all vhosts. diff --git a/trunk/doc/CHANGELOG.md b/trunk/doc/CHANGELOG.md index 2979df8d9..523d9d910 100644 --- a/trunk/doc/CHANGELOG.md +++ b/trunk/doc/CHANGELOG.md @@ -7,6 +7,7 @@ The changelog for SRS. ## SRS 7.0 Changelog +* v7.0, 2025-10-26, HLS: Support hls_master_m3u8_path_relative for reverse proxy compatibility. v7.0.104 (#4338) * v7.0, 2025-10-25, API: Remove minimum limit of 10 for count parameter in /api/v1/streams and /api/v1/clients. v7.0.103 (#4358) * v7.0, 2025-10-22, AI: Only support AAC/MP3/Opus audio codec. v7.0.102 (#4516) * v7.0, 2025-10-22, AI: Fix AAC audio sample rate reporting in API. v7.0.101 (#4518) diff --git a/trunk/src/app/srs_app_config.cpp b/trunk/src/app/srs_app_config.cpp index 12f9ac8e4..7a353826a 100644 --- a/trunk/src/app/srs_app_config.cpp +++ b/trunk/src/app/srs_app_config.cpp @@ -2290,7 +2290,7 @@ srs_error_t SrsConfig::check_normal_config() } else if (n == "hls") { for (int j = 0; j < (int)conf->directives_.size(); j++) { string m = conf->at(j)->name_; - if (m != "enabled" && m != "hls_entry_prefix" && m != "hls_path" && m != "hls_fragment" && m != "hls_window" && m != "hls_on_error" && m != "hls_storage" && m != "hls_mount" && m != "hls_td_ratio" && m != "hls_aof_ratio" && m != "hls_acodec" && m != "hls_vcodec" && m != "hls_m3u8_file" && m != "hls_ts_file" && m != "hls_ts_floor" && m != "hls_cleanup" && m != "hls_nb_notify" && m != "hls_wait_keyframe" && m != "hls_dispose" && m != "hls_keys" && m != "hls_fragments_per_key" && m != "hls_key_file" && m != "hls_key_file_path" && m != "hls_key_url" && m != "hls_dts_directly" && m != "hls_ctx" && m != "hls_ts_ctx" && m != "hls_use_fmp4" && m != "hls_fmp4_file" && m != "hls_init_file" && m != "hls_recover") { + if (m != "enabled" && m != "hls_entry_prefix" && m != "hls_path" && m != "hls_fragment" && m != "hls_window" && m != "hls_on_error" && m != "hls_storage" && m != "hls_mount" && m != "hls_td_ratio" && m != "hls_aof_ratio" && m != "hls_acodec" && m != "hls_vcodec" && m != "hls_m3u8_file" && m != "hls_ts_file" && m != "hls_ts_floor" && m != "hls_cleanup" && m != "hls_nb_notify" && m != "hls_wait_keyframe" && m != "hls_dispose" && m != "hls_keys" && m != "hls_fragments_per_key" && m != "hls_key_file" && m != "hls_key_file_path" && m != "hls_key_url" && m != "hls_dts_directly" && m != "hls_ctx" && m != "hls_ts_ctx" && m != "hls_use_fmp4" && m != "hls_fmp4_file" && m != "hls_init_file" && m != "hls_recover" && m != "hls_master_m3u8_path_relative") { return srs_error_new(ERROR_SYSTEM_CONFIG_INVALID, "illegal vhost.hls.%s of %s", m.c_str(), vhost->arg0().c_str()); } @@ -6692,6 +6692,25 @@ string SrsConfig::get_hls_key_url(std::string vhost) return conf->arg0(); } +bool SrsConfig::get_hls_master_m3u8_path_relative(string vhost) +{ + SRS_OVERWRITE_BY_ENV_BOOL("srs.vhost.hls.hls_master_m3u8_path_relative"); // SRS_VHOST_HLS_HLS_MASTER_M3U8_PATH_RELATIVE + + static bool DEFAULT = false; + + SrsConfDirective *conf = get_hls(vhost); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("hls_master_m3u8_path_relative"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return SRS_CONF_PREFER_FALSE(conf->arg0()); +} + bool SrsConfig::get_hls_recover(string vhost) { SRS_OVERWRITE_BY_ENV_BOOL2("srs.vhost.hls.hls_recover"); // SRS_VHOST_HLS_HLS_RECOVER diff --git a/trunk/src/app/srs_app_config.hpp b/trunk/src/app/srs_app_config.hpp index c2028eda3..d72dd1b69 100644 --- a/trunk/src/app/srs_app_config.hpp +++ b/trunk/src/app/srs_app_config.hpp @@ -519,6 +519,7 @@ public: virtual bool get_vhost_hls_dts_directly(std::string vhost) = 0; virtual bool get_hls_ctx_enabled(std::string vhost) = 0; virtual bool get_hls_ts_ctx_enabled(std::string vhost) = 0; + virtual bool get_hls_master_m3u8_path_relative(std::string vhost) = 0; virtual bool get_hls_recover(std::string vhost) = 0; virtual bool get_dash_enabled(std::string vhost) = 0; virtual bool get_dash_enabled(SrsConfDirective *vhost) = 0; @@ -1366,6 +1367,11 @@ public: // Whether enable session for ts file. // The ts file including .ts file for MPEG-ts segment, .m4s file and init.mp4 file for fmp4 segment. virtual bool get_hls_ts_ctx_enabled(std::string vhost); + // Whether use relative path for media playlist URL in HLS master playlist. + // When on, uses relative path (e.g., "livestream.m3u8?hls_ctx=xxx"). + // When off, uses absolute path (e.g., "/live/livestream.m3u8?hls_ctx=xxx"). + // Default is off for backward compatibility. + virtual bool get_hls_master_m3u8_path_relative(std::string vhost); // Toggles HLS recover mode. // In this mode HLS sequence number is started from where it stopped last time. // Old fragments are kept. Default is on. diff --git a/trunk/src/app/srs_app_http_static.cpp b/trunk/src/app/srs_app_http_static.cpp index b0ed4547a..2ac409ad2 100644 --- a/trunk/src/app/srs_app_http_static.cpp +++ b/trunk/src/app/srs_app_http_static.cpp @@ -178,10 +178,21 @@ srs_error_t SrsHlsStream::serve_new_session(ISrsHttpResponseWriter *w, ISrsHttpM return srs_error_wrap(err, "HLS: http_hooks_on_play"); } + // Determine the media playlist URL path in master playlist based on configuration. + // When hls_master_m3u8_path_relative is on, use relative path (just filename) for better + // compatibility with reverse proxies that do path rewriting. + // For example, convert "/live/livestream.m3u8" to "livestream.m3u8" + // When off (default), use absolute path for backward compatibility. + std::string media_playlist_url = hr->path(); + if (_srs_config->get_hls_master_m3u8_path_relative(req->vhost_)) { + SrsPath path; + media_playlist_url = path.filepath_base(hr->path()); + } + std::stringstream ss; ss << "#EXTM3U" << SRS_CONSTS_LF; ss << "#EXT-X-STREAM-INF:BANDWIDTH=1,AVERAGE-BANDWIDTH=1" << SRS_CONSTS_LF; - ss << hr->path() << "?" << SRS_CONTEXT_IN_HLS << "=" << ctx; + ss << media_playlist_url << "?" << SRS_CONTEXT_IN_HLS << "=" << ctx; if (!hr->query().empty() && hr->query_get(SRS_CONTEXT_IN_HLS).empty()) { ss << "&" << hr->query(); } diff --git a/trunk/src/core/srs_core_version7.hpp b/trunk/src/core/srs_core_version7.hpp index 258198d6f..a28cde114 100644 --- a/trunk/src/core/srs_core_version7.hpp +++ b/trunk/src/core/srs_core_version7.hpp @@ -9,6 +9,6 @@ #define VERSION_MAJOR 7 #define VERSION_MINOR 0 -#define VERSION_REVISION 103 +#define VERSION_REVISION 104 #endif \ No newline at end of file diff --git a/trunk/src/utest/srs_utest_manual_mock.cpp b/trunk/src/utest/srs_utest_manual_mock.cpp index 12f1e024d..858b21f88 100644 --- a/trunk/src/utest/srs_utest_manual_mock.cpp +++ b/trunk/src/utest/srs_utest_manual_mock.cpp @@ -842,6 +842,11 @@ bool MockAppConfig::get_hls_ts_ctx_enabled(std::string vhost) return true; } +bool MockAppConfig::get_hls_master_m3u8_path_relative(std::string vhost) +{ + return false; +} + bool MockAppConfig::get_hls_recover(std::string vhost) { return true; diff --git a/trunk/src/utest/srs_utest_manual_mock.hpp b/trunk/src/utest/srs_utest_manual_mock.hpp index 69011fccc..0fbd96d9a 100644 --- a/trunk/src/utest/srs_utest_manual_mock.hpp +++ b/trunk/src/utest/srs_utest_manual_mock.hpp @@ -456,6 +456,7 @@ public: virtual bool get_vhost_hls_dts_directly(std::string vhost); virtual bool get_hls_ctx_enabled(std::string vhost); virtual bool get_hls_ts_ctx_enabled(std::string vhost); + virtual bool get_hls_master_m3u8_path_relative(std::string vhost); virtual bool get_hls_recover(std::string vhost); virtual bool get_forward_enabled(std::string vhost); virtual SrsConfDirective *get_forwards(std::string vhost);