From d9ea25b44154b603f02b02ed3dace11185889929 Mon Sep 17 00:00:00 2001 From: OSSRS-AI Date: Fri, 24 Oct 2025 22:21:34 -0400 Subject: [PATCH 01/11] AI: Update conf description for multiple ep for callback. #4421 --- trunk/conf/full.conf | 4 ++++ trunk/conf/http.hooks.callback.conf | 4 ++++ trunk/src/utest/srs_utest_manual_st.cpp | 12 ++++++------ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/trunk/conf/full.conf b/trunk/conf/full.conf index dd0900188..1b7595730 100644 --- a/trunk/conf/full.conf +++ b/trunk/conf/full.conf @@ -1123,6 +1123,8 @@ vhost hooks.callback.srs.com { # @remark For SRS4, the HTTPS url is supported, for example: # on_publish https://xxx/api0 https://xxx/api1 https://xxx/apiN # Overwrite by env SRS_VHOST_HTTP_HOOKS_ON_PUBLISH for all vhosts. + # @remark When using environment variables, use space-separated URLs with proper quoting: + # SRS_VHOST_HTTP_HOOKS_ON_PUBLISH="https://xxx/api0 https://xxx/api1" on_publish http://127.0.0.1:8085/api/v1/streams http://localhost:8085/api/v1/streams; # when client(encoder) stop publish to vhost/app/stream, call the hook, # the request in the POST data string is a object encode by json: @@ -1160,6 +1162,8 @@ vhost hooks.callback.srs.com { # @remark For SRS4, the HTTPS url is supported, for example: # on_play https://xxx/api0 https://xxx/api1 https://xxx/apiN # Overwrite by env SRS_VHOST_HTTP_HOOKS_ON_PLAY for all vhosts. + # @remark When using environment variables, use space-separated URLs with proper quoting: + # SRS_VHOST_HTTP_HOOKS_ON_PLAY="https://xxx/api0 https://xxx/api1" on_play http://127.0.0.1:8085/api/v1/sessions http://localhost:8085/api/v1/sessions; # when client stop to play vhost/app/stream, call the hook, # the request in the POST data string is a object encode by json: diff --git a/trunk/conf/http.hooks.callback.conf b/trunk/conf/http.hooks.callback.conf index 91fdba401..94b65b642 100644 --- a/trunk/conf/http.hooks.callback.conf +++ b/trunk/conf/http.hooks.callback.conf @@ -1,5 +1,9 @@ # http-hooks or http-callbacks config for srs. # @see full.conf for detail config. +# +# Multiple URLs can be specified for each hook (space-separated). +# When using environment variables, use space-separated URLs with proper quoting: +# SRS_VHOST_HTTP_HOOKS_ON_PLAY="http://url1 http://url2" max_connections 1000; daemon off; diff --git a/trunk/src/utest/srs_utest_manual_st.cpp b/trunk/src/utest/srs_utest_manual_st.cpp index 833218277..ddd19c26b 100644 --- a/trunk/src/utest/srs_utest_manual_st.cpp +++ b/trunk/src/utest/srs_utest_manual_st.cpp @@ -85,8 +85,8 @@ VOID TEST(StTest, StUtimePerformance) // Calculate absolute difference between the two elapsed times int time_diff = gettimeofday_elapsed_time > st_utime_elapsed_time - ? gettimeofday_elapsed_time - st_utime_elapsed_time - : st_utime_elapsed_time - gettimeofday_elapsed_time; + ? gettimeofday_elapsed_time - st_utime_elapsed_time + : st_utime_elapsed_time - gettimeofday_elapsed_time; // The difference should be less than N clock ticks (microseconds) EXPECT_LT(time_diff, 100); @@ -111,8 +111,8 @@ VOID TEST(StTest, StUtimePerformance) // Calculate absolute difference between the two elapsed times int time_diff = gettimeofday_elapsed_time > st_utime_elapsed_time - ? gettimeofday_elapsed_time - st_utime_elapsed_time - : st_utime_elapsed_time - gettimeofday_elapsed_time; + ? gettimeofday_elapsed_time - st_utime_elapsed_time + : st_utime_elapsed_time - gettimeofday_elapsed_time; // The difference should be less than N clock ticks (microseconds) EXPECT_LT(time_diff, 100); @@ -139,8 +139,8 @@ VOID TEST(StTest, StUtimePerformance) // Calculate absolute difference between the two elapsed times int time_diff = gettimeofday_elapsed_time > st_utime_elapsed_time - ? gettimeofday_elapsed_time - st_utime_elapsed_time - : st_utime_elapsed_time - gettimeofday_elapsed_time; + ? gettimeofday_elapsed_time - st_utime_elapsed_time + : st_utime_elapsed_time - gettimeofday_elapsed_time; // The difference should be less than N clock ticks (microseconds) EXPECT_LT(time_diff, 100); From b7828e1fba25cd868eb8616c90fbf9ec4dd9efc4 Mon Sep 17 00:00:00 2001 From: OSSRS-AI Date: Sat, 25 Oct 2025 19:43:38 -0400 Subject: [PATCH 02/11] API: Remove minimum limit of 10 for count parameter in /api/v1/streams and /api/v1/clients. v7.0.103 (#4358) --- trunk/doc/CHANGELOG.md | 1 + trunk/src/app/srs_app_http_api.cpp | 4 ++-- trunk/src/core/srs_core_version7.hpp | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/trunk/doc/CHANGELOG.md b/trunk/doc/CHANGELOG.md index e8b2454d4..2979df8d9 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-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) * v7.0, 2025-10-20, Merge [#4537](https://github.com/ossrs/srs/pull/4537): Forward: Reject RTMPS destinations with clear error message. v7.0.100 (#4537) diff --git a/trunk/src/app/srs_app_http_api.cpp b/trunk/src/app/srs_app_http_api.cpp index 79c35fac1..cf39aaea9 100644 --- a/trunk/src/app/srs_app_http_api.cpp +++ b/trunk/src/app/srs_app_http_api.cpp @@ -787,7 +787,7 @@ srs_error_t SrsGoApiStreams::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessa std::string rstart = r->query_get("start"); std::string rcount = r->query_get("count"); int start = srs_max(0, atoi(rstart.c_str())); - int count = srs_max(10, atoi(rcount.c_str())); + int count = srs_max(1, atoi(rcount.c_str())); if ((err = stat_->dumps_streams(data, start, count)) != srs_success) { int code = srs_error_code(err); srs_freep(err); @@ -849,7 +849,7 @@ srs_error_t SrsGoApiClients::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessa std::string rstart = r->query_get("start"); std::string rcount = r->query_get("count"); int start = srs_max(0, atoi(rstart.c_str())); - int count = srs_max(10, atoi(rcount.c_str())); + int count = srs_max(1, atoi(rcount.c_str())); if ((err = stat_->dumps_clients(data, start, count)) != srs_success) { int code = srs_error_code(err); srs_freep(err); diff --git a/trunk/src/core/srs_core_version7.hpp b/trunk/src/core/srs_core_version7.hpp index 1058c066c..258198d6f 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 102 +#define VERSION_REVISION 103 #endif \ No newline at end of file From 6590871ca86150ac0e21163665b94e152e94be31 Mon Sep 17 00:00:00 2001 From: OSSRS-AI Date: Sat, 25 Oct 2025 21:10:03 -0400 Subject: [PATCH 03/11] 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); From 9eae868e9138b882e2a7e86e6453ffab8c7751cd Mon Sep 17 00:00:00 2001 From: OSSRS-AI Date: Sat, 25 Oct 2025 22:21:08 -0400 Subject: [PATCH 04/11] AI: Build: Improve dependency checking to report all missing dependencies at once. v7.0.105 (#4293) --- trunk/auto/depends.sh | 200 ++++++++++----------------- trunk/doc/CHANGELOG.md | 1 + trunk/src/core/srs_core_version7.hpp | 2 +- 3 files changed, 78 insertions(+), 125 deletions(-) diff --git a/trunk/auto/depends.sh b/trunk/auto/depends.sh index b1fe69d21..bfc9a08b4 100755 --- a/trunk/auto/depends.sh +++ b/trunk/auto/depends.sh @@ -48,140 +48,92 @@ if [[ $SRS_OSX == YES ]]; then echo "Please install brew at https://brew.sh/"; exit $ret; fi fi -# Check perl, which is depended by automake for building libopus etc. -perl --version >/dev/null 2>/dev/null; ret=$?; if [[ 0 -ne $ret ]]; then - if [[ $OS_IS_CENTOS == YES ]]; then - echo "Please install perl by:" - echo " yum install -y perl" - elif [[ $OS_IS_UBUNTU == YES ]]; then - echo "Please install perl by:" - echo " apt install -y perl" - else - echo "Please install perl" + +# Arrays to track missing dependencies +MISSING_DEPS=() +MISSING_DEPS_UBUNTU=() +MISSING_DEPS_CENTOS=() +MISSING_DEPS_OSX=() + +# Helper function to check if a command exists +check_command() { + local cmd=$1 + local cmd_name=$2 + local ubuntu_pkg=$3 + local centos_pkg=$4 + local osx_pkg=$5 + + $cmd >/dev/null 2>/dev/null + if [[ $? -ne 0 ]]; then + MISSING_DEPS+=("$cmd_name") + if [[ ! -z "$ubuntu_pkg" ]]; then + MISSING_DEPS_UBUNTU+=("$ubuntu_pkg") + fi + if [[ ! -z "$centos_pkg" ]]; then + MISSING_DEPS_CENTOS+=("$centos_pkg") + fi + if [[ ! -z "$osx_pkg" ]]; then + MISSING_DEPS_OSX+=("$osx_pkg") + fi + return 1 fi - exit $ret; -fi -gcc --version >/dev/null 2>/dev/null; ret=$?; if [[ 0 -ne $ret ]]; then - if [[ $OS_IS_CENTOS == YES ]]; then - echo "Please install gcc by:" - echo " yum install -y gcc" - elif [[ $OS_IS_UBUNTU == YES ]]; then - echo "Please install gcc by:" - echo " apt install -y gcc" - else - echo "Please install gcc" - fi - exit $ret; -fi -g++ --version >/dev/null 2>/dev/null; ret=$?; if [[ 0 -ne $ret ]]; then - if [[ $OS_IS_CENTOS == YES ]]; then - echo "Please install g++ by:" - echo " yum install -y gcc-c++" - elif [[ $OS_IS_UBUNTU == YES ]]; then - echo "Please install g++ by:" - echo " apt install -y g++" - else - echo "Please install gcc-c++" - fi - exit $ret; -fi -make --version >/dev/null 2>/dev/null; ret=$?; if [[ 0 -ne $ret ]]; then - if [[ $OS_IS_CENTOS == YES ]]; then - echo "Please install make by:" - echo " yum install -y make" - elif [[ $OS_IS_UBUNTU == YES ]]; then - echo "Please install make by:" - echo " apt install -y make" - else - echo "Please install make" - fi - exit $ret; -fi -patch --version >/dev/null 2>/dev/null; ret=$?; if [[ 0 -ne $ret ]]; then - if [[ $OS_IS_CENTOS == YES ]]; then - echo "Please install patch by:" - echo " yum install -y patch" - elif [[ $OS_IS_UBUNTU == YES ]]; then - echo "Please install patch by:" - echo " apt install -y patch" - else - echo "Please install patch" - fi - exit $ret; -fi -unzip -v >/dev/null 2>/dev/null; ret=$?; if [[ 0 -ne $ret ]]; then - if [[ $OS_IS_CENTOS == YES ]]; then - echo "Please install unzip by:" - echo " yum install -y unzip" - elif [[ $OS_IS_UBUNTU == YES ]]; then - echo "Please install unzip by:" - echo " apt install -y unzip" - else - echo "Please install unzip" - fi - exit $ret; -fi -automake --version >/dev/null 2>/dev/null; ret=$?; if [[ 0 -ne $ret ]]; then - if [[ $OS_IS_CENTOS == YES ]]; then - echo "Please install automake by:" - echo " yum install -y automake" - elif [[ $OS_IS_UBUNTU == YES ]]; then - echo "Please install automake by:" - echo " apt install -y automake" - else - echo "Please install automake" - fi - exit $ret; -fi + return 0 +} + +# Check required tools +echo "Checking required tools: perl gcc g++ make patch unzip automake pkg-config which" +check_command "perl --version" "perl" "perl" "perl" "perl" +check_command "gcc --version" "gcc" "gcc" "gcc" "gcc" +check_command "g++ --version" "g++" "g++" "gcc-c++" "gcc" +check_command "make --version" "make" "make" "make" "make" +check_command "patch --version" "patch" "patch" "patch" "gpatch" +check_command "unzip -v" "unzip" "unzip" "unzip" "unzip" +check_command "automake --version" "automake" "automake" "automake" "automake" +check_command "pkg-config --version" "pkg-config" "pkg-config" "pkgconfig" "pkg-config" +check_command "which ls" "which" "which" "which" "which" + +# Check optional tools for valgrind if [[ $SRS_VALGRIND == YES ]]; then - valgrind --version >/dev/null 2>/dev/null; ret=$?; if [[ 0 -ne $ret ]]; then - echo "Please install valgrind"; exit $ret; - fi + check_command "valgrind --version" "valgrind" "valgrind" "valgrind" "valgrind" if [[ ! -f /usr/include/valgrind/valgrind.h ]]; then - echo "Please install valgrind-dev"; exit $ret; + MISSING_DEPS+=("valgrind-dev") + MISSING_DEPS_UBUNTU+=("valgrind") + MISSING_DEPS_CENTOS+=("valgrind-devel") + MISSING_DEPS_OSX+=("valgrind") fi fi -# Check tclsh, which is depended by SRT. + +# Check optional tools for SRT if [[ $SRS_SRT == YES ]]; then - tclsh <<< "exit" >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then - if [[ $OS_IS_CENTOS == YES ]]; then - echo "Please install tclsh by:" - echo " yum install -y tcl" - elif [[ $OS_IS_UBUNTU == YES ]]; then - echo "Please install tclsh by:" - echo " apt install -y tclsh" - else - echo "Please install tclsh" - fi - exit $ret; - fi - cmake --version >/dev/null 2>/dev/null; ret=$?; if [[ 0 -ne $ret ]]; then - if [[ $OS_IS_CENTOS == YES ]]; then - echo "Please install cmake by:" - echo " yum install -y cmake" - elif [[ $OS_IS_UBUNTU == YES ]]; then - echo "Please install cmake by:" - echo " apt install -y cmake" - else - echo "Please install cmake" - fi - exit $ret; + echo "Checking optional tools for SRT: tclsh cmake" + # Special check for tclsh + tclsh <<< "exit" >/dev/null 2>&1 + if [[ $? -ne 0 ]]; then + MISSING_DEPS+=("tclsh") + MISSING_DEPS_UBUNTU+=("tclsh") + MISSING_DEPS_CENTOS+=("tcl") + MISSING_DEPS_OSX+=("tcl-tk") fi + check_command "cmake --version" "cmake" "cmake" "cmake" "cmake" fi -pkg-config --version >/dev/null 2>/dev/null; ret=$?; if [[ 0 -ne $ret ]]; then - echo "Please install pkg-config"; exit $ret; -fi -which ls >/dev/null 2>/dev/null; ret=$?; if [[ 0 -ne $ret ]]; then - if [[ $OS_IS_CENTOS == YES ]]; then - echo "Please install which by:" - echo " yum install -y which" - elif [[ $OS_IS_UBUNTU == YES ]]; then - echo "Please install which by:" - echo " apt install -y which" + +# Report all missing dependencies at once +if [[ ${#MISSING_DEPS[@]} -gt 0 ]]; then + echo "" + echo -e "Missing dependencies (${#MISSING_DEPS[@]}): ${RED}${MISSING_DEPS[@]}${BLACK}" + + if [[ $OS_IS_UBUNTU == YES && ${#MISSING_DEPS_UBUNTU[@]} -gt 0 ]]; then + echo -e "Please install missing dependencies by: ${GREEN}sudo apt install -y ${MISSING_DEPS_UBUNTU[@]}${BLACK}" + elif [[ $OS_IS_CENTOS == YES && ${#MISSING_DEPS_CENTOS[@]} -gt 0 ]]; then + echo -e "Please install missing dependencies by: ${GREEN}sudo yum install -y ${MISSING_DEPS_CENTOS[@]}${BLACK}" + elif [[ $SRS_OSX == YES && ${#MISSING_DEPS_OSX[@]} -gt 0 ]]; then + echo -e "Please install missing dependencies by: ${GREEN}brew install ${MISSING_DEPS_OSX[@]}${BLACK}" else - echo "Please install which" + echo "Please install the missing dependencies above." fi - exit $ret; + + echo "Please install the missing dependencies above and rerun configure." + exit 1 fi ##################################################################################### diff --git a/trunk/doc/CHANGELOG.md b/trunk/doc/CHANGELOG.md index 523d9d910..a82d3f70b 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, Build: Improve dependency checking to report all missing dependencies at once. v7.0.105 (#4293) * 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) diff --git a/trunk/src/core/srs_core_version7.hpp b/trunk/src/core/srs_core_version7.hpp index a28cde114..e74c5d5dd 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 104 +#define VERSION_REVISION 105 #endif \ No newline at end of file From 51ab6403a34968e296a9194e52d47e20b6d313b3 Mon Sep 17 00:00:00 2001 From: OSSRS-AI Date: Sun, 26 Oct 2025 08:43:51 -0400 Subject: [PATCH 05/11] AI: WebRTC: Fix camera/microphone not released after closing publisher. v7.0.106 (#4261) --- trunk/doc/CHANGELOG.md | 7 +++-- trunk/research/players/js/srs.sdk.js | 41 ++++++++++++++++++++++++---- trunk/research/players/whep.html | 21 ++++++++++++++ trunk/research/players/whip.html | 22 +++++++++++++++ trunk/src/core/srs_core_version7.hpp | 2 +- 5 files changed, 83 insertions(+), 10 deletions(-) diff --git a/trunk/doc/CHANGELOG.md b/trunk/doc/CHANGELOG.md index a82d3f70b..9ce73d2d3 100644 --- a/trunk/doc/CHANGELOG.md +++ b/trunk/doc/CHANGELOG.md @@ -7,9 +7,10 @@ The changelog for SRS. ## SRS 7.0 Changelog -* v7.0, 2025-10-26, Build: Improve dependency checking to report all missing dependencies at once. v7.0.105 (#4293) -* 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-26, AI: WebRTC: Fix camera/microphone not released after closing publisher. v7.0.106 (#4261) +* v7.0, 2025-10-26, AI: Build: Improve dependency checking to report all missing dependencies at once. v7.0.105 (#4293) +* v7.0, 2025-10-26, AI: HLS: Support hls_master_m3u8_path_relative for reverse proxy compatibility. v7.0.104 (#4338) +* v7.0, 2025-10-25, AI: 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) * v7.0, 2025-10-20, Merge [#4537](https://github.com/ossrs/srs/pull/4537): Forward: Reject RTMPS destinations with clear error message. v7.0.100 (#4537) diff --git a/trunk/research/players/js/srs.sdk.js b/trunk/research/players/js/srs.sdk.js index 7d49ddc79..4dadb445d 100644 --- a/trunk/research/players/js/srs.sdk.js +++ b/trunk/research/players/js/srs.sdk.js @@ -28,6 +28,9 @@ function SrsRtcPublisherAsync() { } }; + // Store media stream to stop tracks when closing. + self.userStream = null; + // @see https://github.com/rtcdn/rtcdn-draft // @url The WebRTC url to play with, for example: // webrtc://r.ossrs.net/live/livestream @@ -60,10 +63,10 @@ function SrsRtcPublisherAsync() { if (!navigator.mediaDevices && window.location.protocol === 'http:' && window.location.hostname !== 'localhost') { throw new SrsError('HttpsRequiredError', `Please use HTTPS or localhost to publish, read https://github.com/ossrs/srs/issues/2762#issuecomment-983147576`); } - var stream = await navigator.mediaDevices.getUserMedia(self.constraints); + self.userStream = await navigator.mediaDevices.getUserMedia(self.constraints); // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack - stream.getTracks().forEach(function (track) { + self.userStream.getTracks().forEach(function (track) { self.pc.addTrack(track); // Notify about local track when stream is ok. @@ -104,6 +107,14 @@ function SrsRtcPublisherAsync() { self.close = function () { self.pc && self.pc.close(); self.pc = null; + + // Stop all media tracks to release camera/microphone. + if (self.userStream) { + self.userStream.getTracks().forEach(function (track) { + track.stop(); + }); + self.userStream = null; + } }; // The callback when got local stream. @@ -523,6 +534,10 @@ function SrsRtcWhipWhepAsync() { } }; + // Store media streams to stop tracks when closing. + self.displayStream = null; + self.userStream = null; + // See https://datatracker.ietf.org/doc/draft-ietf-wish-whip/ // @url The WebRTC url to publish with, for example: // http://localhost:1985/rtc/v1/whip/?app=live&stream=livestream @@ -557,11 +572,11 @@ function SrsRtcWhipWhepAsync() { } if (useScreen) { - const displayStream = await navigator.mediaDevices.getDisplayMedia({ + self.displayStream = await navigator.mediaDevices.getDisplayMedia({ video: true }); // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack - displayStream.getTracks().forEach(function (track) { + self.displayStream.getTracks().forEach(function (track) { self.pc.addTrack(track); // Notify about local track when stream is ok. self.ontrack && self.ontrack({track: track}); @@ -569,9 +584,9 @@ function SrsRtcWhipWhepAsync() { } if (useCamera || hasAudio) { - const userStream = await navigator.mediaDevices.getUserMedia(self.constraints); + self.userStream = await navigator.mediaDevices.getUserMedia(self.constraints); - userStream.getTracks().forEach(function (track) { + self.userStream.getTracks().forEach(function (track) { self.pc.addTrack(track); // Notify about local track when stream is ok. self.ontrack && self.ontrack({track: track}); @@ -643,6 +658,20 @@ function SrsRtcWhipWhepAsync() { self.close = function () { self.pc && self.pc.close(); self.pc = null; + + // Stop all media tracks to release camera/microphone. + if (self.displayStream) { + self.displayStream.getTracks().forEach(function (track) { + track.stop(); + }); + self.displayStream = null; + } + if (self.userStream) { + self.userStream.getTracks().forEach(function (track) { + track.stop(); + }); + self.userStream = null; + } }; // The callback when got local stream. diff --git a/trunk/research/players/whep.html b/trunk/research/players/whep.html index b4a5b24da..103412e59 100644 --- a/trunk/research/players/whep.html +++ b/trunk/research/players/whep.html @@ -53,6 +53,7 @@ URL: +

@@ -130,6 +131,9 @@ $(function(){ }).then(function(session){ $('#sessionid').html(session.sessionid); $('#simulator-drop').attr('href', session.simulator + '?drop=1&username=' + session.sessionid); + // Enable stop button after successful play + $('#btn_stop').prop('disabled', false); + $('#btn_play').prop('disabled', true); }).catch(function (reason) { sdk.close(); $('#rtc_media_player').hide(); @@ -137,11 +141,28 @@ $(function(){ }); }; + var stopPlay = function() { + if (sdk) { + sdk.close(); + + if (statsTimer) { + clearInterval(statsTimer); + statsTimer = null; + } + + $('#btn_stop').prop('disabled', true); + $('#btn_play').prop('disabled', false); + $('#sessionid').html('(stopped)'); + $('#rtc_media_player').hide(); + } + }; + $('#rtc_media_player').hide(); var query = parse_query_string(); srs_init_whep("#txt_url", query); $("#btn_play").click(startPlay); + $("#btn_stop").click(stopPlay); // Never play util windows loaded @see https://github.com/ossrs/srs/issues/2732 if (query.autostart === 'true') { $('#rtc_media_player').prop('muted', true); diff --git a/trunk/research/players/whip.html b/trunk/research/players/whip.html index 36c27ada3..407d3af84 100644 --- a/trunk/research/players/whip.html +++ b/trunk/research/players/whip.html @@ -53,6 +53,7 @@ URL: +

@@ -138,6 +139,9 @@ $(function(){ }).then(function(session){ $('#sessionid').html(session.sessionid); $('#simulator-drop').attr('href', session.simulator + '?drop=1&username=' + session.sessionid); + // Enable stop button after successful publish + $('#btn_stop').prop('disabled', false); + $('#btn_publish').prop('disabled', true); }).catch(function (reason) { // Throw by sdk. if (reason instanceof SrsError) { @@ -164,11 +168,29 @@ $(function(){ }); }; + var stopPublish = function() { + if (sdk) { + sdk.close(); + + if (statsTimer) { + clearInterval(statsTimer); + statsTimer = null; + } + + $('#btn_stop').prop('disabled', true); + $('#btn_publish').prop('disabled', false); + $('#sessionid').html('(stopped)'); + $('#rtc_media_player').hide(); + console.log('PeerConnection closed. Check if camera indicator is still on!'); + } + }; + $('#rtc_media_player').hide(); var query = parse_query_string(); srs_init_whip("#txt_url", query); $("#btn_publish").click(startPublish); + $("#btn_stop").click(stopPublish); // Never play util windows loaded @see https://github.com/ossrs/srs/issues/2732 if (query.autostart === 'true') { window.addEventListener("load", function(){ startPublish(); }); diff --git a/trunk/src/core/srs_core_version7.hpp b/trunk/src/core/srs_core_version7.hpp index e74c5d5dd..23b1ffe90 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 105 +#define VERSION_REVISION 106 #endif \ No newline at end of file From 4ae9871285910fbf7bf58c8181017034a8fafdc3 Mon Sep 17 00:00:00 2001 From: OSSRS-AI Date: Sun, 26 Oct 2025 09:33:45 -0400 Subject: [PATCH 06/11] AI: Remove deprecated SrsRtcPublisherAsync and SrsRtcPlayerAsync use WHIP/WHEP. --- trunk/3rdparty/signaling/main.go | 2 +- .../ossrs/go-oryx-lib/errors/errors.go | 74 +-- .../ossrs/go-oryx-lib/errors/stack.go | 18 +- .../ossrs/go-oryx-lib/logger/go17.go | 1 + .../ossrs/go-oryx-lib/logger/logger.go | 23 +- .../ossrs/go-oryx-lib/logger/pre_go17.go | 1 + .../golang.org/x/net/websocket/websocket.go | 5 +- .../signaling/www/demos/js/srs.sdk.js | 622 +++++------------- .../3rdparty/signaling/www/demos/one2one.html | 34 +- trunk/3rdparty/signaling/www/demos/room.html | 36 +- .../srs-bench/blackbox/blackbox_test.go | 3 +- trunk/3rdparty/srs-bench/blackbox/dvr_test.go | 5 +- .../3rdparty/srs-bench/blackbox/hevc_test.go | 5 +- trunk/3rdparty/srs-bench/blackbox/hls_test.go | 5 +- .../srs-bench/blackbox/http_api_test.go | 5 +- trunk/3rdparty/srs-bench/blackbox/mp3_test.go | 5 +- .../3rdparty/srs-bench/blackbox/rtmp_test.go | 5 +- trunk/3rdparty/srs-bench/blackbox/srt_test.go | 5 +- trunk/3rdparty/srs-bench/blackbox/util.go | 7 +- trunk/3rdparty/srs-bench/janus/api.go | 5 +- trunk/3rdparty/srs-bench/janus/janus.go | 5 +- trunk/3rdparty/srs-bench/live/live.go | 2 +- trunk/3rdparty/srs-bench/srs/srs.go | 5 +- trunk/3rdparty/srs-bench/srs/srs_test.go | 3 +- trunk/research/players/js/srs.sdk.js | 506 -------------- trunk/research/players/rtc_player.html | 31 +- trunk/research/players/rtc_publisher.html | 58 +- trunk/research/players/srs_gb28181.html | 43 +- 28 files changed, 434 insertions(+), 1085 deletions(-) diff --git a/trunk/3rdparty/signaling/main.go b/trunk/3rdparty/signaling/main.go index a3969200a..8e3b2ea2d 100644 --- a/trunk/3rdparty/signaling/main.go +++ b/trunk/3rdparty/signaling/main.go @@ -1,6 +1,6 @@ // The MIT License (MIT) // -// Copyright (c) 2025 Winlin +// # Copyright (c) 2025 Winlin // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in diff --git a/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/errors/errors.go b/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/errors/errors.go index 257bc3ccd..d64470404 100644 --- a/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/errors/errors.go +++ b/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/errors/errors.go @@ -2,88 +2,88 @@ // // The traditional error handling idiom in Go is roughly akin to // -// if err != nil { -// return err -// } +// if err != nil { +// return err +// } // // which applied recursively up the call stack results in error reports // without context or debugging information. The errors package allows // programmers to add context to the failure path in their code in a way // that does not destroy the original value of the error. // -// Adding context to an error +// # Adding context to an error // // The errors.Wrap function returns a new error that adds context to the // original error by recording a stack trace at the point Wrap is called, // and the supplied message. For example // -// _, err := ioutil.ReadAll(r) -// if err != nil { -// return errors.Wrap(err, "read failed") -// } +// _, err := ioutil.ReadAll(r) +// if err != nil { +// return errors.Wrap(err, "read failed") +// } // // If additional control is required the errors.WithStack and errors.WithMessage // functions destructure errors.Wrap into its component operations of annotating // an error with a stack trace and an a message, respectively. // -// Retrieving the cause of an error +// # Retrieving the cause of an error // // Using errors.Wrap constructs a stack of errors, adding context to the // preceding error. Depending on the nature of the error it may be necessary // to reverse the operation of errors.Wrap to retrieve the original error // for inspection. Any error value which implements this interface // -// type causer interface { -// Cause() error -// } +// type causer interface { +// Cause() error +// } // // can be inspected by errors.Cause. errors.Cause will recursively retrieve // the topmost error which does not implement causer, which is assumed to be // the original cause. For example: // -// switch err := errors.Cause(err).(type) { -// case *MyError: -// // handle specifically -// default: -// // unknown error -// } +// switch err := errors.Cause(err).(type) { +// case *MyError: +// // handle specifically +// default: +// // unknown error +// } // // causer interface is not exported by this package, but is considered a part // of stable public API. // -// Formatted printing of errors +// # Formatted printing of errors // // All error values returned from this package implement fmt.Formatter and can // be formatted by the fmt package. The following verbs are supported // -// %s print the error. If the error has a Cause it will be -// printed recursively -// %v see %s -// %+v extended format. Each Frame of the error's StackTrace will -// be printed in detail. +// %s print the error. If the error has a Cause it will be +// printed recursively +// %v see %s +// %+v extended format. Each Frame of the error's StackTrace will +// be printed in detail. // -// Retrieving the stack trace of an error or wrapper +// # Retrieving the stack trace of an error or wrapper // // New, Errorf, Wrap, and Wrapf record a stack trace at the point they are // invoked. This information can be retrieved with the following interface. // -// type stackTracer interface { -// StackTrace() errors.StackTrace -// } +// type stackTracer interface { +// StackTrace() errors.StackTrace +// } // // Where errors.StackTrace is defined as // -// type StackTrace []Frame +// type StackTrace []Frame // // The Frame type represents a call site in the stack trace. Frame supports // the fmt.Formatter interface that can be used for printing information about // the stack trace of this error. For example: // -// if err, ok := err.(stackTracer); ok { -// for _, f := range err.StackTrace() { -// fmt.Printf("%+s:%d", f) -// } -// } +// if err, ok := err.(stackTracer); ok { +// for _, f := range err.StackTrace() { +// fmt.Printf("%+s:%d", f) +// } +// } // // stackTracer interface is not exported by this package, but is considered a part // of stable public API. @@ -247,9 +247,9 @@ func (w *withMessage) Format(s fmt.State, verb rune) { // An error value has a cause if it implements the following // interface: // -// type causer interface { -// Cause() error -// } +// type causer interface { +// Cause() error +// } // // If the error does not implement Cause, the original error will // be returned. If the error is nil, nil will be returned without further diff --git a/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/errors/stack.go b/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/errors/stack.go index 6c42db5a8..7e5aacc48 100644 --- a/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/errors/stack.go +++ b/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/errors/stack.go @@ -40,15 +40,15 @@ func (f Frame) line() int { // Format formats the frame according to the fmt.Formatter interface. // -// %s source file -// %d source line -// %n function name -// %v equivalent to %s:%d +// %s source file +// %d source line +// %n function name +// %v equivalent to %s:%d // // Format accepts flags that alter the printing of some verbs, as follows: // -// %+s path of source file relative to the compile time GOPATH -// %+v equivalent to %+s:%d +// %+s path of source file relative to the compile time GOPATH +// %+v equivalent to %+s:%d func (f Frame) Format(s fmt.State, verb rune) { switch verb { case 's': @@ -82,12 +82,12 @@ type StackTrace []Frame // Format formats the stack of Frames according to the fmt.Formatter interface. // -// %s lists source files for each Frame in the stack -// %v lists the source file and line number for each Frame in the stack +// %s lists source files for each Frame in the stack +// %v lists the source file and line number for each Frame in the stack // // Format accepts flags that alter the printing of some verbs, as follows: // -// %+v Prints filename, function, and line number for each Frame in the stack. +// %+v Prints filename, function, and line number for each Frame in the stack. func (st StackTrace) Format(s fmt.State, verb rune) { switch verb { case 'v': diff --git a/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/logger/go17.go b/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/logger/go17.go index 65bdeb76f..917f571a0 100644 --- a/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/logger/go17.go +++ b/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/logger/go17.go @@ -19,6 +19,7 @@ // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +//go:build go1.7 // +build go1.7 package logger diff --git a/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/logger/logger.go b/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/logger/logger.go index 61cea362f..90b22fc35 100644 --- a/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/logger/logger.go +++ b/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/logger/logger.go @@ -20,18 +20,23 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // The oryx logger package provides connection-oriented log service. -// logger.I(ctx, ...) -// logger.T(ctx, ...) -// logger.W(ctx, ...) -// logger.E(ctx, ...) +// +// logger.I(ctx, ...) +// logger.T(ctx, ...) +// logger.W(ctx, ...) +// logger.E(ctx, ...) +// // Or use format: -// logger.If(ctx, format, ...) -// logger.Tf(ctx, format, ...) -// logger.Wf(ctx, format, ...) -// logger.Ef(ctx, format, ...) +// +// logger.If(ctx, format, ...) +// logger.Tf(ctx, format, ...) +// logger.Wf(ctx, format, ...) +// logger.Ef(ctx, format, ...) +// // @remark the Context is optional thus can be nil. // @remark From 1.7+, the ctx could be context.Context, wrap by logger.WithContext, -// please read ExampleLogger_ContextGO17(). +// +// please read ExampleLogger_ContextGO17(). package logger import ( diff --git a/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/logger/pre_go17.go b/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/logger/pre_go17.go index 24041dc88..1c46e8dde 100644 --- a/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/logger/pre_go17.go +++ b/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/logger/pre_go17.go @@ -19,6 +19,7 @@ // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +//go:build !go1.7 // +build !go1.7 package logger diff --git a/trunk/3rdparty/signaling/vendor/golang.org/x/net/websocket/websocket.go b/trunk/3rdparty/signaling/vendor/golang.org/x/net/websocket/websocket.go index 6c45c7352..ea422e110 100644 --- a/trunk/3rdparty/signaling/vendor/golang.org/x/net/websocket/websocket.go +++ b/trunk/3rdparty/signaling/vendor/golang.org/x/net/websocket/websocket.go @@ -8,8 +8,8 @@ // This package currently lacks some features found in alternative // and more actively maintained WebSocket packages: // -// https://godoc.org/github.com/gorilla/websocket -// https://godoc.org/nhooyr.io/websocket +// https://godoc.org/github.com/gorilla/websocket +// https://godoc.org/nhooyr.io/websocket package websocket // import "golang.org/x/net/websocket" import ( @@ -416,7 +416,6 @@ Trivial usage: // send binary frame data = []byte{0, 1, 2} websocket.Message.Send(ws, data) - */ var Message = Codec{marshal, unmarshal} diff --git a/trunk/3rdparty/signaling/www/demos/js/srs.sdk.js b/trunk/3rdparty/signaling/www/demos/js/srs.sdk.js index 6895807e4..34673c948 100644 --- a/trunk/3rdparty/signaling/www/demos/js/srs.sdk.js +++ b/trunk/3rdparty/signaling/www/demos/js/srs.sdk.js @@ -1,32 +1,23 @@ -/** - * The MIT License (MIT) - * - * Copyright (c) 2013-2025 Winlin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ +// +// Copyright (c) 2013-2025 Winlin +// +// SPDX-License-Identifier: MIT +// 'use strict'; +function SrsError(name, message) { + this.name = name; + this.message = message; + this.stack = (new Error()).stack; +} +SrsError.prototype = Object.create(Error.prototype); +SrsError.prototype.constructor = SrsError; + // Depends on adapter-7.4.0.min.js from https://github.com/webrtc/adapter -// Async-awat-prmise based SRS RTC Publisher. -function SrsRtcPublisherAsync() { +// Async-awat-prmise based SRS RTC Publisher by WHIP. +function SrsRtcWhipWhepAsync() { var self = {}; // https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia @@ -37,80 +28,144 @@ function SrsRtcPublisherAsync() { } }; - // @see https://github.com/rtcdn/rtcdn-draft - // @url The WebRTC url to play with, for example: - // webrtc://r.ossrs.net/live/livestream - // or specifies the API port: - // webrtc://r.ossrs.net:11985/live/livestream - // or autostart the publish: - // webrtc://r.ossrs.net/live/livestream?autostart=true - // or change the app from live to myapp: - // webrtc://r.ossrs.net:11985/myapp/livestream - // or change the stream from livestream to mystream: - // webrtc://r.ossrs.net:11985/live/mystream - // or set the api server to myapi.domain.com: - // webrtc://myapi.domain.com/live/livestream - // or set the candidate(ip) of answer: - // webrtc://r.ossrs.net/live/livestream?eip=39.107.238.185 - // or force to access https API: - // webrtc://r.ossrs.net/live/livestream?schema=https - // or use plaintext, without SRTP: - // webrtc://r.ossrs.net/live/livestream?encrypt=false - // or any other information, will pass-by in the query: - // webrtc://r.ossrs.net/live/livestream?vhost=xxx - // webrtc://r.ossrs.net/live/livestream?token=xxx - self.publish = async function (url) { - var conf = self.__internal.prepareUrl(url); - self.pc.addTransceiver("audio", {direction: "sendonly"}); - self.pc.addTransceiver("video", {direction: "sendonly"}); + // Store media streams to stop tracks when closing. + self.displayStream = null; + self.userStream = null; - var stream = await navigator.mediaDevices.getUserMedia(self.constraints); + // See https://datatracker.ietf.org/doc/draft-ietf-wish-whip/ + // @url The WebRTC url to publish with, for example: + // http://localhost:1985/rtc/v1/whip/?app=live&stream=livestream + // @options The options to control playing, supports: + // camera: boolean, whether capture video from camera, default to true. + // screen: boolean, whether capture video from screen, default to false. + // audio: boolean, whether play audio, default to true. + self.publish = async function (url, options) { + if (url.indexOf('/whip/') === -1) throw new Error(`invalid WHIP url ${url}`); + const hasAudio = options?.audio ?? true; + const useCamera = options?.camera ?? true; + const useScreen = options?.screen ?? false; - // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack - stream.getTracks().forEach(function (track) { - self.pc.addTrack(track); + if (!hasAudio && !useCamera && !useScreen) throw new Error(`The camera, screen and audio can't be false at the same time`); - // Notify about local track when stream is ok. - self.ontrack && self.ontrack({track: track}); - }); + if (hasAudio) { + self.pc.addTransceiver("audio", {direction: "sendonly"}); + } else { + self.constraints.audio = false; + } + + if (useCamera || useScreen) { + self.pc.addTransceiver("video", {direction: "sendonly"}); + } + + if (!useCamera) { + self.constraints.video = false; + } + + if (!navigator.mediaDevices && window.location.protocol === 'http:' && window.location.hostname !== 'localhost') { + throw new SrsError('HttpsRequiredError', `Please use HTTPS or localhost to publish, read https://github.com/ossrs/srs/issues/2762#issuecomment-983147576`); + } + + if (useScreen) { + self.displayStream = await navigator.mediaDevices.getDisplayMedia({ + video: true + }); + // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack + self.displayStream.getTracks().forEach(function (track) { + self.pc.addTrack(track); + // Notify about local track when stream is ok. + self.ontrack && self.ontrack({track: track}); + }); + } + + if (useCamera || hasAudio) { + self.userStream = await navigator.mediaDevices.getUserMedia(self.constraints); + + self.userStream.getTracks().forEach(function (track) { + self.pc.addTrack(track); + // Notify about local track when stream is ok. + self.ontrack && self.ontrack({track: track}); + }); + } var offer = await self.pc.createOffer(); await self.pc.setLocalDescription(offer); - var session = await new Promise(function (resolve, reject) { - // @see https://github.com/rtcdn/rtcdn-draft - var data = { - api: conf.apiUrl, tid: conf.tid, streamurl: conf.streamUrl, - clientip: null, sdp: offer.sdp - }; - console.log("Generated offer: ", data); + const answer = await new Promise(function (resolve, reject) { + console.log(`Generated offer: ${offer.sdp}`); - $.ajax({ - type: "POST", url: conf.apiUrl, data: JSON.stringify(data), - contentType: 'application/json', dataType: 'json' - }).done(function (data) { + const xhr = new XMLHttpRequest(); + xhr.onload = function() { + if (xhr.readyState !== xhr.DONE) return; + if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr); + const data = xhr.responseText; console.log("Got answer: ", data); - if (data.code) { - reject(data); - return; - } - - resolve(data); - }).fail(function (reason) { - reject(reason); - }); + return data.code ? reject(xhr) : resolve(data); + } + xhr.open('POST', url, true); + xhr.setRequestHeader('Content-type', 'application/sdp'); + xhr.send(offer.sdp); }); await self.pc.setRemoteDescription( - new RTCSessionDescription({type: 'answer', sdp: session.sdp}) + new RTCSessionDescription({type: 'answer', sdp: answer}) ); - session.simulator = conf.schema + '//' + conf.urlObject.server + ':' + conf.port + '/rtc/v1/nack/'; - return session; + return self.__internal.parseId(url, offer.sdp, answer); + }; + + // See https://datatracker.ietf.org/doc/draft-ietf-wish-whip/ + // @url The WebRTC url to play with, for example: + // http://localhost:1985/rtc/v1/whep/?app=live&stream=livestream + // @options The options to control playing, supports: + // videoOnly: boolean, whether only play video, default to false. + // audioOnly: boolean, whether only play audio, default to false. + self.play = async function(url, options) { + if (url.indexOf('/whip-play/') === -1 && url.indexOf('/whep/') === -1) throw new Error(`invalid WHEP url ${url}`); + if (options?.videoOnly && options?.audioOnly) throw new Error(`The videoOnly and audioOnly in options can't be true at the same time`); + + if (!options?.videoOnly) self.pc.addTransceiver("audio", {direction: "recvonly"}); + if (!options?.audioOnly) self.pc.addTransceiver("video", {direction: "recvonly"}); + + var offer = await self.pc.createOffer(); + await self.pc.setLocalDescription(offer); + const answer = await new Promise(function(resolve, reject) { + console.log(`Generated offer: ${offer.sdp}`); + + const xhr = new XMLHttpRequest(); + xhr.onload = function() { + if (xhr.readyState !== xhr.DONE) return; + if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr); + const data = xhr.responseText; + console.log("Got answer: ", data); + return data.code ? reject(xhr) : resolve(data); + } + xhr.open('POST', url, true); + xhr.setRequestHeader('Content-type', 'application/sdp'); + xhr.send(offer.sdp); + }); + await self.pc.setRemoteDescription( + new RTCSessionDescription({type: 'answer', sdp: answer}) + ); + + return self.__internal.parseId(url, offer.sdp, answer); }; // Close the publisher. self.close = function () { self.pc && self.pc.close(); self.pc = null; + + // Stop all media tracks to release camera/microphone. + if (self.displayStream) { + self.displayStream.getTracks().forEach(function (track) { + track.stop(); + }); + self.displayStream = null; + } + if (self.userStream) { + self.userStream.getTracks().forEach(function (track) { + track.stop(); + }); + self.userStream = null; + } }; // The callback when got local stream. @@ -120,147 +175,6 @@ function SrsRtcPublisherAsync() { self.stream.addTrack(event.track); }; - // Internal APIs. - self.__internal = { - defaultPath: '/rtc/v1/publish/', - prepareUrl: function (webrtcUrl) { - var urlObject = self.__internal.parse(webrtcUrl); - - // If user specifies the schema, use it as API schema. - var schema = urlObject.user_query.schema; - schema = schema ? schema + ':' : window.location.protocol; - - var port = urlObject.port || 1985; - if (schema === 'https:') { - port = urlObject.port || 443; - } - - // @see https://github.com/rtcdn/rtcdn-draft - var api = urlObject.user_query.play || self.__internal.defaultPath; - if (api.lastIndexOf('/') !== api.length - 1) { - api += '/'; - } - - apiUrl = schema + '//' + urlObject.server + ':' + port + api; - for (var key in urlObject.user_query) { - if (key !== 'api' && key !== 'play') { - apiUrl += '&' + key + '=' + urlObject.user_query[key]; - } - } - // Replace /rtc/v1/play/&k=v to /rtc/v1/play/?k=v - var apiUrl = apiUrl.replace(api + '&', api + '?'); - - var streamUrl = urlObject.url; - - return { - apiUrl: apiUrl, streamUrl: streamUrl, schema: schema, urlObject: urlObject, port: port, - tid: Number(parseInt(new Date().getTime()*Math.random()*100)).toString(16).slice(0, 7) - }; - }, - parse: function (url) { - // @see: http://stackoverflow.com/questions/10469575/how-to-use-location-object-to-parse-url-without-redirecting-the-page-in-javascri - var a = document.createElement("a"); - a.href = url.replace("rtmp://", "http://") - .replace("webrtc://", "http://") - .replace("rtc://", "http://"); - - var vhost = a.hostname; - var app = a.pathname.substring(1, a.pathname.lastIndexOf("/")); - var stream = a.pathname.slice(a.pathname.lastIndexOf("/") + 1); - - // parse the vhost in the params of app, that srs supports. - app = app.replace("...vhost...", "?vhost="); - if (app.indexOf("?") >= 0) { - var params = app.slice(app.indexOf("?")); - app = app.slice(0, app.indexOf("?")); - - if (params.indexOf("vhost=") > 0) { - vhost = params.slice(params.indexOf("vhost=") + "vhost=".length); - if (vhost.indexOf("&") > 0) { - vhost = vhost.slice(0, vhost.indexOf("&")); - } - } - } - - // when vhost equals to server, and server is ip, - // the vhost is __defaultVhost__ - if (a.hostname === vhost) { - var re = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/; - if (re.test(a.hostname)) { - vhost = "__defaultVhost__"; - } - } - - // parse the schema - var schema = "rtmp"; - if (url.indexOf("://") > 0) { - schema = url.slice(0, url.indexOf("://")); - } - - var port = a.port; - if (!port) { - if (schema === 'http') { - port = 80; - } else if (schema === 'https') { - port = 443; - } else if (schema === 'rtmp') { - port = 1935; - } - } - - var ret = { - url: url, - schema: schema, - server: a.hostname, port: port, - vhost: vhost, app: app, stream: stream - }; - self.__internal.fill_query(a.search, ret); - - // For webrtc API, we use 443 if page is https, or schema specified it. - if (!ret.port) { - if (schema === 'webrtc' || schema === 'rtc') { - if (ret.user_query.schema === 'https') { - ret.port = 443; - } else if (window.location.href.indexOf('https://') === 0) { - ret.port = 443; - } else { - // For WebRTC, SRS use 1985 as default API port. - ret.port = 1985; - } - } - } - - return ret; - }, - fill_query: function (query_string, obj) { - // pure user query object. - obj.user_query = {}; - - if (query_string.length === 0) { - return; - } - - // split again for angularjs. - if (query_string.indexOf("?") >= 0) { - query_string = query_string.split("?")[1]; - } - - var queries = query_string.split("&"); - for (var i = 0; i < queries.length; i++) { - var elem = queries[i]; - - var query = elem.split("="); - obj[query[0]] = query[1]; - obj.user_query[query[0]] = query[1]; - } - - // alias domain for vhost. - if (obj.domain) { - obj.vhost = obj.domain; - } - } - }; - self.pc = new RTCPeerConnection(null); // To keep api consistent between player and publisher. @@ -268,231 +182,23 @@ function SrsRtcPublisherAsync() { // @see https://webrtc.org/getting-started/media-devices self.stream = new MediaStream(); - return self; -} - -// Depends on adapter-7.4.0.min.js from https://github.com/webrtc/adapter -// Async-await-promise based SRS RTC Player. -function SrsRtcPlayerAsync() { - var self = {}; - - // @see https://github.com/rtcdn/rtcdn-draft - // @url The WebRTC url to play with, for example: - // webrtc://r.ossrs.net/live/livestream - // or specifies the API port: - // webrtc://r.ossrs.net:11985/live/livestream - // or autostart the play: - // webrtc://r.ossrs.net/live/livestream?autostart=true - // or change the app from live to myapp: - // webrtc://r.ossrs.net:11985/myapp/livestream - // or change the stream from livestream to mystream: - // webrtc://r.ossrs.net:11985/live/mystream - // or set the api server to myapi.domain.com: - // webrtc://myapi.domain.com/live/livestream - // or set the candidate(ip) of answer: - // webrtc://r.ossrs.net/live/livestream?eip=39.107.238.185 - // or force to access https API: - // webrtc://r.ossrs.net/live/livestream?schema=https - // or use plaintext, without SRTP: - // webrtc://r.ossrs.net/live/livestream?encrypt=false - // or any other information, will pass-by in the query: - // webrtc://r.ossrs.net/live/livestream?vhost=xxx - // webrtc://r.ossrs.net/live/livestream?token=xxx - self.play = async function(url) { - var conf = self.__internal.prepareUrl(url); - self.pc.addTransceiver("audio", {direction: "recvonly"}); - self.pc.addTransceiver("video", {direction: "recvonly"}); - - var offer = await self.pc.createOffer(); - await self.pc.setLocalDescription(offer); - var session = await new Promise(function(resolve, reject) { - // @see https://github.com/rtcdn/rtcdn-draft - var data = { - api: conf.apiUrl, tid: conf.tid, streamurl: conf.streamUrl, - clientip: null, sdp: offer.sdp - }; - console.log("Generated offer: ", data); - - $.ajax({ - type: "POST", url: conf.apiUrl, data: JSON.stringify(data), - contentType:'application/json', dataType: 'json' - }).done(function(data) { - console.log("Got answer: ", data); - if (data.code) { - reject(data); return; - } - - resolve(data); - }).fail(function(reason){ - reject(reason); - }); - }); - await self.pc.setRemoteDescription( - new RTCSessionDescription({type: 'answer', sdp: session.sdp}) - ); - session.simulator = conf.schema + '//' + conf.urlObject.server + ':' + conf.port + '/rtc/v1/nack/'; - return session; - }; - - // Close the player. - self.close = function() { - self.pc && self.pc.close(); - self.pc = null; - }; - - // The callback when got remote track. - // Note that the onaddstream is deprecated, @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/onaddstream - self.ontrack = function (event) { - // https://webrtc.org/getting-started/remote-streams - self.stream.addTrack(event.track); - }; - // Internal APIs. self.__internal = { - defaultPath: '/rtc/v1/play/', - prepareUrl: function (webrtcUrl) { - var urlObject = self.__internal.parse(webrtcUrl); - - // If user specifies the schema, use it as API schema. - var schema = urlObject.user_query.schema; - schema = schema ? schema + ':' : window.location.protocol; - - var port = urlObject.port || 1985; - if (schema === 'https:') { - port = urlObject.port || 443; - } - - // @see https://github.com/rtcdn/rtcdn-draft - var api = urlObject.user_query.play || self.__internal.defaultPath; - if (api.lastIndexOf('/') !== api.length - 1) { - api += '/'; - } - - apiUrl = schema + '//' + urlObject.server + ':' + port + api; - for (var key in urlObject.user_query) { - if (key !== 'api' && key !== 'play') { - apiUrl += '&' + key + '=' + urlObject.user_query[key]; - } - } - // Replace /rtc/v1/play/&k=v to /rtc/v1/play/?k=v - var apiUrl = apiUrl.replace(api + '&', api + '?'); - - var streamUrl = urlObject.url; + parseId: (url, offer, answer) => { + let sessionid = offer.substr(offer.indexOf('a=ice-ufrag:') + 'a=ice-ufrag:'.length); + sessionid = sessionid.substr(0, sessionid.indexOf('\n') - 1) + ':'; + sessionid += answer.substr(answer.indexOf('a=ice-ufrag:') + 'a=ice-ufrag:'.length); + sessionid = sessionid.substr(0, sessionid.indexOf('\n')); + const a = document.createElement("a"); + a.href = url; return { - apiUrl: apiUrl, streamUrl: streamUrl, schema: schema, urlObject: urlObject, port: port, - tid: Number(parseInt(new Date().getTime()*Math.random()*100)).toString(16).slice(0, 7) + sessionid: sessionid, // Should be ice-ufrag of answer:offer. + simulator: a.protocol + '//' + a.host + '/rtc/v1/nack/', }; }, - parse: function (url) { - // @see: http://stackoverflow.com/questions/10469575/how-to-use-location-object-to-parse-url-without-redirecting-the-page-in-javascri - var a = document.createElement("a"); - a.href = url.replace("rtmp://", "http://") - .replace("webrtc://", "http://") - .replace("rtc://", "http://"); - - var vhost = a.hostname; - var app = a.pathname.substring(1, a.pathname.lastIndexOf("/")); - var stream = a.pathname.slice(a.pathname.lastIndexOf("/") + 1); - - // parse the vhost in the params of app, that srs supports. - app = app.replace("...vhost...", "?vhost="); - if (app.indexOf("?") >= 0) { - var params = app.slice(app.indexOf("?")); - app = app.slice(0, app.indexOf("?")); - - if (params.indexOf("vhost=") > 0) { - vhost = params.slice(params.indexOf("vhost=") + "vhost=".length); - if (vhost.indexOf("&") > 0) { - vhost = vhost.slice(0, vhost.indexOf("&")); - } - } - } - - // when vhost equals to server, and server is ip, - // the vhost is __defaultVhost__ - if (a.hostname === vhost) { - var re = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/; - if (re.test(a.hostname)) { - vhost = "__defaultVhost__"; - } - } - - // parse the schema - var schema = "rtmp"; - if (url.indexOf("://") > 0) { - schema = url.slice(0, url.indexOf("://")); - } - - var port = a.port; - if (!port) { - if (schema === 'http') { - port = 80; - } else if (schema === 'https') { - port = 443; - } else if (schema === 'rtmp') { - port = 1935; - } - } - - var ret = { - url: url, - schema: schema, - server: a.hostname, port: port, - vhost: vhost, app: app, stream: stream - }; - self.__internal.fill_query(a.search, ret); - - // For webrtc API, we use 443 if page is https, or schema specified it. - if (!ret.port) { - if (schema === 'webrtc' || schema === 'rtc') { - if (ret.user_query.schema === 'https') { - ret.port = 443; - } else if (window.location.href.indexOf('https://') === 0) { - ret.port = 443; - } else { - // For WebRTC, SRS use 1985 as default API port. - ret.port = 1985; - } - } - } - - return ret; - }, - fill_query: function (query_string, obj) { - // pure user query object. - obj.user_query = {}; - - if (query_string.length === 0) { - return; - } - - // split again for angularjs. - if (query_string.indexOf("?") >= 0) { - query_string = query_string.split("?")[1]; - } - - var queries = query_string.split("&"); - for (var i = 0; i < queries.length; i++) { - var elem = queries[i]; - - var query = elem.split("="); - obj[query[0]] = query[1]; - obj.user_query[query[0]] = query[1]; - } - - // alias domain for vhost. - if (obj.domain) { - obj.vhost = obj.domain; - } - } }; - self.pc = new RTCPeerConnection(null); - - // Create a stream to add track to the stream, @see https://webrtc.org/getting-started/remote-streams - self.stream = new MediaStream(); - // https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/ontrack self.pc.ontrack = function(event) { if (self.ontrack) { @@ -503,33 +209,29 @@ function SrsRtcPlayerAsync() { return self; } -// Format the codec of RTCRtpSender, kind(audio/video) is optional filter. -// https://developer.mozilla.org/en-US/docs/Web/Media/Formats/WebRTC_codecs#getting_the_supported_codecs -function SrsRtcFormatSenders(senders, kind) { +// https://developer.mozilla.org/en-US/docs/Web/API/RTCStatsReport +function SrsRtcFormatStats(stats, kind) { var codecs = []; - senders.forEach(function (sender) { - var params = sender.getParameters(); - params && params.codecs && params.codecs.forEach(function(c) { - if (kind && sender.track.kind !== kind) { - return; - } - - if (c.mimeType.indexOf('/red') > 0 || c.mimeType.indexOf('/rtx') > 0 || c.mimeType.indexOf('/fec') > 0) { - return; - } - + stats.forEach((report) => { + if (report.type === 'codec' && report.mimeType?.toLowerCase().startsWith(kind)) { var s = ''; - s += c.mimeType.replace('audio/', '').replace('video/', ''); - s += ', ' + c.clockRate + 'HZ'; - if (sender.track.kind === "audio") { - s += ', channels: ' + c.channels; + s += report.mimeType.split('/')[1] || report.mimeType; + + if (report.clockRate) { + s += ', ' + report.clockRate + 'HZ'; } - s += ', pt: ' + c.payloadType; + if (kind === 'audio' && report.channels) { + s += ', channels: ' + report.channels; + } + + if (report.payloadType) { + s += ', pt: ' + report.payloadType; + } + codecs.push(s); - }); + } }); return codecs.join(", "); -} - +} \ No newline at end of file diff --git a/trunk/3rdparty/signaling/www/demos/one2one.html b/trunk/3rdparty/signaling/www/demos/one2one.html index 033980103..f7d03fbc1 100644 --- a/trunk/3rdparty/signaling/www/demos/one2one.html +++ b/trunk/3rdparty/signaling/www/demos/one2one.html @@ -213,23 +213,33 @@ } }; + // Convert webrtc:// URL to WHIP URL + var convertToWhipUrl = function(host, room, display) { + var schema = window.location.protocol; + var port = 1985; + if (schema === 'https:') { + port = 443; + } + return schema + '//' + host + ':' + port + '/rtc/v1/whip/?app=' + room + '&stream=' + display + conf.query.replace('?', '&'); + }; + var startPublish = function (host, room, display) { $(".ff_first").each(function(i,e) { $(e).text(display); }); - var url = 'webrtc://' + host + '/' + room + '/' + display + conf.query; + var whipUrl = convertToWhipUrl(host, room, display); $('#rtc_media_publisher').show(); $('#publisher').show(); if (publisher) { publisher.close(); } - publisher = new SrsRtcPublisherAsync(); + publisher = new SrsRtcWhipWhepAsync(); $('#rtc_media_publisher').prop('srcObject', publisher.stream); - return publisher.publish(url).then(function(session){ - $('#self').text('Self: ' + url); + return publisher.publish(whipUrl).then(function(session){ + $('#self').text('Self: ' + display); }).catch(function (reason) { publisher.close(); $('#rtc_media_publisher').hide(); @@ -237,12 +247,22 @@ }); }; + // Convert webrtc:// URL to WHEP URL + var convertToWhepUrl = function(host, room, display) { + var schema = window.location.protocol; + var port = 1985; + if (schema === 'https:') { + port = 443; + } + return schema + '//' + host + ':' + port + '/rtc/v1/whep/?app=' + room + '&stream=' + display + conf.query.replace('?', '&'); + }; + var startPlay = function (host, room, display) { $(".ff_second").each(function(i,e) { $(e).text(display); }); - var url = 'webrtc://' + host + '/' + room + '/' + display + conf.query; + var whepUrl = convertToWhepUrl(host, room, display); $('#rtc_media_player').show(); $('#player').show(); @@ -250,10 +270,10 @@ player.close(); } - player = new SrsRtcPlayerAsync(); + player = new SrsRtcWhipWhepAsync(); $('#rtc_media_player').prop('srcObject', player.stream); - player.play(url).then(function(session){ + player.play(whepUrl).then(function(session){ $('#peer').text('Peer: ' + display); $('#rtc_media_player').prop('muted', false); }).catch(function (reason) { diff --git a/trunk/3rdparty/signaling/www/demos/room.html b/trunk/3rdparty/signaling/www/demos/room.html index 5c67bf271..956876de0 100644 --- a/trunk/3rdparty/signaling/www/demos/room.html +++ b/trunk/3rdparty/signaling/www/demos/room.html @@ -126,23 +126,33 @@ }); }; + // Convert webrtc:// URL to WHIP URL + var convertToWhipUrl = function(host, room, display) { + var schema = window.location.protocol; + var port = 1985; + if (schema === 'https:') { + port = 443; + } + return schema + '//' + host + ':' + port + '/rtc/v1/whip/?app=' + room + '&stream=' + display + conf.query.replace('?', '&'); + }; + var startPublish = function (host, room, display) { $(".ff_first").each(function(i,e) { $(e).text(display); }); - var url = 'webrtc://' + host + '/' + room + '/' + display + conf.query; + var whipUrl = convertToWhipUrl(host, room, display); $('#rtc_media_publisher').show(); $('#publisher').show(); if (publisher) { publisher.close(); } - publisher = new SrsRtcPublisherAsync(); + publisher = new SrsRtcWhipWhepAsync(); $('#rtc_media_publisher').prop('srcObject', publisher.stream); - return publisher.publish(url).then(function(session){ - $('#self').text('Self: ' + url); + return publisher.publish(whipUrl).then(function(session){ + $('#self').text('Self: ' + display); }).catch(function (reason) { publisher.close(); $('#rtc_media_publisher').hide(); @@ -150,6 +160,16 @@ }); }; + // Convert webrtc:// URL to WHEP URL + var convertToWhepUrl = function(host, room, display) { + var schema = window.location.protocol; + var port = 1985; + if (schema === 'https:') { + port = 443; + } + return schema + '//' + host + ':' + port + '/rtc/v1/whep/?app=' + room + '&stream=' + display + conf.query.replace('?', '&'); + }; + var startPlay = function (host, room, display) { $(".ff_second").each(function(i,e) { $(e).text(display); @@ -165,20 +185,20 @@ let ui = $('#player').clone().attr('id', 'player-' + display); let video = ui.children('#rtc_media_player'); console.log(video.length); - let player = new SrsRtcPlayerAsync(); + let player = new SrsRtcWhipWhepAsync(); players[display] = {ui:ui, video:video, player:player}; $('.srs_players').append(ui); // Start play for this user. - var url = 'webrtc://' + host + '/' + room + '/' + display + conf.query; + var whepUrl = convertToWhepUrl(host, room, display); video.show(); ui.show(); video.prop('srcObject', player.stream); - player.play(url).then(function(session){ - ui.children('#peer').text('Peer: ' + url); + player.play(whepUrl).then(function(session){ + ui.children('#peer').text('Peer: ' + display); video.prop('muted', false); }).catch(function (reason) { player.close(); diff --git a/trunk/3rdparty/srs-bench/blackbox/blackbox_test.go b/trunk/3rdparty/srs-bench/blackbox/blackbox_test.go index 5c950d070..43a559085 100644 --- a/trunk/3rdparty/srs-bench/blackbox/blackbox_test.go +++ b/trunk/3rdparty/srs-bench/blackbox/blackbox_test.go @@ -21,12 +21,13 @@ package blackbox import ( - "github.com/ossrs/go-oryx-lib/logger" "io/ioutil" "math/rand" "os" "testing" "time" + + "github.com/ossrs/go-oryx-lib/logger" ) func TestMain(m *testing.M) { diff --git a/trunk/3rdparty/srs-bench/blackbox/dvr_test.go b/trunk/3rdparty/srs-bench/blackbox/dvr_test.go index 5cf199660..6cef7ee8f 100644 --- a/trunk/3rdparty/srs-bench/blackbox/dvr_test.go +++ b/trunk/3rdparty/srs-bench/blackbox/dvr_test.go @@ -23,14 +23,15 @@ package blackbox import ( "context" "fmt" - "github.com/ossrs/go-oryx-lib/errors" - "github.com/ossrs/go-oryx-lib/logger" "math/rand" "os" "path" "sync" "testing" "time" + + "github.com/ossrs/go-oryx-lib/errors" + "github.com/ossrs/go-oryx-lib/logger" ) func TestFast_RtmpPublish_DvrFlv_Basic(t *testing.T) { diff --git a/trunk/3rdparty/srs-bench/blackbox/hevc_test.go b/trunk/3rdparty/srs-bench/blackbox/hevc_test.go index 892c6643c..12de11573 100644 --- a/trunk/3rdparty/srs-bench/blackbox/hevc_test.go +++ b/trunk/3rdparty/srs-bench/blackbox/hevc_test.go @@ -23,8 +23,6 @@ package blackbox import ( "context" "fmt" - "github.com/ossrs/go-oryx-lib/errors" - "github.com/ossrs/go-oryx-lib/logger" "math/rand" "os" "path" @@ -32,6 +30,9 @@ import ( "sync" "testing" "time" + + "github.com/ossrs/go-oryx-lib/errors" + "github.com/ossrs/go-oryx-lib/logger" ) func TestSlow_RtmpPublish_RtmpPlay_HEVC_Basic(t *testing.T) { diff --git a/trunk/3rdparty/srs-bench/blackbox/hls_test.go b/trunk/3rdparty/srs-bench/blackbox/hls_test.go index 395bf6da3..b2aa45768 100644 --- a/trunk/3rdparty/srs-bench/blackbox/hls_test.go +++ b/trunk/3rdparty/srs-bench/blackbox/hls_test.go @@ -23,14 +23,15 @@ package blackbox import ( "context" "fmt" - "github.com/ossrs/go-oryx-lib/errors" - "github.com/ossrs/go-oryx-lib/logger" "math/rand" "os" "path" "sync" "testing" "time" + + "github.com/ossrs/go-oryx-lib/errors" + "github.com/ossrs/go-oryx-lib/logger" ) func TestFast_RtmpPublish_HlsPlay_Basic(t *testing.T) { diff --git a/trunk/3rdparty/srs-bench/blackbox/http_api_test.go b/trunk/3rdparty/srs-bench/blackbox/http_api_test.go index d81034a3c..f6d704c6c 100644 --- a/trunk/3rdparty/srs-bench/blackbox/http_api_test.go +++ b/trunk/3rdparty/srs-bench/blackbox/http_api_test.go @@ -23,12 +23,13 @@ package blackbox import ( "context" "fmt" - "github.com/ossrs/go-oryx-lib/errors" - "github.com/ossrs/go-oryx-lib/logger" "net/http" "sync" "testing" "time" + + "github.com/ossrs/go-oryx-lib/errors" + "github.com/ossrs/go-oryx-lib/logger" ) func TestFast_Http_Api_Basic_Auth(t *testing.T) { diff --git a/trunk/3rdparty/srs-bench/blackbox/mp3_test.go b/trunk/3rdparty/srs-bench/blackbox/mp3_test.go index 5fd9d2fb6..9507d9c82 100644 --- a/trunk/3rdparty/srs-bench/blackbox/mp3_test.go +++ b/trunk/3rdparty/srs-bench/blackbox/mp3_test.go @@ -23,14 +23,15 @@ package blackbox import ( "context" "fmt" - "github.com/ossrs/go-oryx-lib/errors" - "github.com/ossrs/go-oryx-lib/logger" "math/rand" "os" "path" "sync" "testing" "time" + + "github.com/ossrs/go-oryx-lib/errors" + "github.com/ossrs/go-oryx-lib/logger" ) func TestFast_RtmpPublish_RtmpPlay_CodecMP3_Basic(t *testing.T) { diff --git a/trunk/3rdparty/srs-bench/blackbox/rtmp_test.go b/trunk/3rdparty/srs-bench/blackbox/rtmp_test.go index c7c00ec1d..058d0c52f 100644 --- a/trunk/3rdparty/srs-bench/blackbox/rtmp_test.go +++ b/trunk/3rdparty/srs-bench/blackbox/rtmp_test.go @@ -23,14 +23,15 @@ package blackbox import ( "context" "fmt" - "github.com/ossrs/go-oryx-lib/errors" - "github.com/ossrs/go-oryx-lib/logger" "math/rand" "os" "path" "sync" "testing" "time" + + "github.com/ossrs/go-oryx-lib/errors" + "github.com/ossrs/go-oryx-lib/logger" ) func TestFast_RtmpPublish_RtmpPlay_Basic(t *testing.T) { diff --git a/trunk/3rdparty/srs-bench/blackbox/srt_test.go b/trunk/3rdparty/srs-bench/blackbox/srt_test.go index 41f52c282..c9d2e9e2a 100644 --- a/trunk/3rdparty/srs-bench/blackbox/srt_test.go +++ b/trunk/3rdparty/srs-bench/blackbox/srt_test.go @@ -23,14 +23,15 @@ package blackbox import ( "context" "fmt" - "github.com/ossrs/go-oryx-lib/errors" - "github.com/ossrs/go-oryx-lib/logger" "math/rand" "os" "path" "sync" "testing" "time" + + "github.com/ossrs/go-oryx-lib/errors" + "github.com/ossrs/go-oryx-lib/logger" ) func TestFast_SrtPublish_SrtPlay_Basic(t *testing.T) { diff --git a/trunk/3rdparty/srs-bench/blackbox/util.go b/trunk/3rdparty/srs-bench/blackbox/util.go index d1c691e97..85da836c6 100644 --- a/trunk/3rdparty/srs-bench/blackbox/util.go +++ b/trunk/3rdparty/srs-bench/blackbox/util.go @@ -26,9 +26,6 @@ import ( "encoding/json" "flag" "fmt" - "github.com/ossrs/go-oryx-lib/errors" - ohttp "github.com/ossrs/go-oryx-lib/http" - "github.com/ossrs/go-oryx-lib/logger" "io/ioutil" "math/rand" "net/http" @@ -41,6 +38,10 @@ import ( "sync" "syscall" "time" + + "github.com/ossrs/go-oryx-lib/errors" + ohttp "github.com/ossrs/go-oryx-lib/http" + "github.com/ossrs/go-oryx-lib/logger" ) var srsLog *bool diff --git a/trunk/3rdparty/srs-bench/janus/api.go b/trunk/3rdparty/srs-bench/janus/api.go index 3c3317caf..3a534a53e 100644 --- a/trunk/3rdparty/srs-bench/janus/api.go +++ b/trunk/3rdparty/srs-bench/janus/api.go @@ -24,13 +24,14 @@ import ( "context" "encoding/json" "fmt" - "github.com/ossrs/go-oryx-lib/errors" - "github.com/ossrs/go-oryx-lib/logger" "io/ioutil" "net/http" "strings" "sync" "time" + + "github.com/ossrs/go-oryx-lib/errors" + "github.com/ossrs/go-oryx-lib/logger" ) type publisherInfo struct { diff --git a/trunk/3rdparty/srs-bench/janus/janus.go b/trunk/3rdparty/srs-bench/janus/janus.go index ee9b3d44a..62c5bf8c6 100644 --- a/trunk/3rdparty/srs-bench/janus/janus.go +++ b/trunk/3rdparty/srs-bench/janus/janus.go @@ -24,12 +24,13 @@ import ( "context" "flag" "fmt" - "github.com/ossrs/go-oryx-lib/errors" - "github.com/ossrs/go-oryx-lib/logger" "os" "strings" "sync" "time" + + "github.com/ossrs/go-oryx-lib/errors" + "github.com/ossrs/go-oryx-lib/logger" ) var sr string diff --git a/trunk/3rdparty/srs-bench/live/live.go b/trunk/3rdparty/srs-bench/live/live.go index 6dc3a8efe..af02afa3c 100644 --- a/trunk/3rdparty/srs-bench/live/live.go +++ b/trunk/3rdparty/srs-bench/live/live.go @@ -174,7 +174,7 @@ func Run(ctx context.Context) error { gStatLive.Publishers.Alive-- logger.Tf(ctx, "Publisher %v done, alive=%v", pr, gStatLive.Publishers.Alive) - <- publisherStartedCtx.Done() + <-publisherStartedCtx.Done() if gStatLive.Publishers.Alive == 0 { cancel() } diff --git a/trunk/3rdparty/srs-bench/srs/srs.go b/trunk/3rdparty/srs-bench/srs/srs.go index 931b459a6..eff8f004f 100644 --- a/trunk/3rdparty/srs-bench/srs/srs.go +++ b/trunk/3rdparty/srs-bench/srs/srs.go @@ -24,14 +24,15 @@ import ( "context" "flag" "fmt" - "github.com/ossrs/go-oryx-lib/errors" - "github.com/ossrs/go-oryx-lib/logger" "net" "net/http" "os" "strings" "sync" "time" + + "github.com/ossrs/go-oryx-lib/errors" + "github.com/ossrs/go-oryx-lib/logger" ) var sr, dumpAudio, dumpVideo string diff --git a/trunk/3rdparty/srs-bench/srs/srs_test.go b/trunk/3rdparty/srs-bench/srs/srs_test.go index 92edeac2e..1aca9502c 100644 --- a/trunk/3rdparty/srs-bench/srs/srs_test.go +++ b/trunk/3rdparty/srs-bench/srs/srs_test.go @@ -21,12 +21,13 @@ package srs import ( - "github.com/ossrs/go-oryx-lib/logger" "io/ioutil" "math/rand" "os" "testing" "time" + + "github.com/ossrs/go-oryx-lib/logger" ) func TestMain(m *testing.M) { diff --git a/trunk/research/players/js/srs.sdk.js b/trunk/research/players/js/srs.sdk.js index 4dadb445d..34673c948 100644 --- a/trunk/research/players/js/srs.sdk.js +++ b/trunk/research/players/js/srs.sdk.js @@ -15,512 +15,6 @@ function SrsError(name, message) { SrsError.prototype = Object.create(Error.prototype); SrsError.prototype.constructor = SrsError; -// Depends on adapter-7.4.0.min.js from https://github.com/webrtc/adapter -// Async-awat-prmise based SRS RTC Publisher. -function SrsRtcPublisherAsync() { - var self = {}; - - // https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia - self.constraints = { - audio: true, - video: { - width: {ideal: 320, max: 576} - } - }; - - // Store media stream to stop tracks when closing. - self.userStream = null; - - // @see https://github.com/rtcdn/rtcdn-draft - // @url The WebRTC url to play with, for example: - // webrtc://r.ossrs.net/live/livestream - // or specifies the API port: - // webrtc://r.ossrs.net:11985/live/livestream - // or autostart the publish: - // webrtc://r.ossrs.net/live/livestream?autostart=true - // or change the app from live to myapp: - // webrtc://r.ossrs.net:11985/myapp/livestream - // or change the stream from livestream to mystream: - // webrtc://r.ossrs.net:11985/live/mystream - // or set the api server to myapi.domain.com: - // webrtc://myapi.domain.com/live/livestream - // or set the candidate(eip) of answer: - // webrtc://r.ossrs.net/live/livestream?candidate=39.107.238.185 - // or force to access https API: - // webrtc://r.ossrs.net/live/livestream?schema=https - // or use plaintext, without SRTP: - // webrtc://r.ossrs.net/live/livestream?encrypt=false - // or any other information, will pass-by in the query: - // webrtc://r.ossrs.net/live/livestream?vhost=xxx - // webrtc://r.ossrs.net/live/livestream?token=xxx - self.publish = async function (url) { - var conf = self.__internal.prepareUrl(url); - self.pc.addTransceiver("audio", {direction: "sendonly"}); - self.pc.addTransceiver("video", {direction: "sendonly"}); - //self.pc.addTransceiver("video", {direction: "sendonly"}); - //self.pc.addTransceiver("audio", {direction: "sendonly"}); - - if (!navigator.mediaDevices && window.location.protocol === 'http:' && window.location.hostname !== 'localhost') { - throw new SrsError('HttpsRequiredError', `Please use HTTPS or localhost to publish, read https://github.com/ossrs/srs/issues/2762#issuecomment-983147576`); - } - self.userStream = await navigator.mediaDevices.getUserMedia(self.constraints); - - // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack - self.userStream.getTracks().forEach(function (track) { - self.pc.addTrack(track); - - // Notify about local track when stream is ok. - self.ontrack && self.ontrack({track: track}); - }); - - var offer = await self.pc.createOffer(); - await self.pc.setLocalDescription(offer); - var session = await new Promise(function (resolve, reject) { - // @see https://github.com/rtcdn/rtcdn-draft - var data = { - api: conf.apiUrl, tid: conf.tid, streamurl: conf.streamUrl, - clientip: null, sdp: offer.sdp - }; - console.log("Generated offer: ", data); - - const xhr = new XMLHttpRequest(); - xhr.onload = function() { - if (xhr.readyState !== xhr.DONE) return; - if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr); - const data = JSON.parse(xhr.responseText); - console.log("Got answer: ", data); - return data.code ? reject(xhr) : resolve(data); - } - xhr.open('POST', conf.apiUrl, true); - xhr.setRequestHeader('Content-type', 'application/json'); - xhr.send(JSON.stringify(data)); - }); - await self.pc.setRemoteDescription( - new RTCSessionDescription({type: 'answer', sdp: session.sdp}) - ); - session.simulator = conf.schema + '//' + conf.urlObject.server + ':' + conf.port + '/rtc/v1/nack/'; - - return session; - }; - - // Close the publisher. - self.close = function () { - self.pc && self.pc.close(); - self.pc = null; - - // Stop all media tracks to release camera/microphone. - if (self.userStream) { - self.userStream.getTracks().forEach(function (track) { - track.stop(); - }); - self.userStream = null; - } - }; - - // The callback when got local stream. - // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack - self.ontrack = function (event) { - // Add track to stream of SDK. - self.stream.addTrack(event.track); - }; - - // Internal APIs. - self.__internal = { - defaultPath: '/rtc/v1/publish/', - prepareUrl: function (webrtcUrl) { - var urlObject = self.__internal.parse(webrtcUrl); - - // If user specifies the schema, use it as API schema. - var schema = urlObject.user_query.schema; - schema = schema ? schema + ':' : window.location.protocol; - - var port = urlObject.port || 1985; - if (schema === 'https:') { - port = urlObject.port || 443; - } - - // @see https://github.com/rtcdn/rtcdn-draft - var api = urlObject.user_query.play || self.__internal.defaultPath; - if (api.lastIndexOf('/') !== api.length - 1) { - api += '/'; - } - - var apiUrl = schema + '//' + urlObject.server + ':' + port + api; - for (var key in urlObject.user_query) { - if (key !== 'api' && key !== 'play') { - apiUrl += '&' + key + '=' + urlObject.user_query[key]; - } - } - // Replace /rtc/v1/play/&k=v to /rtc/v1/play/?k=v - apiUrl = apiUrl.replace(api + '&', api + '?'); - - var streamUrl = urlObject.url; - - return { - apiUrl: apiUrl, streamUrl: streamUrl, schema: schema, urlObject: urlObject, port: port, - tid: Number(parseInt(new Date().getTime()*Math.random()*100)).toString(16).slice(0, 7) - }; - }, - parse: function (url) { - // @see: http://stackoverflow.com/questions/10469575/how-to-use-location-object-to-parse-url-without-redirecting-the-page-in-javascri - var a = document.createElement("a"); - a.href = url.replace("rtmp://", "http://") - .replace("webrtc://", "http://") - .replace("rtc://", "http://"); - - var vhost = a.hostname; - var app = a.pathname.substring(1, a.pathname.lastIndexOf("/")); - var stream = a.pathname.slice(a.pathname.lastIndexOf("/") + 1); - - // parse the vhost in the params of app, that srs supports. - app = app.replace("...vhost...", "?vhost="); - if (app.indexOf("?") >= 0) { - var params = app.slice(app.indexOf("?")); - app = app.slice(0, app.indexOf("?")); - - if (params.indexOf("vhost=") > 0) { - vhost = params.slice(params.indexOf("vhost=") + "vhost=".length); - if (vhost.indexOf("&") > 0) { - vhost = vhost.slice(0, vhost.indexOf("&")); - } - } - } - - // when vhost equals to server, and server is ip, - // the vhost is __defaultVhost__ - if (a.hostname === vhost) { - var re = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/; - if (re.test(a.hostname)) { - vhost = "__defaultVhost__"; - } - } - - // parse the schema - var schema = "rtmp"; - if (url.indexOf("://") > 0) { - schema = url.slice(0, url.indexOf("://")); - } - - var port = a.port; - if (!port) { - // Finger out by webrtc url, if contains http or https port, to overwrite default 1985. - if (schema === 'webrtc' && url.indexOf(`webrtc://${a.host}:`) === 0) { - port = (url.indexOf(`webrtc://${a.host}:80`) === 0) ? 80 : 443; - } - - // Guess by schema. - if (schema === 'http') { - port = 80; - } else if (schema === 'https') { - port = 443; - } else if (schema === 'rtmp') { - port = 1935; - } - } - - var ret = { - url: url, - schema: schema, - server: a.hostname, port: port, - vhost: vhost, app: app, stream: stream - }; - self.__internal.fill_query(a.search, ret); - - // For webrtc API, we use 443 if page is https, or schema specified it. - if (!ret.port) { - if (schema === 'webrtc' || schema === 'rtc') { - if (ret.user_query.schema === 'https') { - ret.port = 443; - } else if (window.location.href.indexOf('https://') === 0) { - ret.port = 443; - } else { - // For WebRTC, SRS use 1985 as default API port. - ret.port = 1985; - } - } - } - - return ret; - }, - fill_query: function (query_string, obj) { - // pure user query object. - obj.user_query = {}; - - if (query_string.length === 0) { - return; - } - - // split again for angularjs. - if (query_string.indexOf("?") >= 0) { - query_string = query_string.split("?")[1]; - } - - var queries = query_string.split("&"); - for (var i = 0; i < queries.length; i++) { - var elem = queries[i]; - - var query = elem.split("="); - obj[query[0]] = query[1]; - obj.user_query[query[0]] = query[1]; - } - - // alias domain for vhost. - if (obj.domain) { - obj.vhost = obj.domain; - } - } - }; - - self.pc = new RTCPeerConnection(null); - - // To keep api consistent between player and publisher. - // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack - // @see https://webrtc.org/getting-started/media-devices - self.stream = new MediaStream(); - - return self; -} - -// Depends on adapter-7.4.0.min.js from https://github.com/webrtc/adapter -// Async-await-promise based SRS RTC Player. -function SrsRtcPlayerAsync() { - var self = {}; - - // @see https://github.com/rtcdn/rtcdn-draft - // @url The WebRTC url to play with, for example: - // webrtc://r.ossrs.net/live/livestream - // or specifies the API port: - // webrtc://r.ossrs.net:11985/live/livestream - // webrtc://r.ossrs.net:80/live/livestream - // or autostart the play: - // webrtc://r.ossrs.net/live/livestream?autostart=true - // or change the app from live to myapp: - // webrtc://r.ossrs.net:11985/myapp/livestream - // or change the stream from livestream to mystream: - // webrtc://r.ossrs.net:11985/live/mystream - // or set the api server to myapi.domain.com: - // webrtc://myapi.domain.com/live/livestream - // or set the candidate(eip) of answer: - // webrtc://r.ossrs.net/live/livestream?candidate=39.107.238.185 - // or force to access https API: - // webrtc://r.ossrs.net/live/livestream?schema=https - // or use plaintext, without SRTP: - // webrtc://r.ossrs.net/live/livestream?encrypt=false - // or any other information, will pass-by in the query: - // webrtc://r.ossrs.net/live/livestream?vhost=xxx - // webrtc://r.ossrs.net/live/livestream?token=xxx - self.play = async function(url) { - var conf = self.__internal.prepareUrl(url); - self.pc.addTransceiver("audio", {direction: "recvonly"}); - self.pc.addTransceiver("video", {direction: "recvonly"}); - //self.pc.addTransceiver("video", {direction: "recvonly"}); - //self.pc.addTransceiver("audio", {direction: "recvonly"}); - - var offer = await self.pc.createOffer(); - await self.pc.setLocalDescription(offer); - var session = await new Promise(function(resolve, reject) { - // @see https://github.com/rtcdn/rtcdn-draft - var data = { - api: conf.apiUrl, tid: conf.tid, streamurl: conf.streamUrl, - clientip: null, sdp: offer.sdp - }; - console.log("Generated offer: ", data); - - const xhr = new XMLHttpRequest(); - xhr.onload = function() { - if (xhr.readyState !== xhr.DONE) return; - if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr); - const data = JSON.parse(xhr.responseText); - console.log("Got answer: ", data); - return data.code ? reject(xhr) : resolve(data); - } - xhr.open('POST', conf.apiUrl, true); - xhr.setRequestHeader('Content-type', 'application/json'); - xhr.send(JSON.stringify(data)); - }); - await self.pc.setRemoteDescription( - new RTCSessionDescription({type: 'answer', sdp: session.sdp}) - ); - session.simulator = conf.schema + '//' + conf.urlObject.server + ':' + conf.port + '/rtc/v1/nack/'; - - return session; - }; - - // Close the player. - self.close = function() { - self.pc && self.pc.close(); - self.pc = null; - }; - - // The callback when got remote track. - // Note that the onaddstream is deprecated, @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/onaddstream - self.ontrack = function (event) { - // https://webrtc.org/getting-started/remote-streams - self.stream.addTrack(event.track); - }; - - // Internal APIs. - self.__internal = { - defaultPath: '/rtc/v1/play/', - prepareUrl: function (webrtcUrl) { - var urlObject = self.__internal.parse(webrtcUrl); - - // If user specifies the schema, use it as API schema. - var schema = urlObject.user_query.schema; - schema = schema ? schema + ':' : window.location.protocol; - - var port = urlObject.port || 1985; - if (schema === 'https:') { - port = urlObject.port || 443; - } - - // @see https://github.com/rtcdn/rtcdn-draft - var api = urlObject.user_query.play || self.__internal.defaultPath; - if (api.lastIndexOf('/') !== api.length - 1) { - api += '/'; - } - - var apiUrl = schema + '//' + urlObject.server + ':' + port + api; - for (var key in urlObject.user_query) { - if (key !== 'api' && key !== 'play') { - apiUrl += '&' + key + '=' + urlObject.user_query[key]; - } - } - // Replace /rtc/v1/play/&k=v to /rtc/v1/play/?k=v - apiUrl = apiUrl.replace(api + '&', api + '?'); - - var streamUrl = urlObject.url; - - return { - apiUrl: apiUrl, streamUrl: streamUrl, schema: schema, urlObject: urlObject, port: port, - tid: Number(parseInt(new Date().getTime()*Math.random()*100)).toString(16).slice(0, 7) - }; - }, - parse: function (url) { - // @see: http://stackoverflow.com/questions/10469575/how-to-use-location-object-to-parse-url-without-redirecting-the-page-in-javascri - var a = document.createElement("a"); - a.href = url.replace("rtmp://", "http://") - .replace("webrtc://", "http://") - .replace("rtc://", "http://"); - - var vhost = a.hostname; - var app = a.pathname.substring(1, a.pathname.lastIndexOf("/")); - var stream = a.pathname.slice(a.pathname.lastIndexOf("/") + 1); - - // parse the vhost in the params of app, that srs supports. - app = app.replace("...vhost...", "?vhost="); - if (app.indexOf("?") >= 0) { - var params = app.slice(app.indexOf("?")); - app = app.slice(0, app.indexOf("?")); - - if (params.indexOf("vhost=") > 0) { - vhost = params.slice(params.indexOf("vhost=") + "vhost=".length); - if (vhost.indexOf("&") > 0) { - vhost = vhost.slice(0, vhost.indexOf("&")); - } - } - } - - // when vhost equals to server, and server is ip, - // the vhost is __defaultVhost__ - if (a.hostname === vhost) { - var re = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/; - if (re.test(a.hostname)) { - vhost = "__defaultVhost__"; - } - } - - // parse the schema - var schema = "rtmp"; - if (url.indexOf("://") > 0) { - schema = url.slice(0, url.indexOf("://")); - } - - var port = a.port; - if (!port) { - // Finger out by webrtc url, if contains http or https port, to overwrite default 1985. - if (schema === 'webrtc' && url.indexOf(`webrtc://${a.host}:`) === 0) { - port = (url.indexOf(`webrtc://${a.host}:80`) === 0) ? 80 : 443; - } - - // Guess by schema. - if (schema === 'http') { - port = 80; - } else if (schema === 'https') { - port = 443; - } else if (schema === 'rtmp') { - port = 1935; - } - } - - var ret = { - url: url, - schema: schema, - server: a.hostname, port: port, - vhost: vhost, app: app, stream: stream - }; - self.__internal.fill_query(a.search, ret); - - // For webrtc API, we use 443 if page is https, or schema specified it. - if (!ret.port) { - if (schema === 'webrtc' || schema === 'rtc') { - if (ret.user_query.schema === 'https') { - ret.port = 443; - } else if (window.location.href.indexOf('https://') === 0) { - ret.port = 443; - } else { - // For WebRTC, SRS use 1985 as default API port. - ret.port = 1985; - } - } - } - - return ret; - }, - fill_query: function (query_string, obj) { - // pure user query object. - obj.user_query = {}; - - if (query_string.length === 0) { - return; - } - - // split again for angularjs. - if (query_string.indexOf("?") >= 0) { - query_string = query_string.split("?")[1]; - } - - var queries = query_string.split("&"); - for (var i = 0; i < queries.length; i++) { - var elem = queries[i]; - - var query = elem.split("="); - obj[query[0]] = query[1]; - obj.user_query[query[0]] = query[1]; - } - - // alias domain for vhost. - if (obj.domain) { - obj.vhost = obj.domain; - } - } - }; - - self.pc = new RTCPeerConnection(null); - - // Create a stream to add track to the stream, @see https://webrtc.org/getting-started/remote-streams - self.stream = new MediaStream(); - - // https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/ontrack - self.pc.ontrack = function(event) { - if (self.ontrack) { - self.ontrack(event); - } - }; - - return self; -} - // Depends on adapter-7.4.0.min.js from https://github.com/webrtc/adapter // Async-awat-prmise based SRS RTC Publisher by WHIP. function SrsRtcWhipWhepAsync() { diff --git a/trunk/research/players/rtc_player.html b/trunk/research/players/rtc_player.html index e5a00308a..971f9fa77 100644 --- a/trunk/research/players/rtc_player.html +++ b/trunk/research/players/rtc_player.html @@ -67,6 +67,31 @@