### Feature
HLS continuous mode: In this mode HLS sequence number is started from
where it stopped last time. Old fragments are kept. Default is on.
### Configuration
```
vhost __defaultVhost__ {
hls {
enabled on;
hls_path ./objs/nginx/html;
hls_fragment 10;
hls_window 60;
hls_continuous on;
}
}
```
Contributed by AI:
* [AI: Refine and extract HLS
recover.](656e4e296d)
---------
Co-authored-by: Haibo Chen <495810242@qq.com>
Co-authored-by: winlin <winlinvip@gmail.com>
Co-authored-by: OSSRS-AI <winlinam@gmail.com>
This commit is contained in:
parent
ebcaef43c6
commit
664e868972
2
trunk/3rdparty/gtest-fit/CMakeLists.txt
vendored
2
trunk/3rdparty/gtest-fit/CMakeLists.txt
vendored
|
|
@ -1,7 +1,7 @@
|
|||
# Note: CMake support is community-based. The maintainers do not use CMake
|
||||
# internally.
|
||||
|
||||
cmake_minimum_required(VERSION 2.8.12)
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
if (POLICY CMP0048)
|
||||
cmake_policy(SET CMP0048 NEW)
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ else()
|
|||
cmake_policy(SET CMP0048 NEW)
|
||||
project(gmock VERSION ${GOOGLETEST_VERSION} LANGUAGES CXX C)
|
||||
endif()
|
||||
cmake_minimum_required(VERSION 2.8.12)
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
if (COMMAND set_up_hermetic_build)
|
||||
set_up_hermetic_build()
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ else()
|
|||
cmake_policy(SET CMP0048 NEW)
|
||||
project(gtest VERSION ${GOOGLETEST_VERSION} LANGUAGES CXX C)
|
||||
endif()
|
||||
cmake_minimum_required(VERSION 2.8.12)
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
if (POLICY CMP0063) # Visibility
|
||||
cmake_policy(SET CMP0063 NEW)
|
||||
|
|
|
|||
|
|
@ -1870,6 +1870,13 @@ vhost hls.srs.com {
|
|||
# Overwrite by env SRS_VHOST_HLS_HLS_WINDOW for all vhosts.
|
||||
# default: 60
|
||||
hls_window 60;
|
||||
# Whether to enable HLS recover mode. When enabled, HLS sequence numbers
|
||||
# continue from where they stopped last time instead of restarting from 0.
|
||||
# This preserves old fragments and provides better player compatibility.
|
||||
# Useful for stream interruption recovery and live event continuity.
|
||||
# Overwrite by env SRS_VHOST_HLS_HLS_RECOVER for all vhosts.
|
||||
# Default: on
|
||||
hls_recover on;
|
||||
# the error strategy. can be:
|
||||
# ignore, disable the hls.
|
||||
# disconnect, require encoder republish.
|
||||
|
|
|
|||
|
|
@ -17,5 +17,6 @@ vhost __defaultVhost__ {
|
|||
hls_path ./objs/nginx/html;
|
||||
hls_fragment 10;
|
||||
hls_window 60;
|
||||
hls_recover on;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ The changelog for SRS.
|
|||
<a name="v7-changes"></a>
|
||||
|
||||
## SRS 7.0 Changelog
|
||||
* v7.0, 2025-08-19, Merge [#3126](https://github.com/ossrs/srs/pull/3126): HLS: restore HLS information when republish stream.(#3088). v7.0.57 (#3126)
|
||||
* v7.0, 2025-08-18, Merge [#4443](https://github.com/ossrs/srs/pull/4443): Support RTMPS server. v7.0.56 (#4443)
|
||||
* v7.0, 2025-08-16, Merge [#4441](https://github.com/ossrs/srs/pull/4441): fix err memory leak in rtc to rtmp bridge. v7.0.55 (#4441)
|
||||
* v7.0, 2025-08-14, Merge [#4161](https://github.com/ossrs/srs/pull/4161): fix hls & dash segments cleanup. v7.0.54 (#4161)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
# CMake minimum version should be called first
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
# Name of the project.
|
||||
# Language "C" is required for find_package(Threads).
|
||||
if (CMAKE_VERSION VERSION_LESS 3.0)
|
||||
project(srs CXX C)
|
||||
else()
|
||||
cmake_policy(SET CMP0048 NEW)
|
||||
project(srs VERSION 4.0.0 LANGUAGES CXX C)
|
||||
endif()
|
||||
cmake_minimum_required(VERSION 2.8.12)
|
||||
cmake_policy(SET CMP0048 NEW)
|
||||
project(srs VERSION 4.0.0 LANGUAGES CXX C)
|
||||
|
||||
# For utest required C++11.
|
||||
set (CMAKE_CXX_STANDARD 11)
|
||||
|
|
|
|||
|
|
@ -2693,7 +2693,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") {
|
||||
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") {
|
||||
return srs_error_new(ERROR_SYSTEM_CONFIG_INVALID, "illegal vhost.hls.%s of %s", m.c_str(), vhost->arg0().c_str());
|
||||
}
|
||||
|
||||
|
|
@ -7523,6 +7523,25 @@ string SrsConfig::get_hls_key_url(std::string vhost)
|
|||
return conf->arg0();
|
||||
}
|
||||
|
||||
bool SrsConfig::get_hls_recover(string vhost)
|
||||
{
|
||||
SRS_OVERWRITE_BY_ENV_BOOL2("srs.vhost.hls.hls_recover"); // SRS_VHOST_HLS_HLS_RECOVER
|
||||
|
||||
static bool DEFAULT = true;
|
||||
|
||||
SrsConfDirective *conf = get_hls(vhost);
|
||||
if (!conf) {
|
||||
return DEFAULT;
|
||||
}
|
||||
|
||||
conf = conf->get("hls_recover");
|
||||
if (!conf || conf->arg0().empty()) {
|
||||
return DEFAULT;
|
||||
}
|
||||
|
||||
return SRS_CONF_PREFER_TRUE(conf->arg0());
|
||||
}
|
||||
|
||||
SrsConfDirective *SrsConfig::get_hds(const string &vhost)
|
||||
{
|
||||
SrsConfDirective *conf = get_vhost(vhost);
|
||||
|
|
|
|||
|
|
@ -1032,6 +1032,10 @@ 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);
|
||||
// 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.
|
||||
virtual bool get_hls_recover(std::string vhost);
|
||||
// hds section
|
||||
private:
|
||||
// Get the hds directive of vhost.
|
||||
|
|
|
|||
|
|
@ -1254,6 +1254,152 @@ srs_error_t SrsHlsMuxer::update_config(SrsRequest *r, string entry_prefix,
|
|||
return err;
|
||||
}
|
||||
|
||||
srs_error_t SrsHlsMuxer::recover_hls()
|
||||
{
|
||||
srs_error_t err = srs_success;
|
||||
|
||||
// exist the m3u8 file.
|
||||
if (!srs_path_exists(m3u8)) {
|
||||
return err;
|
||||
}
|
||||
|
||||
srs_trace("hls: recover stream m3u8=%s, m3u8_url=%s, hls_path=%s",
|
||||
m3u8.c_str(), m3u8_url.c_str(), hls_path.c_str());
|
||||
|
||||
// read whole m3u8 file content as a string
|
||||
SrsFileReader fr;
|
||||
if ((err = fr.open(m3u8)) != srs_success) {
|
||||
return srs_error_wrap(err, "open file");
|
||||
}
|
||||
|
||||
std::string body;
|
||||
if ((err = srs_ioutil_read_all(&fr, body)) != srs_success) {
|
||||
return srs_error_wrap(err, "read data");
|
||||
}
|
||||
if (body.empty()) {
|
||||
return srs_error_wrap(err, "read empty m3u8");
|
||||
}
|
||||
|
||||
bool discon = false;
|
||||
|
||||
std::string ptl;
|
||||
while (!body.empty()) {
|
||||
size_t pos = string::npos;
|
||||
|
||||
std::string line;
|
||||
if ((pos = body.find("\n")) != string::npos) {
|
||||
line = body.substr(0, pos);
|
||||
body = body.substr(pos + 1);
|
||||
} else {
|
||||
line = body;
|
||||
body = "";
|
||||
}
|
||||
|
||||
line = srs_string_replace(line, "\r", "");
|
||||
line = srs_string_replace(line, " ", "");
|
||||
|
||||
// #EXT-X-VERSION:3
|
||||
// the version must be 3.0
|
||||
if (srs_string_starts_with(line, "#EXT-X-VERSION:")) {
|
||||
if (!srs_string_ends_with(line, ":3")) {
|
||||
srs_warn("m3u8 3.0 required, actual is %s", line.c_str());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// #EXT-X-PLAYLIST-TYPE:VOD
|
||||
// the playlist type, vod or nothing.
|
||||
if (srs_string_starts_with(line, "#EXT-X-PLAYLIST-TYPE:")) {
|
||||
ptl = line;
|
||||
continue;
|
||||
}
|
||||
|
||||
// #EXT-X-MEDIA-SEQUENCE:4294967295
|
||||
// the media sequence no.
|
||||
if (srs_string_starts_with(line, "#EXT-X-MEDIA-SEQUENCE:")) {
|
||||
_sequence_no = ::atof(line.substr(string("#EXT-X-MEDIA-SEQUENCE:").length()).c_str());
|
||||
}
|
||||
|
||||
// #EXT-X-DISCONTINUITY
|
||||
// the discontinuity tag.
|
||||
if (srs_string_starts_with(line, "#EXT-X-DISCONTINUITY")) {
|
||||
discon = true;
|
||||
}
|
||||
|
||||
// #EXTINF:11.401,
|
||||
// livestream-5.ts
|
||||
// parse each ts entry, expect current line is inf.
|
||||
if (!srs_string_starts_with(line, "#EXTINF:")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// expect next line is url.
|
||||
std::string ts_url;
|
||||
if ((pos = body.find("\n")) != string::npos) {
|
||||
ts_url = body.substr(0, pos);
|
||||
body = body.substr(pos + 1);
|
||||
} else {
|
||||
srs_warn("ts entry unexpected eof, inf=%s", line.c_str());
|
||||
break;
|
||||
}
|
||||
|
||||
// parse the ts duration.
|
||||
line = line.substr(string("#EXTINF:").length());
|
||||
if ((pos = line.find(",")) != string::npos) {
|
||||
line = line.substr(0, pos);
|
||||
}
|
||||
|
||||
double ts_duration = ::atof(line.c_str());
|
||||
|
||||
// Only create new segment if it doesn't already exist
|
||||
if (!segment_exists(ts_url)) {
|
||||
// load the default acodec, use the same logic as segment_open().
|
||||
SrsAudioCodecId default_acodec = SrsAudioCodecIdDisabled;
|
||||
|
||||
// Now that we know the latest audio codec in stream, use it.
|
||||
if (latest_acodec_ != SrsAudioCodecIdForbidden)
|
||||
default_acodec = latest_acodec_;
|
||||
|
||||
// load the default vcodec, use the same logic as segment_open().
|
||||
SrsVideoCodecId default_vcodec = SrsVideoCodecIdDisabled;
|
||||
|
||||
// Now that we know the latest video codec in stream, use it.
|
||||
if (latest_vcodec_ != SrsVideoCodecIdForbidden)
|
||||
default_vcodec = latest_vcodec_;
|
||||
|
||||
// new segment.
|
||||
SrsHlsSegment *seg = new SrsHlsSegment(context, default_acodec, default_vcodec, writer);
|
||||
seg->sequence_no = _sequence_no++;
|
||||
seg->set_path(hls_path + "/" + req->app + "/" + ts_url);
|
||||
seg->uri = ts_url;
|
||||
seg->set_sequence_header(discon);
|
||||
|
||||
seg->append(0);
|
||||
seg->append(ts_duration * 1000);
|
||||
|
||||
segments->append(seg);
|
||||
} else {
|
||||
// Segment already exists, just increment sequence number to maintain consistency
|
||||
_sequence_no++;
|
||||
}
|
||||
|
||||
discon = false;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
bool SrsHlsMuxer::segment_exists(const std::string &ts_url)
|
||||
{
|
||||
for (int i = 0; i < segments->size(); i++) {
|
||||
SrsHlsSegment *existing_seg = dynamic_cast<SrsHlsSegment *>(segments->at(i));
|
||||
if (existing_seg && existing_seg->uri == ts_url) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
srs_error_t SrsHlsMuxer::segment_open()
|
||||
{
|
||||
srs_error_t err = srs_success;
|
||||
|
|
@ -1822,8 +1968,9 @@ srs_error_t SrsHlsController::on_publish(SrsRequest *req)
|
|||
string hls_key_file_path = _srs_config->get_hls_key_file_path(vhost);
|
||||
string hls_key_url = _srs_config->get_hls_key_url(vhost);
|
||||
|
||||
// TODO: FIXME: support load exists m3u8, to continue publish stream.
|
||||
// TODO: FIXME: support load exists m3u8, to recover publish stream.
|
||||
// for the HLS donot requires the EXT-X-MEDIA-SEQUENCE be monotonically increase.
|
||||
bool recover = _srs_config->get_hls_recover(vhost);
|
||||
|
||||
if ((err = muxer->on_publish(req)) != srs_success) {
|
||||
return srs_error_wrap(err, "muxer publish");
|
||||
|
|
@ -1835,6 +1982,10 @@ srs_error_t SrsHlsController::on_publish(SrsRequest *req)
|
|||
return srs_error_wrap(err, "hls: update config");
|
||||
}
|
||||
|
||||
if (recover && (err = muxer->recover_hls()) != srs_success) {
|
||||
return srs_error_wrap(err, "hls: recover stream");
|
||||
}
|
||||
|
||||
if ((err = muxer->segment_open()) != srs_success) {
|
||||
return srs_error_wrap(err, "hls: segment open");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -294,6 +294,12 @@ private:
|
|||
virtual srs_error_t write_hls_key();
|
||||
virtual srs_error_t refresh_m3u8();
|
||||
virtual srs_error_t _refresh_m3u8(std::string m3u8_file);
|
||||
// Check if a segment with the given URI already exists in the segments list.
|
||||
virtual bool segment_exists(const std::string &ts_url);
|
||||
|
||||
public:
|
||||
// HLS recover mode.
|
||||
srs_error_t recover_hls();
|
||||
};
|
||||
|
||||
// Mux the HLS stream(m3u8 and m4s files).
|
||||
|
|
|
|||
|
|
@ -9,6 +9,6 @@
|
|||
|
||||
#define VERSION_MAJOR 7
|
||||
#define VERSION_MINOR 0
|
||||
#define VERSION_REVISION 56
|
||||
#define VERSION_REVISION 57
|
||||
|
||||
#endif
|
||||
Loading…
Reference in New Issue
Block a user