diff --git a/trunk/doc/CHANGELOG.md b/trunk/doc/CHANGELOG.md index 7da6f521b..fcadc6e4d 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-11-28, SRT: Fix player not exiting when publisher disconnects. v7.0.130 (#4591) * v7.0, 2025-11-27, Merge [#4588](https://github.com/ossrs/srs/pull/4588): RTMP: Ignore FMLE start packet after flash publish. v7.0.129 (#4588) * v7.0, 2025-11-18, AI: API: Change pagination default count to 10, minimum 1. v7.0.128 * v7.0, 2025-11-14, AI: Fix race condition causing immediate deletion of new sources. v7.0.127 (#4449) diff --git a/trunk/src/app/srs_app_config.hpp b/trunk/src/app/srs_app_config.hpp index 477f6cb97..2c8ae2e82 100644 --- a/trunk/src/app/srs_app_config.hpp +++ b/trunk/src/app/srs_app_config.hpp @@ -371,6 +371,8 @@ public: public: // SRT config virtual std::vector get_srt_listens() = 0; + // Get the srt SRTO_PEERIDLETIMEO, peer idle timeout, default is 10000ms. + virtual srs_utime_t get_srto_peeridletimeout() = 0; public: // Stream caster config diff --git a/trunk/src/app/srs_app_srt_conn.cpp b/trunk/src/app/srs_app_srt_conn.cpp index a2a958291..c9ae19a3a 100644 --- a/trunk/src/app/srs_app_srt_conn.cpp +++ b/trunk/src/app/srs_app_srt_conn.cpp @@ -619,6 +619,7 @@ srs_error_t SrsMpegtsSrtConn::do_playing() } int nb_packets = 0; + srs_utime_t timeout = config_->get_srto_peeridletimeout(); while (true) { // Check recv thread error first, so we can detect the client disconnecting event. @@ -634,8 +635,13 @@ srs_error_t SrsMpegtsSrtConn::do_playing() SrsSrtPacket *pkt_raw = NULL; consumer->dump_packet(&pkt_raw); if (!pkt_raw) { - // TODO: FIXME: We should check the quit event. - consumer->wait(1, 1000 * SRS_UTIME_MILLISECONDS); + // Wait for peer_idle_timeout. Note that enqueue() signals the cond, so we wake up + // immediately when packets arrive during normal playback. Only check publisher disconnect + // when no packets available after timeout. @see https://github.com/ossrs/srs/issues/4591 + bool has_msgs = consumer->wait(1, timeout); + if (!has_msgs && srt_source_->can_publish()) { + return srs_error_new(ERROR_SRT_SOURCE_DISCONNECTED, "srt source disconnected"); + } continue; } diff --git a/trunk/src/app/srs_app_srt_source.cpp b/trunk/src/app/srs_app_srt_source.cpp index 3ee893d98..85ac582dc 100644 --- a/trunk/src/app/srs_app_srt_source.cpp +++ b/trunk/src/app/srs_app_srt_source.cpp @@ -293,20 +293,23 @@ srs_error_t SrsSrtConsumer::dump_packet(SrsSrtPacket **ppkt) return err; } -void SrsSrtConsumer::wait(int nb_msgs, srs_utime_t timeout) +bool SrsSrtConsumer::wait(int nb_msgs, srs_utime_t timeout) { mw_min_msgs_ = nb_msgs; - // when duration ok, signal to flush. - if ((int)queue_.size() > mw_min_msgs_) { - return; + // When duration ok, signal to flush. + if ((int)queue_.size() >= mw_min_msgs_) { + return true; } - // the enqueue will notify this cond. + // The enqueue will notify this cond. mw_waiting_ = true; - // use cond block wait for high performance mode. + // Use cond block wait for high performance mode. srs_cond_timedwait(mw_wait_, timeout); + + // Return true if there are enough messages after wait. + return (int)queue_.size() >= mw_min_msgs_; } SrsSrtFrameBuilder::SrsSrtFrameBuilder(ISrsFrameTarget *target) diff --git a/trunk/src/app/srs_app_srt_source.hpp b/trunk/src/app/srs_app_srt_source.hpp index 76e2e3559..caecde1c1 100644 --- a/trunk/src/app/srs_app_srt_source.hpp +++ b/trunk/src/app/srs_app_srt_source.hpp @@ -113,7 +113,9 @@ public: public: virtual srs_error_t enqueue(SrsSrtPacket *packet) = 0; virtual srs_error_t dump_packet(SrsSrtPacket **ppkt) = 0; - virtual void wait(int nb_msgs, srs_utime_t timeout) = 0; + // Wait for at-least some messages incoming in queue. + // @return true if there are enough messages, false if timeout. + virtual bool wait(int nb_msgs, srs_utime_t timeout) = 0; }; // The SRT consumer, consume packets from SRT stream source. @@ -146,7 +148,8 @@ public: // For SRT, we only got one packet, because there is not many packets in queue. virtual srs_error_t dump_packet(SrsSrtPacket **ppkt); // Wait for at-least some messages incoming in queue. - virtual void wait(int nb_msgs, srs_utime_t timeout); + // @return true if there are enough messages, false if timeout. + virtual bool wait(int nb_msgs, srs_utime_t timeout); }; // The SRT format interface. @@ -271,6 +274,8 @@ public: virtual SrsContextId source_id() = 0; virtual SrsContextId pre_source_id() = 0; virtual void on_consumer_destroy(ISrsSrtConsumer *consumer) = 0; + // Whether we can publish stream to the source, return true if no publisher. + virtual bool can_publish() = 0; }; // A SRT source is a stream, to publish and to play with. diff --git a/trunk/src/core/srs_core_version7.hpp b/trunk/src/core/srs_core_version7.hpp index 8f4fc3fd3..e43b7fc68 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 129 +#define VERSION_REVISION 130 #endif \ No newline at end of file diff --git a/trunk/src/kernel/srs_kernel_error.hpp b/trunk/src/kernel/srs_kernel_error.hpp index 329e15fb5..73f7505ca 100644 --- a/trunk/src/kernel/srs_kernel_error.hpp +++ b/trunk/src/kernel/srs_kernel_error.hpp @@ -397,7 +397,8 @@ XX(ERROR_SRT_SOURCE_BUSY, 6007, "SrtStreamBusy", "SRT stream already exists or busy") \ XX(ERROR_RTMP_TO_SRT, 6008, "SrtFromRtmp", "Covert RTMP to SRT failed") \ XX(ERROR_SRT_STATS, 6009, "SrtStats", "SRT get statistic data failed") \ - XX(ERROR_SRT_TO_RTMP_EMPTY_SPS_PPS, 6010, "SrtToRtmpEmptySpsPps", "SRT to rtmp have empty sps or pps") + XX(ERROR_SRT_TO_RTMP_EMPTY_SPS_PPS, 6010, "SrtToRtmpEmptySpsPps", "SRT to rtmp have empty sps or pps") \ + XX(ERROR_SRT_SOURCE_DISCONNECTED, 6011, "SrtSourceDisconnected", "SRT source publisher disconnected") /**************************************************/ /* For user-define error. */ diff --git a/trunk/src/utest/srs_utest_ai21.cpp b/trunk/src/utest/srs_utest_ai21.cpp index 8e4c8b538..20f71b861 100644 --- a/trunk/src/utest/srs_utest_ai21.cpp +++ b/trunk/src/utest/srs_utest_ai21.cpp @@ -1425,8 +1425,9 @@ srs_error_t MockSrtConsumer::dump_packet(SrsSrtPacket **ppkt) return srs_success; } -void MockSrtConsumer::wait(int nb_msgs, srs_utime_t timeout) +bool MockSrtConsumer::wait(int nb_msgs, srs_utime_t timeout) { + return true; } void MockSrtConsumer::set_enqueue_error(srs_error_t err) diff --git a/trunk/src/utest/srs_utest_ai21.hpp b/trunk/src/utest/srs_utest_ai21.hpp index 017d3b202..d30dcf6a9 100644 --- a/trunk/src/utest/srs_utest_ai21.hpp +++ b/trunk/src/utest/srs_utest_ai21.hpp @@ -107,7 +107,7 @@ public: virtual ~MockSrtConsumer(); virtual srs_error_t enqueue(SrsSrtPacket *packet); virtual srs_error_t dump_packet(SrsSrtPacket **ppkt); - virtual void wait(int nb_msgs, srs_utime_t timeout); + virtual bool wait(int nb_msgs, srs_utime_t timeout); void set_enqueue_error(srs_error_t err); void reset(); }; diff --git a/trunk/src/utest/srs_utest_manual_mock.hpp b/trunk/src/utest/srs_utest_manual_mock.hpp index e98e7fd4b..ba4ed6be6 100644 --- a/trunk/src/utest/srs_utest_manual_mock.hpp +++ b/trunk/src/utest/srs_utest_manual_mock.hpp @@ -534,6 +534,7 @@ public: virtual bool get_srt_enabled(std::string vhost) { return srt_enabled_; } virtual std::string get_srt_default_streamid() { return "#!::r=live/livestream,m=request"; } virtual bool get_srt_to_rtmp(std::string vhost) { return srt_to_rtmp_; } + virtual srs_utime_t get_srto_peeridletimeout() { return 10 * SRS_UTIME_SECONDS; } virtual bool get_rtc_to_rtmp(std::string vhost) { return rtc_to_rtmp_; } virtual srs_utime_t get_rtc_stun_timeout(std::string vhost) { return 30 * SRS_UTIME_SECONDS; } virtual bool get_rtc_stun_strict_check(std::string vhost) { return false; } diff --git a/trunk/src/utest/srs_utest_workflow_srt_conn.cpp b/trunk/src/utest/srs_utest_workflow_srt_conn.cpp index 687147b66..198e783f3 100644 --- a/trunk/src/utest/srs_utest_workflow_srt_conn.cpp +++ b/trunk/src/utest/srs_utest_workflow_srt_conn.cpp @@ -174,6 +174,10 @@ VOID TEST(BasicWorkflowSrtConnTest, ManuallyVerifyForPlayer) mock_srt_conn->streamid_ = "#!::h=127.0.0.1,r=live/livestream,m=request"; mock_srt_conn->srt_fd_ = 100; + // Simulate a publisher is connected (can_publish=false means publisher exists) + // @see https://github.com/ossrs/srs/issues/4591 + mock_srt_sources->set_can_publish(false); + // Create SrsMpegtsSrtConn - it takes ownership of srt_conn SrsUniquePtr conn(new SrsMpegtsSrtConn(mock_manager.get(), 100, "192.168.1.100", 9000));