diff --git a/trunk/3rdparty/signaling/Dockerfile b/trunk/3rdparty/signaling/Dockerfile
index 393e57004..66d8f02ab 100644
--- a/trunk/3rdparty/signaling/Dockerfile
+++ b/trunk/3rdparty/signaling/Dockerfile
@@ -2,7 +2,7 @@
############################################################
# build
############################################################
-FROM registry.cn-hangzhou.aliyuncs.com/ossrs/srs:dev AS build
+FROM ossrs/srs:ubuntu20 AS build
COPY . /tmp/signaling
RUN cd /tmp/signaling && make
@@ -12,7 +12,7 @@ RUN cp -R /tmp/signaling/www /usr/local/
############################################################
# dist
############################################################
-FROM centos:7 AS dist
+FROM ubuntu:focal AS dist
# HTTP/1989
EXPOSE 1989
diff --git a/trunk/3rdparty/srs-docs/doc/edge.md b/trunk/3rdparty/srs-docs/doc/edge.md
index ea7675ea1..d0790ac1d 100644
--- a/trunk/3rdparty/srs-docs/doc/edge.md
+++ b/trunk/3rdparty/srs-docs/doc/edge.md
@@ -136,6 +136,15 @@ the HLS, all edge server never output HLS util client access the RTMP stream on
That is, never config HLS on edge server, it's no use. The HLS delivery must use squid or
traffic server to cache the HTTP origin server.
+## WebRTC Edge
+
+Currently edge cluster only support RTMP and HTTP-FLV, doesn't support WebRTC. A new version
+of Edge Cluster is planning, see [#4402](https://github.com/ossrs/srs/discussions/4402) for details.
+
+Note that you can use new version of [Origin Cluster](./origin-cluster.md) for WebRTC, as it
+can also used to expand the capacity of streams. However, if you need to expand the capacity
+of viewers for one stream, you still need the new version of Edge Cluster.
+
## Transform Vhost
The design of CDN stream system, always use `up.xxxx` and `down.xxxx` to operate them, for example, user publish to cdn by host `up.srs.com` and play by `down.srs.com`.
diff --git a/trunk/3rdparty/srs-docs/doc/flv.md b/trunk/3rdparty/srs-docs/doc/flv.md
index 1edaf54a6..a1c0095e5 100644
--- a/trunk/3rdparty/srs-docs/doc/flv.md
+++ b/trunk/3rdparty/srs-docs/doc/flv.md
@@ -209,5 +209,54 @@ Why use HTTP FLV? HTTP FLV streaming is becoming more popular. The main advantag
5. Universality: Flash can play both RTMP and HTTP FLV. Custom apps and mainstream players also support HTTP FLV playback.
6. Simplicity: FLV is the simplest streaming media encapsulation, and HTTP is the most widely used protocol. Combining these two makes maintenance much easier than RTMP.
+## IPv6
+
+SRS (v7.0.67+) supports IPv6 for HTTP-FLV streaming, enabling dual-stack (IPv4/IPv6) operation for low-latency live streaming. This allows HTTP-FLV clients to access streams using IPv6 addresses while maintaining full compatibility with existing IPv4 infrastructure.
+
+IPv6 support is enabled automatically when SRS detects IPv6 addresses in the HTTP server configuration. Configure the HTTP server to listen on IPv6 addresses:
+
+```bash
+http_server {
+ enabled on;
+ # Listen on both IPv4 and IPv6
+ listen 8080 [::]:8080;
+ dir ./objs/nginx/html;
+}
+```
+
+Access HTTP-FLV streams via IPv6:
+
+```bash
+# HTTP-FLV stream via IPv6
+http://[::1]:8080/live/livestream.flv
+```
+
+Play HTTP-FLV stream via IPv6 using FFplay:
+
+```bash
+ffplay 'http://[::1]:8080/live/livestream.flv'
+```
+
+SRS supports dual-stack HTTP-FLV operation, allowing both IPv4 and IPv6 clients to access streams simultaneously:
+
+```bash
+http_server {
+ enabled on;
+ # Listen on both IPv4 and IPv6
+ listen 8080 [::]:8080;
+ dir ./objs/nginx/html;
+}
+vhost __defaultVhost__ {
+ http_remux {
+ enabled on;
+ mount [vhost]/[app]/[stream].flv;
+ }
+}
+```
+
+This configuration allows:
+- IPv4 clients: `http://192.168.1.100:8080/live/livestream.flv`
+- IPv6 clients: `http://[2001:db8::1]:8080/live/livestream.flv`
+

diff --git a/trunk/3rdparty/srs-docs/doc/hls.md b/trunk/3rdparty/srs-docs/doc/hls.md
index 78792543a..2322e274e 100644
--- a/trunk/3rdparty/srs-docs/doc/hls.md
+++ b/trunk/3rdparty/srs-docs/doc/hls.md
@@ -587,5 +587,52 @@ The generated m3u8 playlist will reference fMP4 segments instead of TS segments,
Play the stream by SRS player: [http://localhost:8080/live/livestream.m3u8](http://localhost:8080/players/srs_player.html?stream=livestream.m3u8)
+## IPv6
+
+SRS (v7.0.67+) supports IPv6 for HLS streaming, enabling dual-stack (IPv4/IPv6) operation for HTTP-based live streaming. This allows HLS clients to access streams using IPv6 addresses while maintaining full compatibility with existing IPv4 infrastructure.
+
+IPv6 support is enabled automatically when SRS detects IPv6 addresses in the HTTP server configuration. Configure the HTTP server to listen on IPv6 addresses:
+
+```bash
+http_server {
+ enabled on;
+ # Listen on both IPv4 and IPv6
+ listen 8080 [::]:8080;
+ dir ./objs/nginx/html;
+}
+```
+
+Access HLS streams via IPv6:
+
+```bash
+# HLS stream via IPv6
+http://[::1]:8080/live/livestream.m3u8
+
+# HLS segments are also accessible via IPv6
+http://[::1]:8080/live/livestream-1.ts
+http://[::1]:8080/live/livestream-2.ts
+```
+
+Play HLS stream via IPv6 using FFplay:
+
+```bash
+ffplay 'http://[::1]:8080/live/livestream.m3u8'
+```
+
+SRS supports dual-stack HLS operation, allowing both IPv4 and IPv6 clients to access streams simultaneously:
+
+```bash
+http_server {
+ enabled on;
+ # Listen on both IPv4 and IPv6
+ listen 8080 [::]:8080;
+ dir ./objs/nginx/html;
+}
+```
+
+This configuration allows:
+- IPv4 clients: `http://192.168.1.100:8080/live/livestream.m3u8`
+- IPv6 clients: `http://[2001:db8::1]:8080/live/livestream.m3u8`
+

diff --git a/trunk/3rdparty/srs-docs/doc/rtmp.md b/trunk/3rdparty/srs-docs/doc/rtmp.md
index 1aa98dfd0..05c9a5743 100644
--- a/trunk/3rdparty/srs-docs/doc/rtmp.md
+++ b/trunk/3rdparty/srs-docs/doc/rtmp.md
@@ -341,6 +341,62 @@ openssl req -new -x509 -key server.key -out server.crt -days 3650 \
For production environments, use certificates from a trusted Certificate Authority (CA) or Let's Encrypt.
+## IPv6
+
+SRS (v7.0.67+) supports IPv6 for RTMP and RTMPS protocols, enabling dual-stack (IPv4/IPv6) operation. This allows clients to connect using IPv6 addresses while maintaining full compatibility with existing IPv4 infrastructure.
+
+IPv6 support is enabled automatically when SRS detects IPv6 addresses in the configuration. You can configure SRS to listen on IPv6 addresses by specifying them in the `listen` directive:
+
+```bash
+rtmp {
+ # Listen on both IPv4 and IPv6
+ listen 1935 [::]:1935;
+}
+
+# For RTMPS
+rtmps {
+ enabled on;
+ listen 1443 [::]:1443;
+ key ./conf/server.key;
+ cert ./conf/server.crt;
+}
+```
+
+Publish RTMP stream via IPv6:
+
+```bash
+ffmpeg -re -i ./doc/source.flv -c copy -f flv 'rtmp://[::1]:1935/live/livestream'
+```
+
+Publish RTMPS stream via IPv6:
+
+```bash
+ffmpeg -re -i ./doc/source.flv -c copy -f flv 'rtmps://[::1]:1443/live/livestream'
+```
+
+Play RTMP stream via IPv6:
+
+```bash
+ffplay 'rtmp://[::1]:1935/live/livestream'
+```
+
+Play RTMPS stream via IPv6:
+
+```bash
+ffplay 'rtmps://[::1]:1443/live/livestream'
+```
+
+SRS supports dual-stack operation, allowing both IPv4 and IPv6 clients to connect simultaneously:
+
+```bash
+# Listen on both IPv4 and IPv6 addresses
+listen 1935 [::]:1935;
+
+# This allows connections from:
+# - IPv4 clients: rtmp://192.168.1.100:1935/live/stream
+# - IPv6 clients: rtmp://[2001:db8::1]:1935/live/stream
+```
+
## On Demand Live Streaming
In some situations, you might want to start streaming only when someone starts watching:
diff --git a/trunk/3rdparty/srs-docs/doc/rtsp.md b/trunk/3rdparty/srs-docs/doc/rtsp.md
index e52153e90..32a10b7ed 100644
--- a/trunk/3rdparty/srs-docs/doc/rtsp.md
+++ b/trunk/3rdparty/srs-docs/doc/rtsp.md
@@ -131,6 +131,66 @@ go test ./blackbox -mod=vendor -v -count=1 -run=TestFast_RtmpPublish_RtspPlay_Ba
The current version implements only basic functionality. Additional features like authentication, redirection, and RTCP will be planned according to actual needs, possibly in the near future.
+## IPv6
+
+SRS (v7.0.67+) supports IPv6 for RTSP protocol, enabling dual-stack (IPv4/IPv6) operation for standards-based streaming. This allows RTSP clients to connect using IPv6 addresses while maintaining full compatibility with existing IPv4 infrastructure.
+
+IPv6 support is enabled automatically when SRS detects IPv6 addresses in the configuration. Configure the RTSP server to listen on IPv6 addresses:
+
+```bash
+rtsp_server {
+ enabled on;
+ # Listen on both IPv4 and IPv6 for UDP media
+ listen 8554 [::]:8554;
+}
+
+vhost __defaultVhost__ {
+ rtsp {
+ enabled on;
+ rtmp_to_rtsp on;
+ }
+}
+```
+
+Play RTSP stream via IPv6 using FFplay (TCP transport only):
+
+```bash
+ffplay -rtsp_transport tcp -i 'rtsp://[::1]:8554/live/livestream'
+```
+
+Play RTSP stream via IPv6 using VLC:
+
+```bash
+vlc 'rtsp://[::1]:8554/live/livestream'
+```
+
+When using IPv6 addresses in RTSP URLs, the IPv6 address must be enclosed in square brackets:
+
+```bash
+# Local IPv6 loopback
+rtsp://[::1]:8554/live/livestream
+
+# Public IPv6 address
+rtsp://[2001:db8::1]:8554/live/livestream
+
+# With authentication (if implemented)
+rtsp://user:pass@[2001:db8::1]:8554/live/livestream
+```
+
+SRS supports dual-stack RTSP operation, allowing both IPv4 and IPv6 clients to connect simultaneously:
+
+```bash
+rtsp_server {
+ enabled on;
+ # Listen on both IPv4 and IPv6
+ listen 8554 [::]:8554;
+}
+```
+
+This configuration allows:
+- IPv4 clients: `rtsp://192.168.1.100:8554/live/livestream`
+- IPv6 clients: `rtsp://[2001:db8::1]:8554/live/livestream`
+
## References
- [rfc2326-1998-rtsp.pdf](/files/rfc2326-1998-rtsp.pdf)
\ No newline at end of file
diff --git a/trunk/3rdparty/srs-docs/doc/srt.md b/trunk/3rdparty/srs-docs/doc/srt.md
index 2844740f9..055ba3d80 100644
--- a/trunk/3rdparty/srs-docs/doc/srt.md
+++ b/trunk/3rdparty/srs-docs/doc/srt.md
@@ -43,7 +43,8 @@ Open the following page to play the stream (if SRS is not on your local machine,
* RTMP(VLC/ffplay): `rtmp://localhost/live/livestream`
* HLS by SRS player: [http://localhost:8080/live/livestream.flv](http://localhost:8080/players/srs_player.html)
-* SRT(VLC/ffplay): `srt://127.0.0.1:10080?streamid=#!::r=live/livestream,m=request`
+* SRT(ffplay): `srt://127.0.0.1:10080?streamid=#!::r=live/livestream,m=request`
+* SRT(VLC): `srt://127.0.0.1:10080`
SRS supports converting SRT to other protocols, which will be described in detail below.
@@ -381,6 +382,121 @@ How does SRS implement SRT? Based on coroutine-based SRT architecture, we need t
> Note: Please note that the SRT in SRS 4.0 is a non-ST architecture, and it is implemented by launching a separate thread, which may not have the same level of maintainability as the native ST coroutine architecture.
+## IPv6
+
+SRS (v7.0.67+) supports IPv6 for SRT protocol, enabling dual-stack (IPv4/IPv6) operation for low-latency streaming. This allows SRT clients to connect using IPv6 addresses while maintaining full compatibility with existing IPv4 infrastructure.
+
+IPv6 support is enabled automatically when SRS detects IPv6 addresses in the configuration. Configure the SRT server to listen on IPv6 addresses:
+
+```bash
+srt_server {
+ enabled on;
+ # Listen on both IPv4 and IPv6
+ listen 10080 [::]:10080;
+
+ # Other SRT parameters remain the same
+ maxbw 1000000000;
+ mss 1500;
+ connect_timeout 4000;
+ peer_idle_timeout 8000;
+ default_app live;
+ peerlatency 0;
+ recvlatency 0;
+}
+```
+
+Publish SRT stream via IPv6 using FFmpeg:
+
+```bash
+ffmpeg -re -i ./doc/source.flv -c copy -pes_payload_size 0 -f mpegts \
+ 'srt://[::1]:10080?streamid=#!::r=live/livestream,m=publish'
+```
+
+Play SRT stream via IPv6 using FFplay:
+
+```bash
+ffplay 'srt://[::1]:10080?streamid=#!::r=live/livestream,m=request'
+```
+
+When using IPv6 addresses in SRT URLs, the IPv6 address must be enclosed in square brackets:
+
+```bash
+# Publishing
+srt://[2001:db8::1]:10080?streamid=#!::r=live/livestream,m=publish
+
+# Playing
+srt://[2001:db8::1]:10080?streamid=#!::r=live/livestream,m=request
+
+# With vhost support
+srt://[2001:db8::1]:10080?streamid=#!::h=srs.srt.com.cn,r=live/livestream,m=publish
+```
+
+SRS supports dual-stack SRT operation, allowing both IPv4 and IPv6 clients to connect simultaneously:
+
+```bash
+srt_server {
+ enabled on;
+ # Listen on both IPv4 and IPv6
+ listen 10080 [::]:10080;
+}
+```
+
+This configuration allows:
+- IPv4 clients: `srt://192.168.1.100:10080?streamid=#!::r=live/stream,m=publish`
+- IPv6 clients: `srt://[2001:db8::1]:10080?streamid=#!::r=live/stream,m=publish`
+
+## VLC
+
+VLC has an important limitation: it does not support the `streamid` URL parameter. When VLC connects
+to an SRT server, it always sends an empty `SRTO_STREAMID` socket option, regardless of what you put
+in the URL. This means VLC can only use the simple URL format `srt://127.0.0.1:10080` without any
+streamid parameter.
+
+To support VLC and other clients that don't set `SRTO_STREAMID`, SRS provides a `default_streamid`
+configuration option. When a client connects without setting streamid, SRS will use this configured
+default value. By default, SRS uses `#!::r=live/livestream,m=publish` for backward compatibility,
+but for VLC playback, you should configure it to use `m=request` mode instead.
+
+SRS provides a ready-to-use configuration file `conf/srt.vlc.conf` optimized for VLC compatibility.
+Start SRS with this configuration:
+
+```bash
+./objs/srs -c conf/srt.vlc.conf
+```
+
+You can also set the default streamid using an environment variable, which is useful for Docker deployments:
+
+```bash
+env SRS_SRT_SERVER_DEFAULT_STREAMID="#!::r=live/livestream,m=request" \
+ ./objs/srs -c conf/srt.conf
+```
+
+Here's a complete workflow example. First, publish a stream with FFmpeg (which explicitly sets streamid
+with `m=publish`):
+
+```bash
+ffmpeg -re -i ./doc/source.flv -c copy -pes_payload_size 0 -f mpegts \
+ 'srt://127.0.0.1:10080?streamid=#!::r=live/livestream,m=publish'
+```
+
+Then play with VLC using the simple URL (VLC will use the server's default streamid with `m=request`):
+
+- Open VLC Media Player
+- Go to Media → Open Network Stream
+- Enter URL: `srt://127.0.0.1:10080`
+- Click Play
+
+> Note: VLC doesn't support SRT with streamid, so you should use the simple URL format `srt://127.0.0.1:10080` without any streamid parameter.
+
+You can also play with FFplay by explicitly setting the streamid:
+
+```bash
+ffplay 'srt://127.0.0.1:10080?streamid=#!::r=live/livestream,m=request'
+```
+
+The key difference between clients: VLC always uses the server's `default_streamid` configuration, while
+FFmpeg/FFplay/OBS can set streamid in the URL or settings, which overrides the server default.
+
## Q&A
1. Does SRS support forwarding SRT streams to Nginx?
diff --git a/trunk/3rdparty/srs-docs/doc/webrtc.md b/trunk/3rdparty/srs-docs/doc/webrtc.md
index 7982bee85..83b2939ed 100644
--- a/trunk/3rdparty/srs-docs/doc/webrtc.md
+++ b/trunk/3rdparty/srs-docs/doc/webrtc.md
@@ -516,6 +516,75 @@ Output:
Winlin 2020.02
+## IPv6
+
+SRS (v7.0.67+) supports IPv6 for WebRTC protocols, enabling dual-stack (IPv4/IPv6) operation for both UDP and TCP media transport. This includes support for WHIP/WHEP signaling and media transmission over IPv6.
+
+IPv6 support is enabled automatically when SRS detects IPv6 addresses in the configuration. Configure the RTC server to listen on IPv6 addresses:
+
+```bash
+rtc_server {
+ enabled on;
+ # Listen on both IPv4 and IPv6 for UDP media
+ listen 8000 [::]:8000;
+
+ # For WebRTC over TCP
+ tcp {
+ enabled on;
+ listen 8000 [::]:8000;
+ }
+}
+
+# HTTP API server for WHIP/WHEP over IPv6
+http_api {
+ enabled on;
+ listen 1985 [::]:1985;
+}
+
+# HTTPS API server for secure WHIP/WHEP over IPv6
+https_api {
+ enabled on;
+ listen 1990 [::]:1990;
+ key ./conf/server.key;
+ cert ./conf/server.crt;
+}
+```
+
+Publish using WHIP via IPv6:
+- WHIP URL: `http://[::1]:1985/rtc/v1/whip/?app=live&stream=livestream`
+- Test page: [http://[::1]:8080/players/whip.html](http://[::1]:8080/players/whip.html)
+
+Play using WHEP via IPv6:
+- WHEP URL: `http://[::1]:1985/rtc/v1/whep/?app=live&stream=livestream`
+- Test page: [http://[::1]:8080/players/whep.html](http://[::1]:8080/players/whep.html)
+
+For secure connections over IPv6:
+
+Publish using WHIP via HTTPS IPv6:
+- WHIP URL: `https://[::1]:1990/rtc/v1/whip/?app=live&stream=livestream`
+- Test page: [https://[::1]:8088/players/whip.html](https://[::1]:8088/players/whip.html)
+
+Play using WHEP via HTTPS IPv6:
+- WHEP URL: `https://[::1]:1990/rtc/v1/whep/?app=live&stream=livestream`
+- Test page: [https://[::1]:8088/players/whep.html](https://[::1]:8088/players/whep.html)
+
+SRS supports dual-stack WebRTC, allowing both IPv4 and IPv6 clients to connect simultaneously:
+
+```bash
+rtc_server {
+ enabled on;
+ # Listen on both IPv4 and IPv6
+ listen 8000 [::]:8000;
+
+ # Configure candidates for both protocols
+ candidate 192.168.1.100 [2001:db8::1];
+}
+```
+
+This enables:
+- IPv4 clients to connect via: `http://192.168.1.100:1985/rtc/v1/whip/`
+- IPv6 clients to connect via: `http://[2001:db8::1]:1985/rtc/v1/whip/`
+

diff --git a/trunk/3rdparty/srs-docs/pages/contact-en.md b/trunk/3rdparty/srs-docs/pages/contact-en.md
index e6e90722c..a1ff1e6db 100644
--- a/trunk/3rdparty/srs-docs/pages/contact-en.md
+++ b/trunk/3rdparty/srs-docs/pages/contact-en.md
@@ -13,19 +13,7 @@ can be resolved through the documentation, and in cases where you still encounte
assistance from other community members. Friends in the community might offer help, but they are not
obligated to do so, as it's a universal rule of open-source communities.
-I have a full-time job and support the community during my spare time, focusing primarily on code and
-documentation development. As a result, I have limited time available for community support and prioritize
-assistance to project contributors. If you wish for me to dedicate time specifically to assist you, you
-may consider becoming a backer or sponsor of the project.
-
-Our documentation and code are open and free, so you don't need to sponsor the community to access these resources. We are a truly open-source community. For financial sponsors, we also offer additional support, including:
-
-* Backer: $5 per month. Online text chat support through Discord. No custom development included.
-* Sponsor: $100 per month. Online text chat and online meeting support. No custom development included.
-* Custom Development: Must be a Backer or Sponsor first. Fees based on duration, evaluated separately. Please contact us on Discord.
-
-Please visit [OpenCollective](https://opencollective.com/srs-server) to become a backer or sponsor,
-and send me a direct message on [Discord](https://discord.gg/yZ4BnPmHAd).
+Our documentation and code are open and free, so you don't need to sponsor the community to access these resources. We are a truly open-source community. Please visit [OpenCollective](https://opencollective.com/srs-server) to become a backer or sponsor.
## Discussion
diff --git a/trunk/configure b/trunk/configure
index 1eda003fa..238792df3 100755
--- a/trunk/configure
+++ b/trunk/configure
@@ -384,8 +384,8 @@ if [[ $SRS_UTEST == YES ]]; then
"srs_utest_coworkers" "srs_utest_pithy_print" "srs_utest_kernel3" "srs_utest_protocol4"
"srs_utest_protocol3" "srs_utest_app" "srs_utest_app2" "srs_utest_app3" "srs_utest_app4"
"srs_utest_app5" "srs_utest_app6" "srs_utest_app7" "srs_utest_app8" "srs_utest_app9"
- "srs_utest_app10" "srs_utest_app11" "srs_utest_app15" "srs_utest_app16"
- "srs_utest_app17")
+ "srs_utest_app10" "srs_utest_app11" "srs_utest_app15" "srs_utest_app16"
+ "srs_utest_app17" "srs_utest_rtc_playstream")
# Always include SRT utest
MODULE_FILES+=("srs_utest_srt")
if [[ $SRS_GB28181 == YES ]]; then
diff --git a/trunk/doc/CHANGELOG.md b/trunk/doc/CHANGELOG.md
index b3efba63b..0b12608da 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-16, Merge [#4530](https://github.com/ossrs/srs/pull/4530): fix crash issue caused by reload configuration file. v7.0.98 (#4530)
* v7.0, 2025-10-15, Merge [#4520](https://github.com/ossrs/srs/pull/4520): srs_app_rtc_conn: fix illegal memory access. v7.0.97 (#4520)
* v7.0, 2025-10-14, Disable sanitizer by default to fix memory leak. (#4364) v7.0.96
* v7.0, 2025-10-01, SRT: Support configurable default_streamid option. v7.0.95 (#4515)
diff --git a/trunk/src/app/srs_app_config.cpp b/trunk/src/app/srs_app_config.cpp
index d541a625b..12f9ac8e4 100644
--- a/trunk/src/app/srs_app_config.cpp
+++ b/trunk/src/app/srs_app_config.cpp
@@ -1509,6 +1509,12 @@ srs_error_t SrsConfig::reload_conf(SrsConfig *conf)
SrsConfDirective *old_vhost = old_root->get("vhost", vhost);
SrsConfDirective *new_vhost = root_->get("vhost", vhost);
+ // Only compare config when both old and new vhost exist.
+ // @see https://github.com/ossrs/srs/issues/4529
+ if (!old_vhost || !new_vhost) {
+ continue;
+ }
+
// chunk_size, only one per vhost.
if (!srs_directive_equals(new_vhost->get("chunk_size"), old_vhost->get("chunk_size"))) {
for (it = subscribes_.begin(); it != subscribes_.end(); ++it) {
diff --git a/trunk/src/app/srs_app_rtc_conn.cpp b/trunk/src/app/srs_app_rtc_conn.cpp
index 8c60942dd..2a8b19ef2 100644
--- a/trunk/src/app/srs_app_rtc_conn.cpp
+++ b/trunk/src/app/srs_app_rtc_conn.cpp
@@ -21,6 +21,7 @@ using namespace std;
#include
#include
+#include
#include
#include
#include
@@ -304,6 +305,14 @@ ISrsRtcPliWorkerHandler::~ISrsRtcPliWorkerHandler()
{
}
+ISrsRtcPliWorker::ISrsRtcPliWorker()
+{
+}
+
+ISrsRtcPliWorker::~ISrsRtcPliWorker()
+{
+}
+
SrsRtcPliWorker::SrsRtcPliWorker(ISrsRtcPliWorkerHandler *h)
{
handler_ = h;
@@ -455,6 +464,7 @@ SrsRtcPlayStream::SrsRtcPlayStream(ISrsExecRtcAsyncTask *exec, ISrsExpire *expir
config_ = _srs_config;
rtc_sources_ = _srs_rtc_sources;
stat_ = _srs_stat;
+ app_factory_ = _srs_app_factory;
}
SrsRtcPlayStream::~SrsRtcPlayStream()
@@ -491,6 +501,7 @@ SrsRtcPlayStream::~SrsRtcPlayStream()
config_ = NULL;
rtc_sources_ = NULL;
stat_ = NULL;
+ app_factory_ = NULL;
}
srs_error_t SrsRtcPlayStream::initialize(ISrsRequest *req, std::map sub_relations)
@@ -617,7 +628,7 @@ srs_error_t SrsRtcPlayStream::start()
}
srs_freep(trd_);
- trd_ = new SrsFastCoroutine("rtc_sender", this, cid_);
+ trd_ = app_factory_->create_coroutine("rtc_sender", this, cid_);
if ((err = trd_->start()) != srs_success) {
return srs_error_wrap(err, "rtc_sender");
diff --git a/trunk/src/app/srs_app_rtc_conn.hpp b/trunk/src/app/srs_app_rtc_conn.hpp
index 494fb13b6..a15dfbfce 100644
--- a/trunk/src/app/srs_app_rtc_conn.hpp
+++ b/trunk/src/app/srs_app_rtc_conn.hpp
@@ -64,6 +64,8 @@ class ISrsSrtSourceManager;
class ISrsLiveSourceManager;
class SrsRtcPublisherNegotiator;
class SrsRtcPlayerNegotiator;
+class ISrsAppFactory;
+class ISrsCoroutine;
const uint8_t kSR = 200;
const uint8_t kRR = 201;
@@ -192,8 +194,20 @@ public:
virtual srs_error_t do_request_keyframe(uint32_t ssrc, SrsContextId cid) = 0;
};
+// The interface for PLI worker coroutine.
+class ISrsRtcPliWorker : public ISrsCoroutineHandler
+{
+public:
+ ISrsRtcPliWorker();
+ virtual ~ISrsRtcPliWorker();
+
+public:
+ virtual srs_error_t start() = 0;
+ virtual void request_keyframe(uint32_t ssrc, SrsContextId cid) = 0;
+};
+
// A worker coroutine to request the PLI.
-class SrsRtcPliWorker : public ISrsCoroutineHandler
+class SrsRtcPliWorker : public ISrsRtcPliWorker
{
// clang-format off
SRS_DECLARE_PRIVATE: // clang-format on
@@ -247,6 +261,7 @@ SRS_DECLARE_PRIVATE: // clang-format on
ISrsExecRtcAsyncTask *exec_;
ISrsExpire *expire_;
ISrsRtcPacketSender *sender_;
+ ISrsAppFactory *app_factory_;
// clang-format off
SRS_DECLARE_PRIVATE: // clang-format on
@@ -257,8 +272,8 @@ SRS_DECLARE_PRIVATE: // clang-format on
// clang-format off
SRS_DECLARE_PRIVATE: // clang-format on
SrsContextId cid_;
- SrsFastCoroutine *trd_;
- SrsRtcPliWorker *pli_worker_;
+ ISrsCoroutine *trd_;
+ ISrsRtcPliWorker *pli_worker_;
// clang-format off
SRS_DECLARE_PRIVATE: // clang-format on
diff --git a/trunk/src/core/srs_core_version7.hpp b/trunk/src/core/srs_core_version7.hpp
index 8148205a2..da4ac8dd8 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 97
+#define VERSION_REVISION 98
#endif
\ No newline at end of file
diff --git a/trunk/src/utest/srs_utest_reload.cpp b/trunk/src/utest/srs_utest_reload.cpp
index 11abf8c82..2d342e3ed 100644
--- a/trunk/src/utest/srs_utest_reload.cpp
+++ b/trunk/src/utest/srs_utest_reload.cpp
@@ -111,3 +111,39 @@ VOID TEST(ConfigReloadTest, ReloadVhostChunkSize)
EXPECT_EQ(1, handler.count_true());
handler.reset();
}
+
+VOID TEST(ConfigReloadTest, ReloadAddNewVhost)
+{
+ srs_error_t err = srs_success;
+
+ MockReloadHandler handler;
+ MockSrsReloadConfig conf;
+
+ conf.subscribe(&handler);
+ // Start with one vhost
+ HELPER_EXPECT_SUCCESS(conf.mock_parse(_MIN_OK_CONF "vhost ossrs.net { chunk_size 60000; }"));
+
+ // Add a new vhost - should not crash
+ HELPER_EXPECT_SUCCESS(conf.do_reload(_MIN_OK_CONF "vhost ossrs.net { chunk_size 60000; } vhost new_vhost { chunk_size 65536; }"));
+ // Handler should not be triggered for new vhost
+ EXPECT_TRUE(handler.all_false());
+ handler.reset();
+}
+
+VOID TEST(ConfigReloadTest, ReloadRemoveVhost)
+{
+ srs_error_t err = srs_success;
+
+ MockReloadHandler handler;
+ MockSrsReloadConfig conf;
+
+ conf.subscribe(&handler);
+ // Start with two vhosts
+ HELPER_EXPECT_SUCCESS(conf.mock_parse(_MIN_OK_CONF "vhost ossrs.net { chunk_size 60000; } vhost old_vhost { chunk_size 65536; }"));
+
+ // Remove old_vhost - should not crash
+ HELPER_EXPECT_SUCCESS(conf.do_reload(_MIN_OK_CONF "vhost ossrs.net { chunk_size 60000; }"));
+ // Handler should not be triggered for removed vhost
+ EXPECT_TRUE(handler.all_false());
+ handler.reset();
+}
diff --git a/trunk/src/utest/srs_utest_rtc_playstream.cpp b/trunk/src/utest/srs_utest_rtc_playstream.cpp
new file mode 100644
index 000000000..b4f7f3fb3
--- /dev/null
+++ b/trunk/src/utest/srs_utest_rtc_playstream.cpp
@@ -0,0 +1,360 @@
+//
+// Copyright (c) 2013-2025 The SRS Authors
+//
+// SPDX-License-Identifier: MIT
+//
+
+#include
+
+#include
+#include
+#include
+#include
+
+// Mock ISrsCoroutine implementation
+MockCoroutineForPlayStream::MockCoroutineForPlayStream()
+{
+ start_error_ = srs_success;
+ start_count_ = 0;
+ stop_count_ = 0;
+ interrupt_count_ = 0;
+}
+
+MockCoroutineForPlayStream::~MockCoroutineForPlayStream()
+{
+ srs_freep(start_error_);
+}
+
+srs_error_t MockCoroutineForPlayStream::start()
+{
+ start_count_++;
+ if (start_error_ != srs_success) {
+ return srs_error_copy(start_error_);
+ }
+ return srs_success;
+}
+
+void MockCoroutineForPlayStream::stop()
+{
+ stop_count_++;
+}
+
+void MockCoroutineForPlayStream::interrupt()
+{
+ interrupt_count_++;
+}
+
+srs_error_t MockCoroutineForPlayStream::pull()
+{
+ return srs_success;
+}
+
+const SrsContextId &MockCoroutineForPlayStream::cid()
+{
+ return cid_;
+}
+
+void MockCoroutineForPlayStream::set_cid(const SrsContextId &cid)
+{
+ cid_ = cid;
+}
+
+void MockCoroutineForPlayStream::reset()
+{
+ srs_freep(start_error_);
+ start_count_ = 0;
+ stop_count_ = 0;
+ interrupt_count_ = 0;
+}
+
+void MockCoroutineForPlayStream::set_start_error(srs_error_t err)
+{
+ srs_freep(start_error_);
+ start_error_ = srs_error_copy(err);
+}
+
+// Mock ISrsAppFactory implementation
+MockAppFactoryForPlayStream::MockAppFactoryForPlayStream()
+{
+ mock_coroutine_ = new MockCoroutineForPlayStream();
+ create_coroutine_error_ = srs_success;
+ create_coroutine_count_ = 0;
+ last_coroutine_handler_ = NULL;
+}
+
+MockAppFactoryForPlayStream::~MockAppFactoryForPlayStream()
+{
+ srs_freep(mock_coroutine_);
+ srs_freep(create_coroutine_error_);
+}
+
+ISrsFileWriter *MockAppFactoryForPlayStream::create_file_writer()
+{
+ return NULL;
+}
+
+ISrsFileWriter *MockAppFactoryForPlayStream::create_enc_file_writer()
+{
+ return NULL;
+}
+
+ISrsFileReader *MockAppFactoryForPlayStream::create_file_reader()
+{
+ return NULL;
+}
+
+SrsPath *MockAppFactoryForPlayStream::create_path()
+{
+ return NULL;
+}
+
+SrsLiveSource *MockAppFactoryForPlayStream::create_live_source()
+{
+ return NULL;
+}
+
+ISrsOriginHub *MockAppFactoryForPlayStream::create_origin_hub()
+{
+ return NULL;
+}
+
+ISrsHourGlass *MockAppFactoryForPlayStream::create_hourglass(const std::string &name, ISrsHourGlassHandler *handler, srs_utime_t interval)
+{
+ return NULL;
+}
+
+ISrsBasicRtmpClient *MockAppFactoryForPlayStream::create_rtmp_client(std::string url, srs_utime_t cto, srs_utime_t sto)
+{
+ return NULL;
+}
+
+ISrsHttpClient *MockAppFactoryForPlayStream::create_http_client()
+{
+ return NULL;
+}
+
+ISrsFileReader *MockAppFactoryForPlayStream::create_http_file_reader(ISrsHttpResponseReader *r)
+{
+ return NULL;
+}
+
+ISrsFlvDecoder *MockAppFactoryForPlayStream::create_flv_decoder()
+{
+ return NULL;
+}
+
+#ifdef SRS_RTSP
+ISrsRtspSendTrack *MockAppFactoryForPlayStream::create_rtsp_audio_send_track(ISrsRtspConnection *session, SrsRtcTrackDescription *track_desc)
+{
+ return NULL;
+}
+
+ISrsRtspSendTrack *MockAppFactoryForPlayStream::create_rtsp_video_send_track(ISrsRtspConnection *session, SrsRtcTrackDescription *track_desc)
+{
+ return NULL;
+}
+#endif
+
+ISrsFlvTransmuxer *MockAppFactoryForPlayStream::create_flv_transmuxer()
+{
+ return NULL;
+}
+
+ISrsMp4Encoder *MockAppFactoryForPlayStream::create_mp4_encoder()
+{
+ return NULL;
+}
+
+ISrsDvrSegmenter *MockAppFactoryForPlayStream::create_dvr_flv_segmenter()
+{
+ return NULL;
+}
+
+ISrsDvrSegmenter *MockAppFactoryForPlayStream::create_dvr_mp4_segmenter()
+{
+ return NULL;
+}
+
+#ifdef SRS_GB28181
+ISrsGbMediaTcpConn *MockAppFactoryForPlayStream::create_gb_media_tcp_conn()
+{
+ return NULL;
+}
+
+ISrsGbSession *MockAppFactoryForPlayStream::create_gb_session()
+{
+ return NULL;
+}
+#endif
+
+ISrsInitMp4 *MockAppFactoryForPlayStream::create_init_mp4()
+{
+ return NULL;
+}
+
+ISrsFragmentWindow *MockAppFactoryForPlayStream::create_fragment_window()
+{
+ return NULL;
+}
+
+ISrsFragmentedMp4 *MockAppFactoryForPlayStream::create_fragmented_mp4()
+{
+ return NULL;
+}
+
+ISrsIpListener *MockAppFactoryForPlayStream::create_tcp_listener(ISrsTcpHandler *handler)
+{
+ return NULL;
+}
+
+ISrsRtcConnection *MockAppFactoryForPlayStream::create_rtc_connection(ISrsExecRtcAsyncTask *exec, const SrsContextId &cid)
+{
+ return NULL;
+}
+
+ISrsFFMPEG *MockAppFactoryForPlayStream::create_ffmpeg(std::string ffmpeg_bin)
+{
+ return NULL;
+}
+
+ISrsIngesterFFMPEG *MockAppFactoryForPlayStream::create_ingester_ffmpeg()
+{
+ return NULL;
+}
+
+ISrsCoroutine *MockAppFactoryForPlayStream::create_coroutine(const std::string &name, ISrsCoroutineHandler *handler, SrsContextId cid)
+{
+ create_coroutine_count_++;
+ last_coroutine_name_ = name;
+ last_coroutine_handler_ = handler;
+ last_coroutine_cid_ = cid;
+
+ if (create_coroutine_error_ != srs_success) {
+ return NULL;
+ }
+
+ return mock_coroutine_;
+}
+
+ISrsTime *MockAppFactoryForPlayStream::create_time()
+{
+ return NULL;
+}
+
+ISrsConfig *MockAppFactoryForPlayStream::create_config()
+{
+ return NULL;
+}
+
+ISrsCond *MockAppFactoryForPlayStream::create_cond()
+{
+ return NULL;
+}
+
+void MockAppFactoryForPlayStream::reset()
+{
+ mock_coroutine_->reset();
+ srs_freep(create_coroutine_error_);
+ create_coroutine_count_ = 0;
+ last_coroutine_name_ = "";
+ last_coroutine_handler_ = NULL;
+}
+
+void MockAppFactoryForPlayStream::set_create_coroutine_error(srs_error_t err)
+{
+ srs_freep(create_coroutine_error_);
+ create_coroutine_error_ = srs_error_copy(err);
+}
+
+// Mock ISrsRtcPliWorker implementation
+MockRtcPliWorkerForPlayStream::MockRtcPliWorkerForPlayStream()
+{
+ start_error_ = srs_success;
+ start_count_ = 0;
+ request_keyframe_count_ = 0;
+}
+
+MockRtcPliWorkerForPlayStream::~MockRtcPliWorkerForPlayStream()
+{
+ srs_freep(start_error_);
+}
+
+srs_error_t MockRtcPliWorkerForPlayStream::start()
+{
+ start_count_++;
+ if (start_error_ != srs_success) {
+ return srs_error_copy(start_error_);
+ }
+ return srs_success;
+}
+
+void MockRtcPliWorkerForPlayStream::request_keyframe(uint32_t ssrc, SrsContextId cid)
+{
+ request_keyframe_count_++;
+}
+
+srs_error_t MockRtcPliWorkerForPlayStream::cycle()
+{
+ return srs_success;
+}
+
+void MockRtcPliWorkerForPlayStream::reset()
+{
+ srs_freep(start_error_);
+ start_count_ = 0;
+ request_keyframe_count_ = 0;
+}
+
+void MockRtcPliWorkerForPlayStream::set_start_error(srs_error_t err)
+{
+ srs_freep(start_error_);
+ start_error_ = srs_error_copy(err);
+}
+
+// Test SrsRtcPlayStream::start() - Basic success scenario
+VOID TEST(RtcPlayStreamStartTest, StartSuccess)
+{
+ srs_error_t err;
+
+ // Create mock objects
+ MockRtcAsyncTaskExecutor mock_exec;
+ MockExpire mock_expire;
+ MockRtcPacketSender mock_sender;
+ SrsContextId cid;
+ cid.set_value("test-play-stream-start-cid");
+
+ // Create RTC play stream
+ SrsUniquePtr play_stream(new SrsRtcPlayStream(&mock_exec, &mock_expire, &mock_sender, cid));
+
+ // Create and inject mock app factory
+ MockAppFactoryForPlayStream mock_factory;
+ play_stream->app_factory_ = &mock_factory;
+
+ // Create and inject mock PLI worker
+ MockRtcPliWorkerForPlayStream *mock_pli_worker = new MockRtcPliWorkerForPlayStream();
+ play_stream->pli_worker_ = mock_pli_worker;
+
+ // Test: First call to start() should succeed
+ HELPER_EXPECT_SUCCESS(play_stream->start());
+
+ // Verify coroutine was created with correct parameters
+ EXPECT_EQ(1, mock_factory.create_coroutine_count_);
+ EXPECT_STREQ("rtc_sender", mock_factory.last_coroutine_name_.c_str());
+ EXPECT_TRUE(mock_factory.last_coroutine_handler_ == play_stream.get());
+ EXPECT_EQ(0, cid.compare(mock_factory.last_coroutine_cid_));
+
+ // Verify coroutine start was called
+ EXPECT_EQ(1, mock_factory.mock_coroutine_->start_count_);
+
+ // Verify PLI worker start was called
+ EXPECT_EQ(1, mock_pli_worker->start_count_);
+
+ // Verify is_started_ flag is set
+ EXPECT_TRUE(play_stream->is_started_);
+
+ // Clean up - set to NULL to avoid double-free
+ play_stream->trd_ = NULL; // Set to NULL before mock_factory is destroyed
+ play_stream->app_factory_ = NULL;
+ play_stream->pli_worker_ = NULL;
+ srs_freep(mock_pli_worker);
+}
diff --git a/trunk/src/utest/srs_utest_rtc_playstream.hpp b/trunk/src/utest/srs_utest_rtc_playstream.hpp
new file mode 100644
index 000000000..a4d81a09e
--- /dev/null
+++ b/trunk/src/utest/srs_utest_rtc_playstream.hpp
@@ -0,0 +1,127 @@
+//
+// Copyright (c) 2013-2025 The SRS Authors
+//
+// SPDX-License-Identifier: MIT
+//
+
+#ifndef SRS_UTEST_RTC_PLAYSTREAM_HPP
+#define SRS_UTEST_RTC_PLAYSTREAM_HPP
+
+/*
+#include
+*/
+#include
+
+#include
+#include
+#include
+
+// Mock ISrsCoroutine for testing SrsRtcPlayStream::start()
+class MockCoroutineForPlayStream : public ISrsCoroutine
+{
+public:
+ srs_error_t start_error_;
+ int start_count_;
+ int stop_count_;
+ int interrupt_count_;
+ SrsContextId cid_;
+
+public:
+ MockCoroutineForPlayStream();
+ virtual ~MockCoroutineForPlayStream();
+
+public:
+ virtual srs_error_t start();
+ virtual void stop();
+ virtual void interrupt();
+ virtual srs_error_t pull();
+ virtual const SrsContextId &cid();
+ virtual void set_cid(const SrsContextId &cid);
+
+public:
+ void reset();
+ void set_start_error(srs_error_t err);
+};
+
+// Mock ISrsAppFactory for testing SrsRtcPlayStream::start()
+class MockAppFactoryForPlayStream : public ISrsAppFactory
+{
+public:
+ MockCoroutineForPlayStream *mock_coroutine_;
+ srs_error_t create_coroutine_error_;
+ int create_coroutine_count_;
+ std::string last_coroutine_name_;
+ ISrsCoroutineHandler *last_coroutine_handler_;
+ SrsContextId last_coroutine_cid_;
+
+public:
+ MockAppFactoryForPlayStream();
+ virtual ~MockAppFactoryForPlayStream();
+
+public:
+ // ISrsAppFactory interface methods
+ virtual ISrsFileWriter *create_file_writer();
+ virtual ISrsFileWriter *create_enc_file_writer();
+ virtual ISrsFileReader *create_file_reader();
+ virtual SrsPath *create_path();
+ virtual SrsLiveSource *create_live_source();
+ virtual ISrsOriginHub *create_origin_hub();
+ virtual ISrsHourGlass *create_hourglass(const std::string &name, ISrsHourGlassHandler *handler, srs_utime_t interval);
+ virtual ISrsBasicRtmpClient *create_rtmp_client(std::string url, srs_utime_t cto, srs_utime_t sto);
+ virtual ISrsHttpClient *create_http_client();
+ virtual ISrsFileReader *create_http_file_reader(ISrsHttpResponseReader *r);
+ virtual ISrsFlvDecoder *create_flv_decoder();
+#ifdef SRS_RTSP
+ virtual ISrsRtspSendTrack *create_rtsp_audio_send_track(ISrsRtspConnection *session, SrsRtcTrackDescription *track_desc);
+ virtual ISrsRtspSendTrack *create_rtsp_video_send_track(ISrsRtspConnection *session, SrsRtcTrackDescription *track_desc);
+#endif
+ virtual ISrsFlvTransmuxer *create_flv_transmuxer();
+ virtual ISrsMp4Encoder *create_mp4_encoder();
+ virtual ISrsDvrSegmenter *create_dvr_flv_segmenter();
+ virtual ISrsDvrSegmenter *create_dvr_mp4_segmenter();
+#ifdef SRS_GB28181
+ virtual ISrsGbMediaTcpConn *create_gb_media_tcp_conn();
+ virtual ISrsGbSession *create_gb_session();
+#endif
+ virtual ISrsInitMp4 *create_init_mp4();
+ virtual ISrsFragmentWindow *create_fragment_window();
+ virtual ISrsFragmentedMp4 *create_fragmented_mp4();
+ virtual ISrsIpListener *create_tcp_listener(ISrsTcpHandler *handler);
+ virtual ISrsRtcConnection *create_rtc_connection(ISrsExecRtcAsyncTask *exec, const SrsContextId &cid);
+ virtual ISrsFFMPEG *create_ffmpeg(std::string ffmpeg_bin);
+ virtual ISrsIngesterFFMPEG *create_ingester_ffmpeg();
+
+ // ISrsKernelFactory interface methods
+ virtual ISrsCoroutine *create_coroutine(const std::string &name, ISrsCoroutineHandler *handler, SrsContextId cid);
+ virtual ISrsTime *create_time();
+ virtual ISrsConfig *create_config();
+ virtual ISrsCond *create_cond();
+
+public:
+ void reset();
+ void set_create_coroutine_error(srs_error_t err);
+};
+
+// Mock ISrsRtcPliWorker for testing SrsRtcPlayStream::start()
+class MockRtcPliWorkerForPlayStream : public ISrsRtcPliWorker
+{
+public:
+ srs_error_t start_error_;
+ int start_count_;
+ int request_keyframe_count_;
+
+public:
+ MockRtcPliWorkerForPlayStream();
+ virtual ~MockRtcPliWorkerForPlayStream();
+
+public:
+ virtual srs_error_t start();
+ virtual void request_keyframe(uint32_t ssrc, SrsContextId cid);
+ virtual srs_error_t cycle();
+
+public:
+ void reset();
+ void set_start_error(srs_error_t err);
+};
+
+#endif