From 8ed4c2a41099adcf926a53507fdf02120875884a Mon Sep 17 00:00:00 2001 From: Aleksandr Voitenko Date: Thu, 25 Jun 2026 16:52:23 +0100 Subject: [PATCH] Allow Enhanced Broadcasting streams as recording parents --- .../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();