Skip to content

Commit b703920

Browse files
authored
Merge pull request #21 from TilsonJoji/main
Keep idle publisher sessions alive, and make track preannounce opt-in
2 parents 4e46ccc + 563bbca commit b703920

9 files changed

Lines changed: 101 additions & 2 deletions

File tree

include/openmoq/publisher/cli_options.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ struct CliOptions {
6262
bool stream_per_object = false;
6363
bool paced = false;
6464
bool loop = false;
65+
// See PublisherConfig::preannounce_tracks.
66+
bool preannounce_tracks = false;
6567
bool dump_plan = false;
6668
bool print_msf_urls = false;
6769
std::chrono::seconds subscriber_timeout = std::chrono::seconds(30);

include/openmoq/publisher/publisher_api.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ struct PublisherConfig {
4646
std::string track_namespace = "media";
4747
bool forward = false;
4848
bool publish_catalog = false;
49+
// Send a PUBLISH for each track immediately after PUBLISH_NAMESPACE, before any
50+
// subscriber exists. Relays differ here: some accept tracks that way and never
51+
// forward a SUBSCRIBE upstream, so a publisher waiting for one would stall,
52+
// while others resolve the track namespace only once a subscriber appears and
53+
// are disturbed by an early PUBLISH. Off by default because the path that
54+
// sends it does not process the PUBLISH_OK it would receive, so the request
55+
// would be made and its answer ignored.
56+
bool preannounce_tracks = false;
4957
bool include_sap = false;
5058
bool include_msf_timeline = false;
5159
bool split_cmaf_chunks = true;

include/openmoq/publisher/transport/moqt_session.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@ class MoqtSession {
115115
catalog_republish_interval_ = interval;
116116
}
117117

118+
// See PublisherConfig::preannounce_tracks.
119+
void set_preannounce_tracks(bool enabled) {
120+
preannounce_tracks_ = enabled;
121+
}
122+
118123
private:
119124
void reset_publish_stats();
120125
void record_published_object(const std::string& track_name, std::uint64_t group_id, std::size_t payload_bytes);
@@ -137,6 +142,7 @@ class MoqtSession {
137142
std::string track_namespace_;
138143
bool auto_forward_ = false;
139144
bool publish_catalog_ = false;
145+
bool preannounce_tracks_ = false;
140146
bool paced_ = false;
141147
bool loop_ = false;
142148
std::chrono::seconds subscriber_timeout_ = std::chrono::seconds(30);

src/cli_options.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,8 @@ CliOptions parse_cli_options(int argc, char** argv) {
375375
options.stream_per_object = true;
376376
} else if (argument == "--timeout") {
377377
options.subscriber_timeout = parse_timeout(require_value("--timeout"));
378+
} else if (argument == "--preannounce-tracks") {
379+
options.preannounce_tracks = true;
378380
} else if (argument == "--paced") {
379381
options.paced = true;
380382
} else if (argument == "--loop") {
@@ -495,7 +497,7 @@ std::string build_usage(const char* argv0) {
495497
" --input <mp4|-> [--live-source auto|stdin|srt|dash] [--srt-config <path>]"
496498
" [--dash-listen host:port] [--dash-path <prefix>] [--dash-queue-depth <count>]"
497499
" [--transport raw|webtransport] [--draft 14|16|17|18] [--namespace <value>] [--forward 0|1] [--timeout <seconds>]"
498-
" [--publish-catalog] [--sap] [--msf-timeline] [--coalesce-cmaf-chunks] [--stream-per-object] [--paced] [--loop] [--dump-plan] [--print-msf-urls] [--emit-dir <dir>]"
500+
" [--publish-catalog] [--sap] [--msf-timeline] [--coalesce-cmaf-chunks] [--stream-per-object] [--paced] [--loop] [--preannounce-tracks] [--dump-plan] [--print-msf-urls] [--emit-dir <dir>]"
499501
" [--vod] [--catalog-republish-interval <seconds>] [--drm-config <path>]"
500502
" [--endpoint host:port|moqt://host:port/path|https://host:port/path] [--url moqt://host/path#msf:ns--track] [--alpn value] [--sni value]"
501503
" [--cert file] [--key file] [--ca file] [--insecure]";

src/main.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ int main(int argc, char** argv) {
5151
.track_namespace = options.track_namespace,
5252
.forward = options.forward,
5353
.publish_catalog = options.publish_catalog,
54+
.preannounce_tracks = options.preannounce_tracks,
5455
.include_sap = options.include_sap,
5556
.include_msf_timeline = options.include_msf_timeline,
5657
.split_cmaf_chunks = options.split_cmaf_chunks,

src/publisher_api.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ transport::TransportStatus Publisher::publish(const PreparedPublish& prepared,
191191
config_.subscriber_timeout,
192192
config_.authorization);
193193
active->session->set_catalog_republish_interval(config_.catalog_republish_interval);
194+
active->session->set_preannounce_tracks(config_.preannounce_tracks);
194195

195196
const transport::EndpointConfig resolved_endpoint = resolve_endpoint(endpoint, endpoint_alpn_overridden);
196197
set_active_session(active, resolved_endpoint, false);
@@ -358,6 +359,7 @@ transport::TransportStatus Publisher::publish_live(const LiveIngestConfig& inges
358359
config_.subscriber_timeout,
359360
config_.authorization);
360361
active->session->set_catalog_republish_interval(config_.catalog_republish_interval);
362+
active->session->set_preannounce_tracks(config_.preannounce_tracks);
361363

362364
const transport::EndpointConfig resolved_endpoint = resolve_endpoint(endpoint, endpoint_alpn_overridden);
363365
set_active_session(active, resolved_endpoint, true);
@@ -492,6 +494,7 @@ transport::TransportStatus Publisher::publish_live_objects(const LiveObjectSourc
492494
config_.subscriber_timeout,
493495
config_.authorization);
494496
active->session->set_catalog_republish_interval(config_.catalog_republish_interval);
497+
active->session->set_preannounce_tracks(config_.preannounce_tracks);
495498

496499
const transport::EndpointConfig resolved_endpoint = resolve_endpoint(endpoint, endpoint_alpn_overridden);
497500
set_active_session(active, resolved_endpoint, true);

src/transport/moqt_session.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4792,7 +4792,19 @@ TransportStatus MoqtSession::publish_live_objects(const openmoq::publisher::Live
47924792
++next_alias;
47934793
}
47944794

4795-
if (!uses_request_streams(draft_version)) {
4795+
// Opt-in preannounce: send a PUBLISH per track before any subscriber exists.
4796+
//
4797+
// Relays split into two camps. Some accept tracks this way, answer PUBLISH_OK
4798+
// and never forward a SUBSCRIBE upstream, so a publisher that waits for one
4799+
// would stall without this. Others resolve the track namespace only once a
4800+
// subscriber appears; an early PUBLISH there is at best ignored and at worst
4801+
// disturbs the namespace registration, after which SUBSCRIBEs are rejected.
4802+
//
4803+
// Off by default, because this path never dispatches PUBLISH_OK: the Forward
4804+
// State in the reply cannot be read, so the request would be made and its
4805+
// answer discarded. Callers whose relay needs it set
4806+
// PublisherConfig::preannounce_tracks (CLI: --preannounce-tracks).
4807+
if (!uses_request_streams(draft_version) && preannounce_tracks_) {
47964808
std::uint64_t request_id = 2;
47974809
for (const auto& [track_name, alias] : alias_by_track) {
47984810
TrackMessage track_message{

src/transport/picoquic_client.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,21 @@ TransportStatus PicoquicClient::connect() {
543543

544544
picoquic_set_callback(impl_->cnx, client_callback, impl_.get());
545545

546+
// Keep the connection alive while the application has nothing to send.
547+
//
548+
// A publisher in await-subscribe mode parks in a blocking control-stream read
549+
// until a SUBSCRIBE arrives, transmitting nothing meanwhile. With no keepalive
550+
// the peer's idle timeout expires first and tears the connection down beneath
551+
// that read, which surfaces to the caller as "transport close requested" after
552+
// roughly 30 seconds. That makes a long-running publisher unusable whenever a
553+
// subscriber is absent or between sessions.
554+
//
555+
// The interval must be comfortably below the negotiated idle timeout. Five
556+
// seconds is far enough inside any commonly negotiated value to be safe, and
557+
// the traffic is one PING frame, so the cost is negligible.
558+
constexpr std::uint64_t kKeepAliveIntervalUs = 5'000'000;
559+
picoquic_enable_keep_alive(impl_->cnx, kKeepAliveIntervalUs);
560+
546561
if (picoquic_start_client_cnx(impl_->cnx) != 0) {
547562
picoquic_free(impl_->quic);
548563
impl_->cnx = nullptr;

tests/moqt_session_test.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3119,6 +3119,8 @@ int main() {
31193119

31203120
MoqtSession object_live_session(
31213121
object_live_transport, std::string(kTestTrackNamespace), false, false, false, std::chrono::seconds(1));
3122+
// Preannounce is opt-in; this case exists to exercise it.
3123+
object_live_session.set_preannounce_tracks(true);
31223124
status = object_live_session.connect(endpoint, tls);
31233125
ok &= expect(status.ok, "expected arbitrary live-object session connect to succeed");
31243126
status = object_live_session.publish_live_objects(source, DraftVersion::kDraft14);
@@ -3134,6 +3136,52 @@ int main() {
31343136
"expected arbitrary live-object publish to finish with PUBLISH_NAMESPACE_DONE");
31353137
}
31363138

3139+
{
3140+
// Default (preannounce off): no PUBLISH is emitted before a subscriber
3141+
// exists. The same flow as above with the opt-in left at its default, so a
3142+
// regression in the gate shows up as a count change rather than silently.
3143+
MockTransport default_no_preannounce_transport;
3144+
default_no_preannounce_transport.reads[0].push_back(encode_server_setup_message({
3145+
.draft = DraftVersion::kDraft14,
3146+
.max_request_id = 8,
3147+
}));
3148+
default_no_preannounce_transport.reads[0].push_back(
3149+
encode_publish_namespace_ok_message(DraftVersion::kDraft14, 0));
3150+
default_no_preannounce_transport.reads[0].push_back({});
3151+
3152+
std::vector<LiveObject> objects = {
3153+
LiveObject{
3154+
.track_name = "events",
3155+
.group_id = 7,
3156+
.subgroup_id = 0,
3157+
.object_id = 3,
3158+
.payload = {'O', 'K'},
3159+
},
3160+
};
3161+
std::size_t object_index = 0;
3162+
LiveObjectSource source{
3163+
.tracks = {LiveTrack{.track_name = "events"}},
3164+
.next_object = [&objects, &object_index]() -> std::optional<LiveObject> {
3165+
if (object_index >= objects.size()) {
3166+
return std::nullopt;
3167+
}
3168+
return objects[object_index++];
3169+
},
3170+
};
3171+
3172+
MoqtSession default_no_preannounce_session(default_no_preannounce_transport,
3173+
std::string(kTestTrackNamespace),
3174+
false,
3175+
false,
3176+
false,
3177+
std::chrono::seconds(1));
3178+
status = default_no_preannounce_session.connect(endpoint, tls);
3179+
ok &= expect(status.ok, "expected default live-object session connect to succeed");
3180+
status = default_no_preannounce_session.publish_live_objects(source, DraftVersion::kDraft14);
3181+
ok &= expect(control_message_count(default_no_preannounce_transport, 0x1d) == 0,
3182+
"expected no PUBLISH preannounce when preannounce_tracks is left at its default");
3183+
}
3184+
31373185
{
31383186
// Regression: draft-18 assigns a track alias in PUBLISH, so an
31393187
// auto-forward live-object publisher must establish every track on a
@@ -3840,6 +3888,8 @@ int main() {
38403888
true,
38413889
false,
38423890
std::chrono::seconds(1));
3891+
// Preannounce is opt-in; this case asserts the PUBLISH count it produces.
3892+
dash_live_session.set_preannounce_tracks(true);
38433893
status = dash_live_session.connect(endpoint, tls);
38443894
ok &= expect(status.ok, "expected FFmpeg-style DASH session connect to succeed");
38453895
status = dash_live_session.publish_live_objects(source, DraftVersion::kDraft16);

0 commit comments

Comments
 (0)