55#include " openmoq/publisher/mp4_box.h"
66
77#include < algorithm>
8+ #include < array>
89#include < atomic>
10+ #include < cerrno>
911#include < chrono>
1012#include < cstdint>
1113#include < cstdlib>
2426#include < tuple>
2527#include < vector>
2628
29+ #if !defined(_WIN32)
30+ #include < poll.h>
31+ #include < unistd.h>
32+ #endif
33+
2734namespace openmoq ::publisher::transport {
2835
2936namespace {
@@ -3279,6 +3286,52 @@ TransportStatus MoqtSession::publish_live(const LiveIngestOptions& ingest,
32793286 encode_publish_namespace_done_message (namespace_message), false );
32803287}
32813288
3289+ namespace {
3290+
3291+ // Reads live input for publish_live, appending to the streaming reader and
3292+ // returning the byte count (0 on end of stream or stop request). For real
3293+ // stdin on POSIX, reads the fd directly behind a bounded poll: istream::read
3294+ // would park until a full chunk arrives, which made shutdown joins hang on a
3295+ // feeder that is alive but idle, and mixing istream reads with raw fd reads
3296+ // would lose bytes buffered inside cin/stdio — so every stdin byte in this
3297+ // flow must come through here. A null stop means wait indefinitely for data,
3298+ // matching the blocking semantics of the pre-thread discovery phase.
3299+ std::size_t read_live_input (std::istream& input,
3300+ openmoq::publisher::StreamingMp4Reader& reader,
3301+ const std::atomic<bool >* stop) {
3302+ #if !defined(_WIN32)
3303+ if (&input == &std::cin) {
3304+ constexpr int kStdinPollTimeoutMsec = 100 ;
3305+ std::array<std::uint8_t , 16384 > buffer;
3306+ while (stop == nullptr || !stop->load (std::memory_order_acquire)) {
3307+ pollfd poll_fd{};
3308+ poll_fd.fd = STDIN_FILENO ;
3309+ poll_fd.events = POLLIN ;
3310+ const int ready = ::poll (&poll_fd, 1 , kStdinPollTimeoutMsec );
3311+ if (ready <= 0 ) {
3312+ continue ;
3313+ }
3314+ const ssize_t count = ::read (STDIN_FILENO , buffer.data (), buffer.size ());
3315+ if (count < 0 ) {
3316+ if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK ) {
3317+ continue ;
3318+ }
3319+ return 0 ;
3320+ }
3321+ if (count == 0 ) {
3322+ return 0 ;
3323+ }
3324+ reader.append (buffer.data (), static_cast <std::size_t >(count));
3325+ return static_cast <std::size_t >(count);
3326+ }
3327+ return 0 ;
3328+ }
3329+ #endif
3330+ return reader.read_from (input);
3331+ }
3332+
3333+ } // namespace
3334+
32823335TransportStatus MoqtSession::publish_live (std::istream& input,
32833336 openmoq::publisher::DraftVersion draft_version,
32843337 bool /* split_cmaf_chunks*/ ) {
@@ -3303,7 +3356,7 @@ TransportStatus MoqtSession::publish_live(std::istream& input,
33033356 std::cerr << " [moqt-session] live: waiting for ftyp+moov from stdin...\n " ;
33043357
33053358 while (ftyp_bytes.empty () || moov_bytes.empty ()) {
3306- const std::size_t bytes_read = reader. read_from (input);
3359+ const std::size_t bytes_read = read_live_input (input, reader, nullptr );
33073360 if (bytes_read == 0 && ftyp_bytes.empty ()) {
33083361 return TransportStatus::failure (" stdin EOF before ftyp box" );
33093362 }
@@ -3455,14 +3508,20 @@ TransportStatus MoqtSession::publish_live(std::istream& input,
34553508 };
34563509 auto queue = std::make_shared<LiveMediaQueue>();
34573510
3458- std::thread stdin_thread ([&reader, &input, &tracks, queue]() {
3511+ std::atomic<bool > stdin_stop{false };
3512+ std::thread stdin_thread ([&reader, &input, &tracks, queue, &stdin_stop]() {
34593513 std::vector<std::uint8_t > pending_moof;
34603514 std::size_t shared_group_id = 0 ;
34613515 std::map<std::string, std::size_t > object_id_in_group; // per track, resets on new group
34623516 bool first_keyframe_seen = false ;
34633517
34643518 while (true ) {
3465- const std::size_t bytes_read = reader.read_from (input);
3519+ if (stdin_stop.load (std::memory_order_acquire)) {
3520+ std::lock_guard<std::mutex> lock (queue->mutex );
3521+ queue->eof = true ;
3522+ break ;
3523+ }
3524+ const std::size_t bytes_read = read_live_input (input, reader, &stdin_stop);
34663525
34673526 while (auto box = reader.next_box ()) {
34683527 if (box->type == " moof" ) {
@@ -3530,6 +3589,13 @@ TransportStatus MoqtSession::publish_live(std::istream& input,
35303589 }
35313590 });
35323591
3592+ const auto join_stdin_thread = [&stdin_thread, &stdin_stop]() {
3593+ stdin_stop.store (true , std::memory_order_release);
3594+ if (stdin_thread.joinable ()) {
3595+ stdin_thread.join ();
3596+ }
3597+ };
3598+
35333599 // Main loop: drain queue and publish fragments
35343600 std::map<std::string, SubgroupSenderState> sender_by_track;
35353601 std::map<std::string, std::uint64_t > last_group_id_by_track;
@@ -3950,7 +4016,7 @@ TransportStatus MoqtSession::publish_live(std::istream& input,
39504016 {
39514017 status = drain_queue ();
39524018 if (!status.ok ) {
3953- stdin_thread. join ();
4019+ join_stdin_thread ();
39544020 return status;
39554021 }
39564022 }
@@ -3963,7 +4029,7 @@ TransportStatus MoqtSession::publish_live(std::istream& input,
39634029
39644030 auto [pre_status, pre_subs] = process_control_messages ();
39654031 if (!pre_status.ok ) {
3966- stdin_thread. join ();
4032+ join_stdin_thread ();
39674033 return pre_status;
39684034 }
39694035
@@ -3977,7 +4043,7 @@ TransportStatus MoqtSession::publish_live(std::istream& input,
39774043 }
39784044 auto [ctrl_status, new_subs] = process_control_messages ();
39794045 if (!ctrl_status.ok ) {
3980- stdin_thread. join ();
4046+ join_stdin_thread ();
39814047 return ctrl_status;
39824048 }
39834049
@@ -4011,7 +4077,7 @@ TransportStatus MoqtSession::publish_live(std::istream& input,
40114077 // Drain remaining
40124078 status = drain_queue ();
40134079 if (!status.ok ) {
4014- stdin_thread. join ();
4080+ join_stdin_thread ();
40154081 return status;
40164082 }
40174083 break ;
@@ -4023,14 +4089,14 @@ TransportStatus MoqtSession::publish_live(std::istream& input,
40234089 if (!pending_control_bytes_.empty ()) {
40244090 auto [pre_status, pre_subs] = process_control_messages ();
40254091 if (!pre_status.ok ) {
4026- stdin_thread. join ();
4092+ join_stdin_thread ();
40274093 return pre_status;
40284094 }
40294095 }
40304096
40314097 auto [request_status, request_subs] = process_control_messages ();
40324098 if (!request_status.ok ) {
4033- stdin_thread. join ();
4099+ join_stdin_thread ();
40344100 return request_status;
40354101 }
40364102
@@ -4065,14 +4131,14 @@ TransportStatus MoqtSession::publish_live(std::istream& input,
40654131 break ;
40664132 }
40674133 } else {
4068- stdin_thread. join ();
4134+ join_stdin_thread ();
40694135 return read_status;
40704136 }
40714137
40724138 // Process control messages
40734139 auto [ctrl_status, new_subs] = process_control_messages ();
40744140 if (!ctrl_status.ok ) {
4075- stdin_thread. join ();
4141+ join_stdin_thread ();
40764142 return ctrl_status;
40774143 }
40784144
@@ -4084,7 +4150,7 @@ TransportStatus MoqtSession::publish_live(std::istream& input,
40844150 if (!subscribed_tracks.empty ()) {
40854151 status = drain_queue ();
40864152 if (!status.ok ) {
4087- stdin_thread. join ();
4153+ join_stdin_thread ();
40884154 return status;
40894155 }
40904156 }
@@ -4098,7 +4164,7 @@ TransportStatus MoqtSession::publish_live(std::istream& input,
40984164 }
40994165 }
41004166
4101- stdin_thread. join ();
4167+ join_stdin_thread ();
41024168
41034169 for (auto & [track_name, sender] : sender_by_track) {
41044170 status = sender.finish_group (transport_);
0 commit comments