From 910004366d43c5e4f40480af50b173702e063fd7 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 9 Jun 2026 15:08:47 -0700 Subject: [PATCH 1/7] Update libobs to v31.1.2sl19b2 (#1728) --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 16be4349e..cdaa0f65c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,7 +25,7 @@ env: SLGenerator: Visual Studio 17 2022 SLDistributeDirectory: distribute SLFullDistributePath: "streamlabs-build.app/distribute" # The .app extension is required to run macOS tests correctly. - LibOBSVersion: 31.1.2sl19 + LibOBSVersion: 31.1.2sl19b2 PACKAGE_NAME: osn jobs: From a7001dd005c5a2e08bf98c17233e05dba3ba7ae1 Mon Sep 17 00:00:00 2001 From: Richard Date: Wed, 10 Jun 2026 12:17:00 -0500 Subject: [PATCH 2/7] fix Rescale output is not working for advanced recordings (#1729) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes advanced recording rescale output not being applied by configuring the recording video encoder’s scaled size when rescaling is enabled. (cherry picked from commit 9eb083dde7b603db9a52df0365beeecfd434d17d) --- obs-studio-server/source/osn-advanced-recording.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/obs-studio-server/source/osn-advanced-recording.cpp b/obs-studio-server/source/osn-advanced-recording.cpp index 69c75b905..216b8d485 100644 --- a/obs-studio-server/source/osn-advanced-recording.cpp +++ b/obs-studio-server/source/osn-advanced-recording.cpp @@ -269,6 +269,17 @@ void osn::IAdvancedRecording::Start(void *data, const int64_t id, const std::vec PRETTY_ERROR_RETURN(ErrorCode::CriticalError, "The specified video encoder is not valid for recording."); } + const char *encoderId = obs_encoder_get_id(recording->videoEncoder); + const bool encoderIsOwned = !recording->useStreamEncoders || obs_get_multiple_rendering(); + const bool encoderSupportsScaling = encoderId && strcmp(encoderId, ENCODER_NVENC_H264_TEX) != 0; + if (encoderIsOwned && encoderSupportsScaling) { + const bool applyRescale = recording->rescaling && recording->outputWidth > 0 && recording->outputHeight > 0; + const uint32_t cx = applyRescale ? recording->outputWidth : 0; + const uint32_t cy = applyRescale ? recording->outputHeight : 0; + obs_encoder_set_scaled_size(recording->videoEncoder, cx, cy); + obs_encoder_set_gpu_scale_type(recording->videoEncoder, applyRescale ? OBS_SCALE_BILINEAR : OBS_SCALE_DISABLE); + } + obs_output_set_video_encoder(recording->GetOutput(), recording->videoEncoder); std::string path = recording->path; From 9264898a7ca0483a8a0f0d84e735b4929b125820 Mon Sep 17 00:00:00 2001 From: Richard Date: Thu, 11 Jun 2026 13:32:32 -0500 Subject: [PATCH 3/7] fix Apple encoder issues (#1731) * enable check_availability_streaming for apple-hevc * fix VT H264 cannot record to FLV * fix obs exception "Invalid data found when processing inputCouldn't initialize muxer" by returning false since this Apple VT H.264 Hardware Encoder does not record to this format * fix https://app.asana.com/1/1083097041131/project/1207748235152481/task/1215301313110016?focus=true --- obs-studio-server/source/osn-encoders.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/obs-studio-server/source/osn-encoders.cpp b/obs-studio-server/source/osn-encoders.cpp index 250b5d0be..b91f8b0c7 100644 --- a/obs-studio-server/source/osn-encoders.cpp +++ b/obs-studio-server/source/osn-encoders.cpp @@ -68,7 +68,7 @@ const std::vector osn::EncoderUtils::videoEn APPLE_HARDWARE_VIDEO_ENCODER_M1, "", true, true, true, false, true, false, PRESET_APPLE, FAMILY_APPLE}, // get_simple_output_encoder had Apple HEVC so add it here, never used with an advanced name but follow the pattern of M1 above {"Apple VT HEVC Hardware Encoder", APPLE_HARDWARE_VIDEO_ENCODER_HEVC, "Hardware (Apple, HEVC)", SIMPLE_ENCODER_APPLE_HEVC, - APPLE_HARDWARE_VIDEO_ENCODER_HEVC, "", true, true, true, false, true, false, PRESET_APPLE, FAMILY_APPLE}, + APPLE_HARDWARE_VIDEO_ENCODER_HEVC, "", true, true, true, true, true, false, PRESET_APPLE, FAMILY_APPLE}, // AMD HW H.264 {"AMD HW H.264", ADVANCED_ENCODER_AMD, "Hardware (AMD, H.264)", SIMPLE_ENCODER_AMD, ADVANCED_ENCODER_AMD, "", true, true, true, false, true, false, PRESET_AMD, FAMILY_AMD}, @@ -212,6 +212,9 @@ bool osn::EncoderUtils::isEncoderCompatible(std::string encoderName, obs_service } if (!containerSupportsCodec(container, codec)) return false; + if (container == "flv" && videoEncoderOptions[checkIndex].family == FAMILY_APPLE) { + return false; + } } return true; From d5bdc10edeed458d02083d7b480f92a190f1714e Mon Sep 17 00:00:00 2001 From: Richard Osborne Date: Thu, 11 Jun 2026 14:13:42 -0500 Subject: [PATCH 4/7] initialize util-osx-impl.mm member vars --- obs-studio-server/source/util-osx-impl.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/obs-studio-server/source/util-osx-impl.mm b/obs-studio-server/source/util-osx-impl.mm index bd08a39c4..352b727b5 100644 --- a/obs-studio-server/source/util-osx-impl.mm +++ b/obs-studio-server/source/util-osx-impl.mm @@ -62,7 +62,7 @@ @implementation UtilImplObj -UtilObjCInt::UtilObjCInt(void) : self(NULL) {} +UtilObjCInt::UtilObjCInt(void) : self(NULL), state(EState::Idle), worker(nullptr) {} UtilObjCInt::~UtilObjCInt(void) { From cc87a2887322a0edeb008a546c15266251634c34 Mon Sep 17 00:00:00 2001 From: Aleksandr Voitenko Date: Tue, 30 Jun 2026 12:46:50 +0100 Subject: [PATCH 5/7] Allow Enhanced Broadcasting streams as recording parents (#1739) --- .../source/advanced-recording.cpp | 35 +++++++++++++++---- .../source/advanced-replay-buffer.cpp | 35 +++++++++++++++---- obs-studio-client/source/simple-recording.cpp | 35 +++++++++++++++---- .../source/simple-replay-buffer.cpp | 35 +++++++++++++++---- ...nhanced_broadcasting_advanced_streaming.ts | 28 +++++++++++++++ ..._enhanced_broadcasting_simple_streaming.ts | 28 +++++++++++++++ 6 files changed, 172 insertions(+), 24 deletions(-) diff --git a/obs-studio-client/source/advanced-recording.cpp b/obs-studio-client/source/advanced-recording.cpp index 00177ec76..b466ec497 100644 --- a/obs-studio-client/source/advanced-recording.cpp +++ b/obs-studio-client/source/advanced-recording.cpp @@ -19,6 +19,7 @@ #include "advanced-recording.hpp" #include "utility.hpp" #include "advanced-streaming.hpp" +#include "enhanced-broadcasting-advanced-streaming.hpp" Napi::FunctionReference osn::AdvancedRecording::constructor; @@ -310,22 +311,44 @@ void osn::AdvancedRecording::SetStreaming(const Napi::CallbackInfo &info, const return; } - Napi::Object obj = value.As(); - if (!obj.InstanceOf(osn::AdvancedStreaming::constructor.Value())) + if (!value.IsObject()) { Napi::TypeError::New(info.Env(), "Object is not a AdvancedStreaming").ThrowAsJavaScriptException(); + return; + } - osn::AdvancedStreaming *streaming = Napi::ObjectWrap::Unwrap(value.ToObject()); + Napi::Object obj = value.As(); + uint64_t streamingUid = UINT64_MAX; + if (obj.InstanceOf(osn::AdvancedStreaming::constructor.Value())) { + osn::AdvancedStreaming *streaming = Napi::ObjectWrap::Unwrap(obj); + if (!streaming) { + Napi::TypeError::New(info.Env(), "Invalid streaming argument").ThrowAsJavaScriptException(); + return; + } + + streamingUid = streaming->uid; + } else if (obj.InstanceOf(osn::EnhancedBroadcastingAdvancedStreaming::constructor.Value())) { + osn::EnhancedBroadcastingAdvancedStreaming *streaming = Napi::ObjectWrap::Unwrap(obj); + if (!streaming) { + Napi::TypeError::New(info.Env(), "Invalid streaming argument").ThrowAsJavaScriptException(); + return; + } + + streamingUid = streaming->uid; + } else { + Napi::TypeError::New(info.Env(), "Object is not a AdvancedStreaming").ThrowAsJavaScriptException(); + return; + } - if (!streaming) { + if (streamingUid == UINT64_MAX) { Napi::TypeError::New(info.Env(), "Invalid streaming argument").ThrowAsJavaScriptException(); return; } - auto response = conn->call_synchronous_helper(className, "SetStreaming", {ipc::value(this->uid), ipc::value(streaming->uid)}); + auto response = conn->call_synchronous_helper(className, "SetStreaming", {ipc::value(this->uid), ipc::value(streamingUid)}); if (!ValidateResponse(info, response)) return; if (!streamingRef.IsEmpty()) streamingRef.Reset(); streamingRef = Napi::Persistent(obj); -} \ No newline at end of file +} diff --git a/obs-studio-client/source/advanced-replay-buffer.cpp b/obs-studio-client/source/advanced-replay-buffer.cpp index 0bc628e39..94f34beeb 100644 --- a/obs-studio-client/source/advanced-replay-buffer.cpp +++ b/obs-studio-client/source/advanced-replay-buffer.cpp @@ -21,6 +21,7 @@ #include "audio-encoder.hpp" #include "advanced-streaming.hpp" #include "advanced-recording.hpp" +#include "enhanced-broadcasting-advanced-streaming.hpp" Napi::FunctionReference osn::AdvancedReplayBuffer::constructor; @@ -209,18 +210,40 @@ void osn::AdvancedReplayBuffer::SetStreaming(const Napi::CallbackInfo &info, con return; } - Napi::Object obj = value.As(); - if (!obj.InstanceOf(osn::AdvancedStreaming::constructor.Value())) + if (!value.IsObject()) { Napi::TypeError::New(info.Env(), "Object is not a valid Streaming").ThrowAsJavaScriptException(); + return; + } - osn::AdvancedStreaming *streaming = Napi::ObjectWrap::Unwrap(value.ToObject()); + Napi::Object obj = value.As(); + uint64_t streamingUid = UINT64_MAX; + if (obj.InstanceOf(osn::AdvancedStreaming::constructor.Value())) { + osn::AdvancedStreaming *streaming = Napi::ObjectWrap::Unwrap(obj); + if (!streaming) { + Napi::TypeError::New(info.Env(), "Invalid streaming argument").ThrowAsJavaScriptException(); + return; + } + + streamingUid = streaming->uid; + } else if (obj.InstanceOf(osn::EnhancedBroadcastingAdvancedStreaming::constructor.Value())) { + osn::EnhancedBroadcastingAdvancedStreaming *streaming = Napi::ObjectWrap::Unwrap(obj); + if (!streaming) { + Napi::TypeError::New(info.Env(), "Invalid streaming argument").ThrowAsJavaScriptException(); + return; + } + + streamingUid = streaming->uid; + } else { + Napi::TypeError::New(info.Env(), "Object is not a valid Streaming").ThrowAsJavaScriptException(); + return; + } - if (!streaming) { + if (streamingUid == UINT64_MAX) { Napi::TypeError::New(info.Env(), "Invalid streaming argument").ThrowAsJavaScriptException(); return; } - auto response = conn->call_synchronous_helper(className, "SetStreaming", {ipc::value(this->uid), ipc::value(streaming->uid)}); + auto response = conn->call_synchronous_helper(className, "SetStreaming", {ipc::value(this->uid), ipc::value(streamingUid)}); if (!ValidateResponse(info, response)) return; @@ -272,4 +295,4 @@ void osn::AdvancedReplayBuffer::SetRecording(const Napi::CallbackInfo &info, con if (!parentOutputRef.IsEmpty()) parentOutputRef.Reset(); parentOutputRef = Napi::Persistent(obj); -} \ No newline at end of file +} diff --git a/obs-studio-client/source/simple-recording.cpp b/obs-studio-client/source/simple-recording.cpp index fc9223320..059a73e8d 100644 --- a/obs-studio-client/source/simple-recording.cpp +++ b/obs-studio-client/source/simple-recording.cpp @@ -24,6 +24,7 @@ #include "reconnect.hpp" #include "network.hpp" #include "simple-streaming.hpp" +#include "enhanced-broadcasting-simple-streaming.hpp" Napi::FunctionReference osn::SimpleRecording::constructor; @@ -282,22 +283,44 @@ void osn::SimpleRecording::SetStreaming(const Napi::CallbackInfo &info, const Na return; } - Napi::Object obj = value.As(); - if (!obj.InstanceOf(osn::SimpleStreaming::constructor.Value())) + if (!value.IsObject()) { Napi::TypeError::New(info.Env(), "Object is not a SimpleStreaming").ThrowAsJavaScriptException(); + return; + } - osn::SimpleStreaming *streaming = Napi::ObjectWrap::Unwrap(value.ToObject()); + Napi::Object obj = value.As(); + uint64_t streamingUid = UINT64_MAX; + if (obj.InstanceOf(osn::SimpleStreaming::constructor.Value())) { + osn::SimpleStreaming *streaming = Napi::ObjectWrap::Unwrap(obj); + if (!streaming) { + Napi::TypeError::New(info.Env(), "Invalid streaming argument").ThrowAsJavaScriptException(); + return; + } + + streamingUid = streaming->uid; + } else if (obj.InstanceOf(osn::EnhancedBroadcastingSimpleStreaming::constructor.Value())) { + osn::EnhancedBroadcastingSimpleStreaming *streaming = Napi::ObjectWrap::Unwrap(obj); + if (!streaming) { + Napi::TypeError::New(info.Env(), "Invalid streaming argument").ThrowAsJavaScriptException(); + return; + } + + streamingUid = streaming->uid; + } else { + Napi::TypeError::New(info.Env(), "Object is not a SimpleStreaming").ThrowAsJavaScriptException(); + return; + } - if (!streaming) { + if (streamingUid == UINT64_MAX) { Napi::TypeError::New(info.Env(), "Invalid streaming argument").ThrowAsJavaScriptException(); return; } - auto response = conn->call_synchronous_helper(className, "SetStreaming", {ipc::value(this->uid), ipc::value(streaming->uid)}); + auto response = conn->call_synchronous_helper(className, "SetStreaming", {ipc::value(this->uid), ipc::value(streamingUid)}); if (!ValidateResponse(info, response)) return; if (!streamingRef.IsEmpty()) streamingRef.Reset(); streamingRef = Napi::Persistent(obj); -} \ No newline at end of file +} diff --git a/obs-studio-client/source/simple-replay-buffer.cpp b/obs-studio-client/source/simple-replay-buffer.cpp index 5c987257d..52f07cd91 100644 --- a/obs-studio-client/source/simple-replay-buffer.cpp +++ b/obs-studio-client/source/simple-replay-buffer.cpp @@ -21,6 +21,7 @@ #include "audio-encoder.hpp" #include "simple-streaming.hpp" #include "simple-recording.hpp" +#include "enhanced-broadcasting-simple-streaming.hpp" Napi::FunctionReference osn::SimpleReplayBuffer::constructor; @@ -184,18 +185,40 @@ void osn::SimpleReplayBuffer::SetStreaming(const Napi::CallbackInfo &info, const return; } - Napi::Object obj = value.As(); - if (!obj.InstanceOf(osn::SimpleStreaming::constructor.Value())) + if (!value.IsObject()) { Napi::TypeError::New(info.Env(), "Object is not a SimpleStreaming").ThrowAsJavaScriptException(); + return; + } - osn::SimpleStreaming *streaming = Napi::ObjectWrap::Unwrap(value.ToObject()); + Napi::Object obj = value.As(); + uint64_t streamingUid = UINT64_MAX; + if (obj.InstanceOf(osn::SimpleStreaming::constructor.Value())) { + osn::SimpleStreaming *streaming = Napi::ObjectWrap::Unwrap(obj); + if (!streaming) { + Napi::TypeError::New(info.Env(), "Invalid streaming argument").ThrowAsJavaScriptException(); + return; + } + + streamingUid = streaming->uid; + } else if (obj.InstanceOf(osn::EnhancedBroadcastingSimpleStreaming::constructor.Value())) { + osn::EnhancedBroadcastingSimpleStreaming *streaming = Napi::ObjectWrap::Unwrap(obj); + if (!streaming) { + Napi::TypeError::New(info.Env(), "Invalid streaming argument").ThrowAsJavaScriptException(); + return; + } + + streamingUid = streaming->uid; + } else { + Napi::TypeError::New(info.Env(), "Object is not a SimpleStreaming").ThrowAsJavaScriptException(); + return; + } - if (!streaming) { + if (streamingUid == UINT64_MAX) { Napi::TypeError::New(info.Env(), "Invalid streaming argument").ThrowAsJavaScriptException(); return; } - auto response = conn->call_synchronous_helper(className, "SetStreaming", {ipc::value(this->uid), ipc::value(streaming->uid)}); + auto response = conn->call_synchronous_helper(className, "SetStreaming", {ipc::value(this->uid), ipc::value(streamingUid)}); if (!ValidateResponse(info, response)) return; @@ -247,4 +270,4 @@ void osn::SimpleReplayBuffer::SetRecording(const Napi::CallbackInfo &info, const if (!parentOutputRef.IsEmpty()) parentOutputRef.Reset(); parentOutputRef = Napi::Persistent(obj); -} \ No newline at end of file +} diff --git a/tests/osn-tests/src/test_osn_enhanced_broadcasting_advanced_streaming.ts b/tests/osn-tests/src/test_osn_enhanced_broadcasting_advanced_streaming.ts index dd963b931..b2a3c1b57 100644 --- a/tests/osn-tests/src/test_osn_enhanced_broadcasting_advanced_streaming.ts +++ b/tests/osn-tests/src/test_osn_enhanced_broadcasting_advanced_streaming.ts @@ -120,6 +120,34 @@ describe(testName, () => { } }); + it('Can be used as the parent stream for advanced recording', function() { + const stream = osn.EnhancedBroadcastingAdvancedStreamingFactory.create(); + const recording = osn.AdvancedRecordingFactory.create(); + + try { + recording.streaming = stream; + + expect(recording.streaming).to.equal(stream); + } finally { + osn.AdvancedRecordingFactory.destroy(recording); + osn.EnhancedBroadcastingAdvancedStreamingFactory.destroy(stream); + } + }); + + it('Can be used as the parent stream for advanced replay buffer', function() { + const stream = osn.EnhancedBroadcastingAdvancedStreamingFactory.create(); + const replayBuffer = osn.AdvancedReplayBufferFactory.create(); + + try { + replayBuffer.streaming = stream; + + expect(replayBuffer.streaming).to.equal(stream); + } finally { + osn.AdvancedReplayBufferFactory.destroy(replayBuffer); + osn.EnhancedBroadcastingAdvancedStreamingFactory.destroy(stream); + } + }); + it('Enhanced Broadcasting Advanced Streaming Single Canvas', async function() { if (obs.isDarwin()) { this.skip(); diff --git a/tests/osn-tests/src/test_osn_enhanced_broadcasting_simple_streaming.ts b/tests/osn-tests/src/test_osn_enhanced_broadcasting_simple_streaming.ts index 0b9527603..65f1a2b36 100644 --- a/tests/osn-tests/src/test_osn_enhanced_broadcasting_simple_streaming.ts +++ b/tests/osn-tests/src/test_osn_enhanced_broadcasting_simple_streaming.ts @@ -69,6 +69,34 @@ describe(testName, () => { hasTestFailed = (await obs.finalizeRetryableTest(this)) || hasTestFailed; }); + it('Can be used as the parent stream for simple recording', function() { + const stream = osn.EnhancedBroadcastingSimpleStreamingFactory.create(); + const recording = osn.SimpleRecordingFactory.create(); + + try { + recording.streaming = stream; + + expect(recording.streaming).to.equal(stream); + } finally { + osn.SimpleRecordingFactory.destroy(recording); + osn.EnhancedBroadcastingSimpleStreamingFactory.destroy(stream); + } + }); + + it('Can be used as the parent stream for simple replay buffer', function() { + const stream = osn.EnhancedBroadcastingSimpleStreamingFactory.create(); + const replayBuffer = osn.SimpleReplayBufferFactory.create(); + + try { + replayBuffer.streaming = stream; + + expect(replayBuffer.streaming).to.equal(stream); + } finally { + osn.SimpleReplayBufferFactory.destroy(replayBuffer); + osn.EnhancedBroadcastingSimpleStreamingFactory.destroy(stream); + } + }); + it('Enhanced Broadcasting Simple Streaming honors stream delay', async function() { if (obs.isDarwin()) { this.skip(); From 4af2a1312b8f2f8d44dd7010d08ef52e9955531f Mon Sep 17 00:00:00 2001 From: Richard Osborne Date: Thu, 9 Jul 2026 16:22:13 -0500 Subject: [PATCH 6/7] bump libOBS * get updates from fix/onacceleratedpaint-race branch, commit 7fef2cce4 --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index cdaa0f65c..e0bf83c7c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,7 +25,7 @@ env: SLGenerator: Visual Studio 17 2022 SLDistributeDirectory: distribute SLFullDistributePath: "streamlabs-build.app/distribute" # The .app extension is required to run macOS tests correctly. - LibOBSVersion: 31.1.2sl19b2 + LibOBSVersion: 31.1.2sl19b3 PACKAGE_NAME: osn jobs: From 85cdd06bb27bb675e41439a5513f97a04c8a71f5 Mon Sep 17 00:00:00 2001 From: Vladimir Sumarov Date: Wed, 12 Aug 2026 22:28:58 +0200 Subject: [PATCH 7/7] Update libobs to v31.1.2sl19b5 Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e0bf83c7c..c2fe9d9e2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,7 +25,7 @@ env: SLGenerator: Visual Studio 17 2022 SLDistributeDirectory: distribute SLFullDistributePath: "streamlabs-build.app/distribute" # The .app extension is required to run macOS tests correctly. - LibOBSVersion: 31.1.2sl19b3 + LibOBSVersion: 31.1.2sl19b5 PACKAGE_NAME: osn jobs: