This PR adds comprehensive IPv6 support to SRS for all major protocols, enabling dual-stack (IPv4/IPv6) operation across the entire streaming server. Key Features: * RTMP/RTMPS: IPv6 support for streaming ingestion and playback * HTTP/HTTPS: IPv6 support for HTTP-FLV streaming and API endpoints * WebRTC: IPv6 support for UDP/TCP media transport (WHIP/WHEP) * SRT: IPv6 support for low-latency streaming * RTSP: IPv6 support for standards-based streaming For config, see `conf/console.ipv46.conf` for example. Publish RTMP or RTMPS via IPv6: ```bash ffmpeg -re -i ./doc/source.flv -c copy -f flv 'rtmp://[::1]:1935/live/livestream' ffmpeg -re -i ./doc/source.flv -c copy -f flv 'rtmps://[::1]:1443/live/livestream' ``` Play RTMP or RTMPS stream via IPv6 by ffplay: ```bash ffplay 'rtmp://[::1]:1935/live/livestream' ffplay 'rtmps://[::1]:1443/live/livestream' ``` Play by IPv6 via HTTP streaming: * HTTP-FLV: [http://[::1]:8080/live/livestream.flv](http://[::1]:8080/players/srs_player.html) * HTTPS-FLV: [https://[::1]:8088/live/livestream.flv](https://[::1]:8088/players/srs_player.html) To access HTTP API via IPv6: * HTTP API: `curl 'http://[::1]:1985/api/v1/versions'` * HTTPS API: `curl -k 'https://[::1]:1990/api/v1/versions'` ```json { "code": 0, "data": { "major": 7, "minor": 0, "revision": 66, "version": "7.0.66" } } ``` Using HTTP API, publish by IPv6 WHIP via [HTTP](http://[::1]:8080/players/whip.html), and play by [WHEP](http://[::1]:8080/players/whep.html) * WHIP: `http://[::1]:1985/rtc/v1/whip/?app=live&stream=livestream` * WHEP: `http://[::1]:1985/rtc/v1/whep/?app=live&stream=livestream` Using HTTPS API, publish by IPv6 WHIP via [WHIP](https://[::1]:8088/players/whip.html), and play by [WHEP](https://[::1]:8088/players/whep.html) * WHIP: `https://[::1]:1990/rtc/v1/whip/?app=live&stream=livestream` * WHEP: `https://[::1]:1990/rtc/v1/whep/?app=live&stream=livestream` Publish SRT stream by FFmpeg via IPv6: ```bash ffmpeg -re -i ./doc/source.flv -c copy -pes_payload_size 0 -f mpegts \ 'srt://[::1]:10080?streamid=#!::r=live/livestream,m=publish' ``` Play SRT stream by ffplay via IPv6: ```bash ffplay 'srt://[::1]:10080?streamid=#!::r=live/livestream,m=request' ``` Play RTSP stream by ffplay via IPv6: ```bash ffplay -rtsp_transport tcp -i 'rtsp://[::1]:8554/live/livestream' ``` --------- Co-authored-by: OSSRS-AI <winlinam@gmail.com>
152 lines
4.4 KiB
C++
152 lines
4.4 KiB
C++
//
|
|
// Copyright (c) 2013-2025 The SRS Authors
|
|
//
|
|
// SPDX-License-Identifier: MIT
|
|
//
|
|
|
|
#include <srs_app_coworkers.hpp>
|
|
|
|
#include <stdlib.h>
|
|
using namespace std;
|
|
|
|
#include <srs_app_config.hpp>
|
|
#include <srs_kernel_error.hpp>
|
|
#include <srs_kernel_utility.hpp>
|
|
#include <srs_protocol_json.hpp>
|
|
#include <srs_protocol_rtmp_stack.hpp>
|
|
#include <srs_protocol_utility.hpp>
|
|
|
|
SrsCoWorkers *SrsCoWorkers::_instance = NULL;
|
|
|
|
SrsCoWorkers::SrsCoWorkers()
|
|
{
|
|
}
|
|
|
|
SrsCoWorkers::~SrsCoWorkers()
|
|
{
|
|
map<string, ISrsRequest *>::iterator it;
|
|
for (it = streams.begin(); it != streams.end(); ++it) {
|
|
ISrsRequest *r = it->second;
|
|
srs_freep(r);
|
|
}
|
|
streams.clear();
|
|
}
|
|
|
|
SrsCoWorkers *SrsCoWorkers::instance()
|
|
{
|
|
if (!_instance) {
|
|
_instance = new SrsCoWorkers();
|
|
}
|
|
return _instance;
|
|
}
|
|
|
|
SrsJsonAny *SrsCoWorkers::dumps(string vhost, string coworker, string app, string stream)
|
|
{
|
|
ISrsRequest *r = find_stream_info(vhost, app, stream);
|
|
if (!r) {
|
|
// TODO: FIXME: Find stream from our origin util return to the start point.
|
|
return SrsJsonAny::null();
|
|
}
|
|
|
|
// The service port parsing from listen port.
|
|
string listen_host;
|
|
int listen_port = SRS_CONSTS_RTMP_DEFAULT_PORT;
|
|
vector<string> listen_hostports = _srs_config->get_listens();
|
|
if (!listen_hostports.empty()) {
|
|
string list_hostport = listen_hostports.at(0);
|
|
|
|
if (list_hostport.find(":") != string::npos) {
|
|
srs_net_split_hostport(list_hostport, listen_host, listen_port);
|
|
} else {
|
|
listen_port = ::atoi(list_hostport.c_str());
|
|
}
|
|
}
|
|
|
|
// The ip of server, we use the request coworker-host as ip, if listen host is localhost or loopback.
|
|
// For example, the server may behind a NAT(192.x.x.x), while its ip is a docker ip(172.x.x.x),
|
|
// we should use the NAT(192.x.x.x) address as it's the exposed ip.
|
|
// @see https://github.com/ossrs/srs/issues/1501
|
|
string service_ip;
|
|
if (listen_host != SRS_CONSTS_LOCALHOST && listen_host != SRS_CONSTS_LOOPBACK && listen_host != SRS_CONSTS_LOOPBACK6) {
|
|
service_ip = listen_host;
|
|
}
|
|
if (service_ip.empty()) {
|
|
int coworker_port;
|
|
string coworker_host = coworker;
|
|
if (coworker.find(":") != string::npos) {
|
|
srs_net_split_hostport(coworker, coworker_host, coworker_port);
|
|
}
|
|
|
|
service_ip = coworker_host;
|
|
}
|
|
if (service_ip.empty()) {
|
|
service_ip = srs_get_public_internet_address();
|
|
}
|
|
|
|
// The backend API endpoint.
|
|
string backend = _srs_config->get_http_api_listens().at(0);
|
|
if (backend.find(":") == string::npos) {
|
|
backend = service_ip + ":" + backend;
|
|
}
|
|
|
|
// The routers to detect loop and identify path.
|
|
SrsJsonArray *routers = SrsJsonAny::array()->append(SrsJsonAny::str(backend.c_str()));
|
|
|
|
srs_trace("Redirect vhost=%s, path=%s/%s to ip=%s, port=%d, api=%s",
|
|
vhost.c_str(), app.c_str(), stream.c_str(), service_ip.c_str(), listen_port, backend.c_str());
|
|
|
|
return SrsJsonAny::object()
|
|
->set("ip", SrsJsonAny::str(service_ip.c_str()))
|
|
->set("port", SrsJsonAny::integer(listen_port))
|
|
->set("vhost", SrsJsonAny::str(r->vhost.c_str()))
|
|
->set("api", SrsJsonAny::str(backend.c_str()))
|
|
->set("routers", routers);
|
|
}
|
|
|
|
ISrsRequest *SrsCoWorkers::find_stream_info(string vhost, string app, string stream)
|
|
{
|
|
// First, we should parse the vhost, if not exists, try default vhost instead.
|
|
SrsConfDirective *conf = _srs_config->get_vhost(vhost, true);
|
|
if (!conf) {
|
|
return NULL;
|
|
}
|
|
|
|
// Get stream information from local cache.
|
|
string url = srs_net_url_encode_sid(conf->arg0(), app, stream);
|
|
map<string, ISrsRequest *>::iterator it = streams.find(url);
|
|
if (it == streams.end()) {
|
|
return NULL;
|
|
}
|
|
|
|
return it->second;
|
|
}
|
|
|
|
srs_error_t SrsCoWorkers::on_publish(ISrsRequest *r)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
string url = r->get_stream_url();
|
|
|
|
// Delete the previous stream informations.
|
|
map<string, ISrsRequest *>::iterator it = streams.find(url);
|
|
if (it != streams.end()) {
|
|
srs_freep(it->second);
|
|
}
|
|
|
|
// Always use the latest one.
|
|
streams[url] = r->copy();
|
|
|
|
return err;
|
|
}
|
|
|
|
void SrsCoWorkers::on_unpublish(ISrsRequest *r)
|
|
{
|
|
string url = r->get_stream_url();
|
|
|
|
map<string, ISrsRequest *>::iterator it = streams.find(url);
|
|
if (it != streams.end()) {
|
|
srs_freep(it->second);
|
|
streams.erase(it);
|
|
}
|
|
}
|