@@ -35,8 +35,6 @@ with this program. If not, see <https://www.gnu.org/licenses/>
3535#define FILTER_ID " osi_branch_output"
3636#define OUTPUT_MAX_RETRIES 7
3737#define OUTPUT_RETRY_DELAY_SECS 1
38- #define CONNECT_ATTEMPTING_TIMEOUT_NS 15000000000ULL
39- #define DISCONNECT_ATTEMPTING_TIMEOUT_NS 2000000000ULL
4038#define RECONNECT_ATTEMPTING_TIMEOUT_NS 2000000000ULL
4139#define AVAILAVILITY_CHECK_INTERVAL_NS 1000000000ULL
4240#define TASK_INTERVAL_MS 1000
@@ -216,9 +214,19 @@ void BranchOutputFilter::removeCallback()
216214 // Unregsiter hotkeys
217215 if (toggleEnableHotkeyPairId != OBS_INVALID_HOTKEY_PAIR_ID) {
218216 obs_hotkey_pair_unregister (toggleEnableHotkeyPairId);
217+ toggleEnableHotkeyPairId = OBS_INVALID_HOTKEY_PAIR_ID;
219218 }
220219 if (splitRecordingHotkeyId != OBS_INVALID_HOTKEY_ID) {
221220 obs_hotkey_unregister (splitRecordingHotkeyId);
221+ splitRecordingHotkeyId = OBS_INVALID_HOTKEY_ID;
222+ }
223+ if (togglePauseRecordingHotkeyPairId != OBS_INVALID_HOTKEY_PAIR_ID) {
224+ obs_hotkey_pair_unregister (togglePauseRecordingHotkeyPairId);
225+ togglePauseRecordingHotkeyPairId = OBS_INVALID_HOTKEY_PAIR_ID;
226+ }
227+ if (addChapterToRecordingHotkeyId != OBS_INVALID_HOTKEY_ID) {
228+ obs_hotkey_unregister (addChapterToRecordingHotkeyId);
229+ addChapterToRecordingHotkeyId = OBS_INVALID_HOTKEY_ID;
222230 }
223231
224232 obs_log (LOG_INFO, " %s: Filter removed" , qUtf8Printable (name));
@@ -361,12 +369,12 @@ obs_data_t *BranchOutputFilter::createRecordingSettings(obs_data_t *settings, bo
361369 if (!mux.isEmpty ()) {
362370 muxFrag += " " + mux;
363371 }
364- obs_data_set_string (settings , " muxer_settings" , qUtf8Printable (muxFrag));
372+ obs_data_set_string (recordingSettings , " muxer_settings" , qUtf8Printable (muxFrag));
365373 } else {
366374 if (isFragmented) {
367375 obs_log (LOG_WARNING, " User enabled fragmented recording, but custom muxer settings contained movflags." );
368376 } else {
369- obs_data_set_string (settings , " muxer_settings" , qUtf8Printable (mux));
377+ obs_data_set_string (recordingSettings , " muxer_settings" , qUtf8Printable (mux));
370378 }
371379 }
372380
@@ -498,10 +506,11 @@ BranchOutputFilter::createSreamingOutput(obs_data_t *settings, size_t index)
498506 }
499507 }
500508
501- // Create stream output
502- context.output = obs_output_create (type, qUtf8Printable (name), streamingSettings, nullptr );
509+ // Create streaming output
510+ context.output =
511+ obs_output_create (type, qUtf8Printable (QString (" %1 (%2)" ).arg (name).arg (index)), streamingSettings, nullptr );
503512 if (!context.output ) {
504- obs_log (LOG_ERROR, " %s: Streaming %zu output creation failed" , qUtf8Printable (name), index);
513+ obs_log (LOG_ERROR, " %s ( %zu): Streaming output creation failed" , qUtf8Printable (name), index);
505514 return {0 };
506515 }
507516 obs_output_set_reconnect_settings (context.output , OUTPUT_MAX_RETRIES, OUTPUT_RETRY_DELAY_SECS);
@@ -532,29 +541,66 @@ void BranchOutputFilter::startStreamingOutput(size_t index)
532541 auto audioContext = &audios[i];
533542 if (audioContext->encoder ) {
534543 obs_log (
535- LOG_WARNING, " %s: No audio encoder selected for streaming %zu , using track %d" ,
544+ LOG_WARNING, " %s (%zu) : No audio encoder selected for streaming output , using track %d" ,
536545 qUtf8Printable (name), index, i + 1
537546 );
538547 obs_output_set_audio_encoder (streamings[index].output , audioContext->encoder , encIndex++);
539548 break ;
540549 }
541550 }
542551 if (!encIndex) {
543- obs_log (LOG_ERROR, " %s: No audio encoder for streaming %zu " , qUtf8Printable (name), index);
552+ obs_log (LOG_ERROR, " %s (%zu) : No audio encoder for streaming output " , qUtf8Printable (name), index);
544553 return ;
545554 }
546555 }
547556
548557 obs_output_set_video_encoder (streamings[index].output , videoEncoder);
549558
550- streamings[index].connectAttemptingAt = os_gettime_ns ();
559+ streamings[index].outputStarting = true ;
560+
561+ // Track starting signal
562+ streamings[index].outputStartingSignal .Connect (
563+ obs_output_get_signal_handler (streamings[index].output ), " starting" ,
564+ [](void *_data, calldata_t *) {
565+ auto context = static_cast <BranchOutputStreamingContext *>(_data);
566+ context->outputStarting = true ;
567+ obs_log (LOG_DEBUG, " %s: Streaming output is starting" , obs_output_get_name (context->output ));
568+ },
569+ &streamings[index]
570+ );
571+
572+ // Track activate signal
573+ streamings[index].outputActivateSignal .Connect (
574+ obs_output_get_signal_handler (streamings[index].output ), " activate" ,
575+ [](void *_data, calldata_t *) {
576+ auto context = static_cast <BranchOutputStreamingContext *>(_data);
577+ context->outputStarting = false ;
578+ obs_log (LOG_DEBUG, " %s: Streaming output has activated" , obs_output_get_name (context->output ));
579+ },
580+ &streamings[index]
581+ );
551582
552583 // Track reconnect signal
553584 streamings[index].outputReconnectSignal .Connect (
554585 obs_output_get_signal_handler (streamings[index].output ), " reconnect" ,
555586 [](void *_data, calldata_t *) {
556587 auto context = static_cast <BranchOutputStreamingContext *>(_data);
557588 context->reconnectAttemptingAt = os_gettime_ns ();
589+ obs_log (LOG_DEBUG, " %s: Streaming output is reconnecting" , obs_output_get_name (context->output ));
590+ },
591+ &streamings[index]
592+ );
593+
594+ // Track stop signal
595+ streamings[index].outputStopSignal .Connect (
596+ obs_output_get_signal_handler (streamings[index].output ), " stop" ,
597+ [](void *_data, calldata_t *cd) {
598+ auto context = static_cast <BranchOutputStreamingContext *>(_data);
599+ context->outputStarting = false ;
600+ auto code = calldata_int (cd, " code" );
601+ obs_log (
602+ LOG_DEBUG, " %s: Streaming output has stopped with code=%lld" , obs_output_get_name (context->output ), code
603+ );
558604 },
559605 &streamings[index]
560606 );
@@ -563,9 +609,9 @@ void BranchOutputFilter::startStreamingOutput(size_t index)
563609 if (obs_output_start (streamings[index].output )) {
564610 streamings[index].active = true ;
565611 obs_source_inc_showing (obs_filter_get_parent (filterSource));
566- obs_log (LOG_INFO, " %s: Starting streaming %zu output succeeded" , qUtf8Printable (name), index);
612+ obs_log (LOG_INFO, " %s (%zu) : Starting streaming output succeeded" , qUtf8Printable (name), index);
567613 } else {
568- obs_log (LOG_ERROR, " %s: Starting streaming %zu output failed" , qUtf8Printable (name), index);
614+ obs_log (LOG_ERROR, " %s (%zu) : Starting streaming output failed" , qUtf8Printable (name), index);
569615 }
570616}
571617
@@ -574,14 +620,19 @@ void BranchOutputFilter::stopStreamingOutput(size_t index)
574620 if (streamings[index].output && streamings[index].active ) {
575621 obs_source_dec_showing (obs_filter_get_parent (filterSource));
576622 obs_output_stop (streamings[index].output );
577- obs_log (LOG_INFO, " %s: Stopping streaming %zu output succeeded" , qUtf8Printable (name), index);
623+ obs_log (LOG_INFO, " %s (%zu) : Stopping streaming output succeeded" , qUtf8Printable (name), index);
578624 }
579625
626+ // Ensure signals are disconnected to prevent leaks across restarts
627+ streamings[index].outputStartingSignal .Disconnect ();
628+ streamings[index].outputActivateSignal .Disconnect ();
629+ streamings[index].outputReconnectSignal .Disconnect ();
630+ streamings[index].outputStopSignal .Disconnect ();
631+
580632 streamings[index].output = nullptr ;
581633 streamings[index].service = nullptr ;
582- streamings[index].connectAttemptingAt = 0 ;
583- streamings[index].disconnectAttemptingAt = 0 ;
584634 streamings[index].reconnectAttemptingAt = 0 ;
635+ streamings[index].outputStarting = false ;
585636 streamings[index].active = false ;
586637 streamings[index].stopping = false ;
587638}
@@ -714,7 +765,7 @@ void BranchOutputFilter::startOutput(obs_data_t *settings)
714765 // Update active revision with stored settings.
715766 activeSettingsRev = storedSettingsRev;
716767
717- // --- Create service and open stream output ---//
768+ // --- Create service and open streaming output ---//
718769 auto serviceCount = (size_t )obs_data_get_int (settings, " service_count" );
719770 for (size_t i = 0 ; i < MAX_SERVICES && i < serviceCount; i++) {
720771 streamings[i] = createSreamingOutput (settings, i);
@@ -917,12 +968,10 @@ void BranchOutputFilter::reconnectStreamingOutput(size_t index)
917968 OBSMutexAutoUnlock locked (&outputMutex);
918969
919970 if (streamings[index].active ) {
920- obs_output_force_stop (streamings[index].output );
921-
922- streamings[index].connectAttemptingAt = os_gettime_ns ();
971+ obs_output_stop (streamings[index].output );
923972
924973 if (!obs_output_start (streamings[index].output )) {
925- obs_log (LOG_ERROR, " %s: Reconnect streaming %zu output failed" , qUtf8Printable (name), index);
974+ obs_log (LOG_ERROR, " %s (%zu) : Reconnect streaming output failed" , qUtf8Printable (name), index);
926975 }
927976 }
928977 }
@@ -935,7 +984,7 @@ void BranchOutputFilter::restartRecordingOutput()
935984 OBSMutexAutoUnlock locked (&outputMutex);
936985
937986 if (recordingActive) {
938- obs_output_force_stop (recordingOutput);
987+ obs_output_stop (recordingOutput);
939988
940989 if (!obs_output_start (recordingOutput)) {
941990 obs_log (LOG_ERROR, " %s: Restart recording output failed" , qUtf8Printable (name));
@@ -1039,32 +1088,20 @@ void BranchOutputFilter::restartOutput()
10391088 }
10401089}
10411090
1042- bool BranchOutputFilter::connectAttemptingTimedOut (size_t index)
1043- {
1044- return streamings[index].connectAttemptingAt &&
1045- os_gettime_ns () - streamings[index].connectAttemptingAt > CONNECT_ATTEMPTING_TIMEOUT_NS;
1046- }
1047-
1048- bool BranchOutputFilter::disconnectAttemptingTimedOut (size_t index)
1049- {
1050- return streamings[index].disconnectAttemptingAt &&
1051- os_gettime_ns () - streamings[index].disconnectAttemptingAt > DISCONNECT_ATTEMPTING_TIMEOUT_NS;
1052- }
1053-
10541091bool BranchOutputFilter::reconnectAttemptingTimedOut (size_t index)
10551092{
10561093 return streamings[index].reconnectAttemptingAt &&
10571094 os_gettime_ns () - streamings[index].reconnectAttemptingAt > RECONNECT_ATTEMPTING_TIMEOUT_NS;
10581095}
10591096
1060- bool BranchOutputFilter::everyConnectAttemptingsTimedOut ()
1097+ bool BranchOutputFilter::someStreamingsStarting ()
10611098{
10621099 for (size_t i = 0 ; i < MAX_SERVICES; i++) {
1063- if (streamings[i].output && ! connectAttemptingTimedOut (i) ) {
1064- return false ;
1100+ if (streamings[i].outputStarting ) {
1101+ return true ;
10651102 }
10661103 }
1067- return true ;
1104+ return false ;
10681105}
10691106
10701107int BranchOutputFilter::countEnabledStreamings (obs_data_t *settings)
@@ -1156,6 +1193,7 @@ void BranchOutputFilter::onIntervalTimerTimeout()
11561193
11571194 if (streamingStopping) {
11581195 onStopOutputGracefully ();
1196+ return ;
11591197 }
11601198
11611199 auto interlockType = statusDock ? statusDock->getInterlockType () : INTERLOCK_TYPE_ALWAYS_ON;
@@ -1205,7 +1243,7 @@ void BranchOutputFilter::onIntervalTimerTimeout()
12051243 auto recordingAlive = recordingOutput && obs_output_active (recordingOutput);
12061244
12071245 if (sourceEnabled) {
1208- if (countActiveStreamings () > 0 && ! everyConnectAttemptingsTimedOut ()) {
1246+ if (someStreamingsStarting ()) {
12091247 return ;
12101248 }
12111249
@@ -1252,7 +1290,7 @@ void BranchOutputFilter::onIntervalTimerTimeout()
12521290 sourceHeight += (sourceHeight & 1 );
12531291
12541292 if (!sourceInFrontend (parent)) {
1255- // Stop output when source resolution is zero or source had been removed
1293+ // Stop output when source had been removed
12561294 onStopOutputGracefully ();
12571295 return ;
12581296 }
@@ -1263,7 +1301,7 @@ void BranchOutputFilter::onIntervalTimerTimeout()
12631301 if (sourceWidth > 0 && sourceHeight > 0 ) {
12641302 if (!obs_data_get_bool (settings, " keep_output_base_resolution" )) {
12651303 // Restart output when source resolution was changed.
1266- obs_log (LOG_INFO, " %s: Attempting restart the stream output" , qUtf8Printable (name));
1304+ obs_log (LOG_INFO, " %s: Attempting restart the streaming output" , qUtf8Printable (name));
12671305 startOutput (settings);
12681306 return ;
12691307 }
@@ -1328,15 +1366,16 @@ void BranchOutputFilter::onIntervalTimerTimeout()
13281366 }
13291367
13301368 for (size_t i = 0 ; i < MAX_SERVICES; i++) {
1331- if (streamings[i].active && streamings[i].output && !obs_output_active (streamings[i].output )) {
1369+ if (streamings[i].active && streamings[i].output && !obs_output_active (streamings[i].output ) &&
1370+ !obs_output_reconnecting (streamings[i].output )) {
13321371 // Restart streaming
1333- obs_log (LOG_INFO, " %s: Attempting reactivate the stream output %zu " , qUtf8Printable (name), i);
1372+ obs_log (LOG_INFO, " %s (%zu) : Attempting reactivate the streaming output" , qUtf8Printable (name), i);
13341373 reconnectStreamingOutput (i);
13351374 }
13361375 }
13371376
13381377 } else {
1339- if (streamingAlive || recordingAlive || recordingPending) {
1378+ if (streamingActive || recordingActive || recordingPending) {
13401379 // Clicked filter's "Eye" icon (Hide)
13411380 onStopOutputGracefully ();
13421381 return ;
@@ -1364,14 +1403,13 @@ void BranchOutputFilter::onStopOutputGracefully()
13641403 for (size_t i = 0 ; i < MAX_SERVICES; i++) {
13651404 if (streamings[i].output && streamings[i].active ) {
13661405 if (streamings[i].stopping ) {
1367- if (disconnectAttemptingTimedOut (i) && reconnectAttemptingTimedOut (i)) {
1406+ if (reconnectAttemptingTimedOut (i)) {
13681407 // stop streaming safely
13691408 stopStreamingOutput (i);
13701409 }
13711410 } else if (obs_output_reconnecting (streamings[i].output )) {
13721411 // Waiting few seconds to avoid crash due to stoppoing during reconnecting
13731412 streamings[i].stopping = true ;
1374- streamings[i].disconnectAttemptingAt = os_gettime_ns ();
13751413 } else {
13761414 // Stop immediately
13771415 stopStreamingOutput (i);
@@ -1704,6 +1742,11 @@ void obs_module_post_load()
17041742
17051743void obs_module_unload ()
17061744{
1745+ // Remove and destroy status dock to avoid leaks (hotkeys, timers, widgets)
1746+ if (statusDock) {
1747+ obs_frontend_remove_dock (" BranchOutputStatusDock" );
1748+ }
1749+
17071750 pthread_mutex_destroy (&pluginMutex);
17081751
17091752 obs_log (LOG_INFO, " Plugin unloaded" );
0 commit comments