diff --git a/js/module.d.ts b/js/module.d.ts index 2bb3aee9f..d7c7003ee 100644 --- a/js/module.d.ts +++ b/js/module.d.ts @@ -754,8 +754,8 @@ export declare const enum ERecordingFormat { FLV = "flv", MOV = "mov", MKV = "mkv", - TS = "mpegts", - M3M8 = "m3m8" + MPEGTS = "ts", + HLS = "m3u8" } export declare const enum ERecordingQuality { Stream = 0, @@ -774,19 +774,19 @@ export declare const enum EProcessPriority { BelowNormal = "BelowNormal", Idle = "Idle" } -export interface IVideoEncoder extends IConfigurable { +export interface IVideoEncoder extends IConfigurable, IReleasable { name: string; readonly type: EVideoEncoderType; readonly active: boolean; readonly id: string; readonly lastError: string; } -export interface IAudioEncoder { +export interface IAudioEncoder extends IReleasable { name: string; bitrate: number; } export interface IAudioEncoderFactory { - create(): IAudioEncoder; + create(id: string, name: string): IAudioEncoder; } export interface IVideoEncoderFactory { types(): string[]; @@ -911,7 +911,7 @@ export interface ISimpleReplayBufferFactory { export interface IAdvancedReplayBufferFactory { create(): IAdvancedReplayBuffer; destroy(stream: IAdvancedReplayBuffer): void; - legacySettings: IAdvancedReplayBufferFactory; + legacySettings: IAdvancedReplayBuffer; } export interface IDelay { enabled: boolean; diff --git a/js/module.ts b/js/module.ts index f4fcb7988..5be503343 100644 --- a/js/module.ts +++ b/js/module.ts @@ -1653,8 +1653,8 @@ export const enum ERecordingFormat { FLV = 'flv', MOV = 'mov', MKV = 'mkv', - TS = 'mpegts', - M3M8 = 'm3m8' + MPEGTS = 'ts', + HLS = 'm3u8' } export const enum ERecordingQuality { @@ -1677,7 +1677,7 @@ export const enum EProcessPriority { Idle = 'Idle' } -export interface IVideoEncoder extends IConfigurable { +export interface IVideoEncoder extends IConfigurable, IReleasable { name: string, readonly type: EVideoEncoderType, readonly active: boolean, @@ -1685,13 +1685,13 @@ export interface IVideoEncoder extends IConfigurable { readonly lastError: string } -export interface IAudioEncoder { +export interface IAudioEncoder extends IReleasable { name: string, bitrate: number } export interface IAudioEncoderFactory { - create(): IAudioEncoder + create(id: string, name: string): IAudioEncoder } export interface IVideoEncoderFactory { @@ -1834,7 +1834,7 @@ export interface ISimpleReplayBufferFactory { export interface IAdvancedReplayBufferFactory { create(): IAdvancedReplayBuffer; destroy(stream: IAdvancedReplayBuffer): void; - legacySettings: IAdvancedReplayBufferFactory; + legacySettings: IAdvancedReplayBuffer; } export interface IDelay { diff --git a/obs-studio-client/source/advanced-recording.cpp b/obs-studio-client/source/advanced-recording.cpp index d98f75a1a..92bf450a1 100644 --- a/obs-studio-client/source/advanced-recording.cpp +++ b/obs-studio-client/source/advanced-recording.cpp @@ -81,6 +81,19 @@ osn::AdvancedRecording::AdvancedRecording(const Napi::CallbackInfo &info) : Napi this->className = std::string("AdvancedRecording"); } +void osn::AdvancedRecording::Finalize(Napi::Env) +{ + ReleaseObjects(); +} + +void osn::AdvancedRecording::ReleaseObjects() +{ + if (!videoEncoderRef.IsEmpty()) + videoEncoderRef.Reset(); + if (!streamingRef.IsEmpty()) + streamingRef.Reset(); +} + Napi::Value osn::AdvancedRecording::Create(const Napi::CallbackInfo &info) { auto conn = GetConnection(info); @@ -107,6 +120,8 @@ void osn::AdvancedRecording::Destroy(const Napi::CallbackInfo &info) recording->stopWorker(); recording->cb.Reset(); + recording->ReleaseObjects(); + auto conn = GetConnection(info); if (!conn) return; @@ -268,22 +283,27 @@ void osn::AdvancedRecording::SetLegacySettings(const Napi::CallbackInfo &info, c } Napi::Value osn::AdvancedRecording::GetStreaming(const Napi::CallbackInfo &info) +{ + return streamingRef.IsEmpty() ? info.Env().Undefined() : streamingRef.Value(); +} + +void osn::AdvancedRecording::SetStreaming(const Napi::CallbackInfo &info, const Napi::Value &value) { auto conn = GetConnection(info); if (!conn) - return info.Env().Undefined(); - - std::vector response = conn->call_synchronous_helper(className, "GetStreaming", {ipc::value(this->uid)}); + return; - if (!ValidateResponse(info, response)) - return info.Env().Undefined(); + if (value.IsNull() || value.IsUndefined()) { + if (!streamingRef.IsEmpty()) + streamingRef.Reset(); + conn->call(className, "SetStreaming", {ipc::value(this->uid), ipc::value(UINT64_MAX)}); + return; + } - auto instance = osn::AdvancedStreaming::constructor.New({Napi::Number::New(info.Env(), static_cast(response[1].value_union.ui64))}); - return instance; -} + Napi::Object obj = value.As(); + if (!obj.InstanceOf(osn::AdvancedStreaming::constructor.Value())) + Napi::TypeError::New(info.Env(), "Object is not a AdvancedStreaming").ThrowAsJavaScriptException(); -void osn::AdvancedRecording::SetStreaming(const Napi::CallbackInfo &info, const Napi::Value &value) -{ osn::AdvancedStreaming *streaming = Napi::ObjectWrap::Unwrap(value.ToObject()); if (!streaming) { @@ -291,9 +311,10 @@ void osn::AdvancedRecording::SetStreaming(const Napi::CallbackInfo &info, const return; } - auto conn = GetConnection(info); - if (!conn) - return; - conn->call(className, "SetStreaming", {ipc::value(this->uid), ipc::value(streaming->uid)}); + + if (!streamingRef.IsEmpty()) + streamingRef.Reset(); + + streamingRef = Napi::Persistent(obj); } \ No newline at end of file diff --git a/obs-studio-client/source/advanced-recording.hpp b/obs-studio-client/source/advanced-recording.hpp index 8bdf25edb..af3215585 100644 --- a/obs-studio-client/source/advanced-recording.hpp +++ b/obs-studio-client/source/advanced-recording.hpp @@ -26,7 +26,8 @@ class AdvancedRecording : public Napi::ObjectWrap, publi static Napi::FunctionReference constructor; static Napi::Object Init(Napi::Env env, Napi::Object exports); AdvancedRecording(const Napi::CallbackInfo &info); - + void Finalize(Napi::Env); + void ReleaseObjects(); static Napi::Value Create(const Napi::CallbackInfo &info); static void Destroy(const Napi::CallbackInfo &info); diff --git a/obs-studio-client/source/advanced-replay-buffer.cpp b/obs-studio-client/source/advanced-replay-buffer.cpp index cb26f1445..6ee7eef7b 100644 --- a/obs-studio-client/source/advanced-replay-buffer.cpp +++ b/obs-studio-client/source/advanced-replay-buffer.cpp @@ -78,6 +78,17 @@ osn::AdvancedReplayBuffer::AdvancedReplayBuffer(const Napi::CallbackInfo &info) this->className = std::string("AdvancedReplayBuffer"); } +void osn::AdvancedReplayBuffer::Finalize(Napi::Env) +{ + ReleaseObjects(); +} + +void osn::AdvancedReplayBuffer::ReleaseObjects() +{ + if (!parentOutputRef.IsEmpty()) + parentOutputRef.Reset(); +} + Napi::Value osn::AdvancedReplayBuffer::Create(const Napi::CallbackInfo &info) { auto conn = GetConnection(info); @@ -103,6 +114,7 @@ void osn::AdvancedReplayBuffer::Destroy(const Napi::CallbackInfo &info) replayBuffer->stopWorker(); replayBuffer->cb.Reset(); + replayBuffer->ReleaseObjects(); auto conn = GetConnection(info); if (!conn) @@ -174,62 +186,82 @@ void osn::AdvancedReplayBuffer::SetLegacySettings(const Napi::CallbackInfo &info Napi::Value osn::AdvancedReplayBuffer::GetStreaming(const Napi::CallbackInfo &info) { - auto conn = GetConnection(info); - if (!conn) - return info.Env().Undefined(); - - std::vector response = conn->call_synchronous_helper(className, "GetStreaming", {ipc::value(this->uid)}); - - if (!ValidateResponse(info, response)) + if (usesStream) { + return parentOutputRef.IsEmpty() ? info.Env().Undefined() : parentOutputRef.Value(); + } else { return info.Env().Undefined(); - - auto instance = osn::AdvancedStreaming::constructor.New({Napi::Number::New(info.Env(), static_cast(response[1].value_union.ui64))}); - return instance; + } } void osn::AdvancedReplayBuffer::SetStreaming(const Napi::CallbackInfo &info, const Napi::Value &value) { - osn::AdvancedStreaming *encoder = Napi::ObjectWrap::Unwrap(value.ToObject()); + auto conn = GetConnection(info); + if (!conn) + return; - if (!encoder) { - Napi::TypeError::New(info.Env(), "Invalid streaming argument").ThrowAsJavaScriptException(); + if (value.IsNull() || value.IsUndefined()) { + if (!parentOutputRef.IsEmpty()) + parentOutputRef.Reset(); + conn->call(className, "SetStreaming", {ipc::value(this->uid), ipc::value(UINT64_MAX)}); + usesStream = false; return; } - auto conn = GetConnection(info); - if (!conn) + Napi::Object obj = value.As(); + if (!obj.InstanceOf(osn::AdvancedStreaming::constructor.Value())) + Napi::TypeError::New(info.Env(), "Object is not a valid Streaming").ThrowAsJavaScriptException(); + + osn::AdvancedStreaming *streaming = Napi::ObjectWrap::Unwrap(value.ToObject()); + + if (!streaming) { + Napi::TypeError::New(info.Env(), "Invalid streaming argument").ThrowAsJavaScriptException(); return; + } + + conn->call(className, "SetStreaming", {ipc::value(this->uid), ipc::value(streaming->uid)}); + usesStream = true; + if (!parentOutputRef.IsEmpty()) + parentOutputRef.Reset(); - conn->call(className, "SetStreaming", {ipc::value(this->uid), ipc::value(encoder->uid)}); + parentOutputRef = Napi::Persistent(obj); } Napi::Value osn::AdvancedReplayBuffer::GetRecording(const Napi::CallbackInfo &info) { - auto conn = GetConnection(info); - if (!conn) + if (usesStream) { return info.Env().Undefined(); - - std::vector response = conn->call_synchronous_helper(className, "GetRecording", {ipc::value(this->uid)}); - - if (!ValidateResponse(info, response)) - return info.Env().Undefined(); - - auto instance = osn::AdvancedRecording::constructor.New({Napi::Number::New(info.Env(), static_cast(response[1].value_union.ui64))}); - return instance; + } else { + return parentOutputRef.IsEmpty() ? info.Env().Undefined() : parentOutputRef.Value(); + } } void osn::AdvancedReplayBuffer::SetRecording(const Napi::CallbackInfo &info, const Napi::Value &value) { - osn::AdvancedRecording *recording = Napi::ObjectWrap::Unwrap(value.ToObject()); + auto conn = GetConnection(info); + if (!conn) + return; - if (!recording) { - Napi::TypeError::New(info.Env(), "Invalid streaming argument").ThrowAsJavaScriptException(); + if (value.IsNull() || value.IsUndefined()) { + if (!parentOutputRef.IsEmpty()) + parentOutputRef.Reset(); + conn->call(className, "SetRecording", {ipc::value(this->uid), ipc::value(UINT64_MAX)}); + usesStream = false; return; } - auto conn = GetConnection(info); - if (!conn) + Napi::Object obj = value.As(); + if (!obj.InstanceOf(osn::AdvancedRecording::constructor.Value())) + Napi::TypeError::New(info.Env(), "Object is not a Valid Recording").ThrowAsJavaScriptException(); + + osn::AdvancedRecording *recording = Napi::ObjectWrap::Unwrap(obj); + if (!recording) { + Napi::TypeError::New(info.Env(), "Invalid recording argument").ThrowAsJavaScriptException(); return; + } conn->call(className, "SetRecording", {ipc::value(this->uid), ipc::value(recording->uid)}); + usesStream = false; + if (!parentOutputRef.IsEmpty()) + parentOutputRef.Reset(); + parentOutputRef = Napi::Persistent(obj); } \ No newline at end of file diff --git a/obs-studio-client/source/advanced-replay-buffer.hpp b/obs-studio-client/source/advanced-replay-buffer.hpp index 4395f8fa4..60cdeeb36 100644 --- a/obs-studio-client/source/advanced-replay-buffer.hpp +++ b/obs-studio-client/source/advanced-replay-buffer.hpp @@ -26,7 +26,8 @@ class AdvancedReplayBuffer : public Napi::ObjectWrap, static Napi::FunctionReference constructor; static Napi::Object Init(Napi::Env env, Napi::Object exports); AdvancedReplayBuffer(const Napi::CallbackInfo &info); - + void Finalize(Napi::Env); + void ReleaseObjects(); static Napi::Value Create(const Napi::CallbackInfo &info); static void Destroy(const Napi::CallbackInfo &info); diff --git a/obs-studio-client/source/advanced-streaming.cpp b/obs-studio-client/source/advanced-streaming.cpp index 267169b07..018896e18 100644 --- a/obs-studio-client/source/advanced-streaming.cpp +++ b/obs-studio-client/source/advanced-streaming.cpp @@ -80,6 +80,16 @@ osn::AdvancedStreaming::AdvancedStreaming(const Napi::CallbackInfo &info) : Napi this->className = std::string("AdvancedStreaming"); } +void osn::AdvancedStreaming::Finalize(Napi::Env) +{ + ReleaseObjects(); +} + +void osn::AdvancedStreaming::ReleaseObjects() +{ + osn::Streaming::ReleaseObjects(); +} + Napi::Value osn::AdvancedStreaming::Create(const Napi::CallbackInfo &info) { auto conn = GetConnection(info); @@ -106,6 +116,8 @@ void osn::AdvancedStreaming::Destroy(const Napi::CallbackInfo &info) stream->stopWorker(); stream->cb.Reset(); + stream->ReleaseObjects(); + auto conn = GetConnection(info); if (!conn) return; diff --git a/obs-studio-client/source/advanced-streaming.hpp b/obs-studio-client/source/advanced-streaming.hpp index 261c5585c..354f54af3 100644 --- a/obs-studio-client/source/advanced-streaming.hpp +++ b/obs-studio-client/source/advanced-streaming.hpp @@ -26,7 +26,8 @@ class AdvancedStreaming : public Napi::ObjectWrap, publi static Napi::FunctionReference constructor; static Napi::Object Init(Napi::Env env, Napi::Object exports); AdvancedStreaming(const Napi::CallbackInfo &info); - + void Finalize(Napi::Env); + void ReleaseObjects(); static Napi::Value Create(const Napi::CallbackInfo &info); static void Destroy(const Napi::CallbackInfo &info); diff --git a/obs-studio-client/source/audio-encoder.cpp b/obs-studio-client/source/audio-encoder.cpp index 08cd273a6..d13e626e1 100644 --- a/obs-studio-client/source/audio-encoder.cpp +++ b/obs-studio-client/source/audio-encoder.cpp @@ -32,6 +32,8 @@ Napi::Object osn::AudioEncoder::Init(Napi::Env env, Napi::Object exports) InstanceAccessor("name", &osn::AudioEncoder::GetName, &osn::AudioEncoder::SetName), InstanceAccessor("bitrate", &osn::AudioEncoder::GetBitrate, &osn::AudioEncoder::SetBitrate), + + InstanceMethod("release", &osn::AudioEncoder::Release), }); exports.Set("AudioEncoder", func); osn::AudioEncoder::constructor = Napi::Persistent(func); @@ -45,6 +47,7 @@ osn::AudioEncoder::AudioEncoder(const Napi::CallbackInfo &info) : Napi::ObjectWr Napi::HandleScope scope(env); size_t length = info.Length(); this->uid = 0; + this->encoderInitialized = false; if (length <= 0 || !info[0].IsNumber()) { Napi::TypeError::New(env, "Number expected").ThrowAsJavaScriptException(); @@ -52,15 +55,24 @@ osn::AudioEncoder::AudioEncoder(const Napi::CallbackInfo &info) : Napi::ObjectWr } this->uid = (uint64_t)info[0].ToNumber().Int64Value(); + this->encoderInitialized = true; } Napi::Value osn::AudioEncoder::Create(const Napi::CallbackInfo &info) { + std::string id = "ffmpeg_aac"; + std::string name = "audio-encoder"; + + if (info.Length() >= 2) { + id = info[0].ToString().Utf8Value(); + name = info[1].ToString().Utf8Value(); + } + auto conn = GetConnection(info); if (!conn) return info.Env().Undefined(); - std::vector response = conn->call_synchronous_helper("AudioEncoder", "Create", {}); + std::vector response = conn->call_synchronous_helper("AudioEncoder", "Create", {ipc::value(id), ipc::value(name)}); if (!ValidateResponse(info, response)) return info.Env().Undefined(); @@ -70,6 +82,38 @@ Napi::Value osn::AudioEncoder::Create(const Napi::CallbackInfo &info) return instance; } +void osn::AudioEncoder::Finalize(Napi::Env env) +{ + if (!this->encoderInitialized) + return; + + auto conn = GetConnection(env); + if (!conn) + return; + + std::vector response = conn->call_synchronous_helper("AudioEncoder", "Finalize", {ipc::value(this->uid)}); + this->encoderInitialized = false; + this->uid = UINT64_MAX; + if (!ValidateResponse(env, response)) + return; +} + +void osn::AudioEncoder::Release(const Napi::CallbackInfo &info) +{ + if (!this->encoderInitialized) + return; + + auto conn = GetConnection(info); + if (!conn) + return; + + this->encoderInitialized = false; + std::vector response = conn->call_synchronous_helper("AudioEncoder", "Release", {ipc::value(this->uid)}); + this->uid = UINT64_MAX; + if (!ValidateResponse(info, response)) + return; +} + Napi::Value osn::AudioEncoder::GetName(const Napi::CallbackInfo &info) { auto conn = GetConnection(info); diff --git a/obs-studio-client/source/audio-encoder.hpp b/obs-studio-client/source/audio-encoder.hpp index f03c2d1b3..99e0141e2 100644 --- a/obs-studio-client/source/audio-encoder.hpp +++ b/obs-studio-client/source/audio-encoder.hpp @@ -24,6 +24,7 @@ namespace osn { class AudioEncoder : public Napi::ObjectWrap { public: uint64_t uid; + bool encoderInitialized; public: static Napi::FunctionReference constructor; @@ -31,6 +32,8 @@ class AudioEncoder : public Napi::ObjectWrap { AudioEncoder(const Napi::CallbackInfo &info); static Napi::Value Create(const Napi::CallbackInfo &info); + void Finalize(Napi::Env env); + void Release(const Napi::CallbackInfo &info); Napi::Value GetName(const Napi::CallbackInfo &info); void SetName(const Napi::CallbackInfo &info, const Napi::Value &value); diff --git a/obs-studio-client/source/delay.cpp b/obs-studio-client/source/delay.cpp index 87f27990a..0fb532ff8 100644 --- a/obs-studio-client/source/delay.cpp +++ b/obs-studio-client/source/delay.cpp @@ -71,6 +71,20 @@ Napi::Value osn::Delay::Create(const Napi::CallbackInfo &info) return instance; } +void osn::Delay::Finalize(Napi::Env env) +{ + auto conn = GetConnection(env); + if (!conn) + return; + + if (this->uid == UINT64_MAX) + return; + + conn->call_synchronous_helper("Delay", "Destroy", {ipc::value(this->uid)}); + + this->uid = UINT64_MAX; +} + Napi::Value osn::Delay::GetEnabled(const Napi::CallbackInfo &info) { auto conn = GetConnection(info); diff --git a/obs-studio-client/source/delay.hpp b/obs-studio-client/source/delay.hpp index 605a5754f..13092f19c 100644 --- a/obs-studio-client/source/delay.hpp +++ b/obs-studio-client/source/delay.hpp @@ -30,7 +30,7 @@ class Delay : public Napi::ObjectWrap { Delay(const Napi::CallbackInfo &info); static Napi::Value Create(const Napi::CallbackInfo &info); - + void Finalize(Napi::Env env); Napi::Value GetEnabled(const Napi::CallbackInfo &info); void SetEnabled(const Napi::CallbackInfo &info, const Napi::Value &value); Napi::Value GetDelaySec(const Napi::CallbackInfo &info); diff --git a/obs-studio-client/source/network.cpp b/obs-studio-client/source/network.cpp index 840548efa..47971b6c8 100644 --- a/obs-studio-client/source/network.cpp +++ b/obs-studio-client/source/network.cpp @@ -48,7 +48,7 @@ osn::Network::Network(const Napi::CallbackInfo &info) : Napi::ObjectWrapuid = 0; + this->uid = UINT64_MAX; if (length <= 0 || !info[0].IsNumber()) { Napi::TypeError::New(env, "Number expected").ThrowAsJavaScriptException(); @@ -74,6 +74,20 @@ Napi::Value osn::Network::Create(const Napi::CallbackInfo &info) return instance; } +void osn::Network::Finalize(Napi::Env env) +{ + auto conn = GetConnection(env); + if (!conn) + return; + + if (this->uid == UINT64_MAX) + return; + + conn->call_synchronous_helper("Network", "Destroy", {ipc::value(this->uid)}); + + this->uid = UINT64_MAX; +} + Napi::Value osn::Network::GetBindIP(const Napi::CallbackInfo &info) { auto conn = GetConnection(info); diff --git a/obs-studio-client/source/network.hpp b/obs-studio-client/source/network.hpp index 6a3b14824..fbd343cbe 100644 --- a/obs-studio-client/source/network.hpp +++ b/obs-studio-client/source/network.hpp @@ -30,7 +30,7 @@ class Network : public Napi::ObjectWrap { Network(const Napi::CallbackInfo &info); static Napi::Value Create(const Napi::CallbackInfo &info); - + void Finalize(Napi::Env env); Napi::Value GetBindIP(const Napi::CallbackInfo &info); void SetBindIP(const Napi::CallbackInfo &info, const Napi::Value &value); Napi::Value GetNetworkInterfaces(const Napi::CallbackInfo &info); diff --git a/obs-studio-client/source/reconnect.cpp b/obs-studio-client/source/reconnect.cpp index 237ba54ab..95ecdb1be 100644 --- a/obs-studio-client/source/reconnect.cpp +++ b/obs-studio-client/source/reconnect.cpp @@ -71,6 +71,18 @@ Napi::Value osn::Reconnect::Create(const Napi::CallbackInfo &info) return instance; } +void osn::Reconnect::Finalize(Napi::Env env) +{ + auto conn = GetConnection(env); + if (!conn) + return; + + if (this->uid != UINT64_MAX) { + conn->call_synchronous_helper("Reconnect", "Destroy", {ipc::value(this->uid)}); + this->uid = UINT64_MAX; + } +} + Napi::Value osn::Reconnect::GetEnabled(const Napi::CallbackInfo &info) { auto conn = GetConnection(info); diff --git a/obs-studio-client/source/reconnect.hpp b/obs-studio-client/source/reconnect.hpp index 29ebfe273..fed6871ef 100644 --- a/obs-studio-client/source/reconnect.hpp +++ b/obs-studio-client/source/reconnect.hpp @@ -30,6 +30,7 @@ class Reconnect : public Napi::ObjectWrap { Reconnect(const Napi::CallbackInfo &info); static Napi::Value Create(const Napi::CallbackInfo &info); + void Finalize(Napi::Env env); Napi::Value GetEnabled(const Napi::CallbackInfo &info); void SetEnabled(const Napi::CallbackInfo &info, const Napi::Value &value); diff --git a/obs-studio-client/source/recording.cpp b/obs-studio-client/source/recording.cpp index 656aed422..62d607461 100644 --- a/obs-studio-client/source/recording.cpp +++ b/obs-studio-client/source/recording.cpp @@ -21,23 +21,27 @@ #include "video-encoder.hpp" Napi::Value osn::Recording::GetVideoEncoder(const Napi::CallbackInfo &info) +{ + return videoEncoderRef.IsEmpty() ? info.Env().Undefined() : videoEncoderRef.Value(); +} + +void osn::Recording::SetVideoEncoder(const Napi::CallbackInfo &info, const Napi::Value &value) { auto conn = GetConnection(info); if (!conn) - return info.Env().Undefined(); - - std::vector response = conn->call_synchronous_helper(className, "GetVideoEncoder", {ipc::value(this->uid)}); - - if (!ValidateResponse(info, response)) - return info.Env().Undefined(); + return; - auto instance = osn::VideoEncoder::constructor.New({Napi::Number::New(info.Env(), static_cast(response[1].value_union.ui64))}); + if (value.IsNull() || value.IsUndefined()) { + if (!videoEncoderRef.IsEmpty()) + videoEncoderRef.Reset(); + conn->call(className, "SetVideoEncoder", {ipc::value(this->uid), ipc::value(UINT64_MAX)}); + return; + } - return instance; -} + Napi::Object obj = value.As(); + if (!obj.InstanceOf(osn::VideoEncoder::constructor.Value())) + Napi::TypeError::New(info.Env(), "Object is not a VideoEncoder").ThrowAsJavaScriptException(); -void osn::Recording::SetVideoEncoder(const Napi::CallbackInfo &info, const Napi::Value &value) -{ osn::VideoEncoder *encoder = Napi::ObjectWrap::Unwrap(value.ToObject()); if (!encoder) { @@ -45,11 +49,12 @@ void osn::Recording::SetVideoEncoder(const Napi::CallbackInfo &info, const Napi: return; } - auto conn = GetConnection(info); - if (!conn) - return; - conn->call(className, "SetVideoEncoder", {ipc::value(this->uid), ipc::value(encoder->uid)}); + + if (!videoEncoderRef.IsEmpty()) + videoEncoderRef.Reset(); + + videoEncoderRef = Napi::Persistent(obj); } Napi::Value osn::Recording::GetSignalHandler(const Napi::CallbackInfo &info) diff --git a/obs-studio-client/source/recording.hpp b/obs-studio-client/source/recording.hpp index 84af80883..b597b7222 100644 --- a/obs-studio-client/source/recording.hpp +++ b/obs-studio-client/source/recording.hpp @@ -29,6 +29,9 @@ class Recording : public WorkerSignals, public FileOutput { protected: Napi::Function signalHandler; std::string className; + Napi::Reference videoEncoderRef; + Napi::Reference streamingRef; + Napi::Reference audioEncoderRef; Napi::Value GetVideoEncoder(const Napi::CallbackInfo &info); void SetVideoEncoder(const Napi::CallbackInfo &info, const Napi::Value &value); diff --git a/obs-studio-client/source/replay-buffer.cpp b/obs-studio-client/source/replay-buffer.cpp index f459cd4b5..de019905c 100644 --- a/obs-studio-client/source/replay-buffer.cpp +++ b/obs-studio-client/source/replay-buffer.cpp @@ -91,24 +91,17 @@ void osn::ReplayBuffer::SetSuffix(const Napi::CallbackInfo &info, const Napi::Va Napi::Value osn::ReplayBuffer::GetUsesStream(const Napi::CallbackInfo &info) { - auto conn = GetConnection(info); - if (!conn) - return info.Env().Undefined(); - - std::vector response = conn->call_synchronous_helper(className, "GetUsesStream", {ipc::value(this->uid)}); - - if (!ValidateResponse(info, response)) - return info.Env().Undefined(); - - return Napi::Boolean::New(info.Env(), response[1].value_union.ui32); + return Napi::Boolean::New(info.Env(), usesStream); } void osn::ReplayBuffer::SetUsesStream(const Napi::CallbackInfo &info, const Napi::Value &value) { + //todo should be depricated auto conn = GetConnection(info); if (!conn) return; + usesStream = value.ToBoolean().Value(); conn->call_synchronous_helper(className, "SetUsesStream", {ipc::value(this->uid), ipc::value(value.ToBoolean().Value())}); } diff --git a/obs-studio-client/source/replay-buffer.hpp b/obs-studio-client/source/replay-buffer.hpp index 5250b5d75..8fbac491f 100644 --- a/obs-studio-client/source/replay-buffer.hpp +++ b/obs-studio-client/source/replay-buffer.hpp @@ -29,6 +29,8 @@ class ReplayBuffer : public WorkerSignals, public FileOutput { protected: Napi::Function signalHandler; std::string className; + Napi::Reference parentOutputRef; + bool usesStream = false; Napi::Value GetDuration(const Napi::CallbackInfo &info); void SetDuration(const Napi::CallbackInfo &info, const Napi::Value &value); diff --git a/obs-studio-client/source/simple-recording.cpp b/obs-studio-client/source/simple-recording.cpp index f80456ec1..148efe52a 100644 --- a/obs-studio-client/source/simple-recording.cpp +++ b/obs-studio-client/source/simple-recording.cpp @@ -85,6 +85,23 @@ osn::SimpleRecording::SimpleRecording(const Napi::CallbackInfo &info) : Napi::Ob this->className = std::string("SimpleRecording"); } +void osn::SimpleRecording::Finalize(Napi::Env) +{ + ReleaseObjects(); +} + +void osn::SimpleRecording::ReleaseObjects() +{ + if (!videoEncoderRef.IsEmpty()) + videoEncoderRef.Reset(); + + if (!streamingRef.IsEmpty()) + streamingRef.Reset(); + + if (!audioEncoderRef.IsEmpty()) + audioEncoderRef.Reset(); +} + Napi::Value osn::SimpleRecording::Create(const Napi::CallbackInfo &info) { auto conn = GetConnection(info); @@ -111,6 +128,8 @@ void osn::SimpleRecording::Destroy(const Napi::CallbackInfo &info) recording->stopWorker(); recording->cb.Reset(); + recording->ReleaseObjects(); + auto conn = GetConnection(info); if (!conn) return; @@ -145,22 +164,27 @@ void osn::SimpleRecording::SetQuality(const Napi::CallbackInfo &info, const Napi } Napi::Value osn::SimpleRecording::GetAudioEncoder(const Napi::CallbackInfo &info) +{ + return audioEncoderRef.IsEmpty() ? info.Env().Undefined() : audioEncoderRef.Value(); +} + +void osn::SimpleRecording::SetAudioEncoder(const Napi::CallbackInfo &info, const Napi::Value &value) { auto conn = GetConnection(info); if (!conn) - return info.Env().Undefined(); + return; - std::vector response = conn->call_synchronous_helper("SimpleRecording", "GetAudioEncoder", {ipc::value(this->uid)}); + if (value.IsNull() || value.IsUndefined()) { + if (!audioEncoderRef.IsEmpty()) + audioEncoderRef.Reset(); + conn->call(className, "SetAudioEncoder", {ipc::value(this->uid), ipc::value(UINT64_MAX)}); + return; + } - if (!ValidateResponse(info, response)) - return info.Env().Undefined(); + Napi::Object obj = value.As(); + if (!obj.InstanceOf(osn::AudioEncoder::constructor.Value())) + Napi::TypeError::New(info.Env(), "Object is not a AudioEncoder").ThrowAsJavaScriptException(); - auto instance = osn::AudioEncoder::constructor.New({Napi::Number::New(info.Env(), static_cast(response[1].value_union.ui64))}); - return instance; -} - -void osn::SimpleRecording::SetAudioEncoder(const Napi::CallbackInfo &info, const Napi::Value &value) -{ osn::AudioEncoder *encoder = Napi::ObjectWrap::Unwrap(value.ToObject()); if (!encoder) { @@ -168,11 +192,12 @@ void osn::SimpleRecording::SetAudioEncoder(const Napi::CallbackInfo &info, const return; } - auto conn = GetConnection(info); - if (!conn) - return; - conn->call(className, "SetAudioEncoder", {ipc::value(this->uid), ipc::value(encoder->uid)}); + + if (!audioEncoderRef.IsEmpty()) + audioEncoderRef.Reset(); + + audioEncoderRef = Napi::Persistent(obj); } Napi::Value osn::SimpleRecording::GetLowCPU(const Napi::CallbackInfo &info) @@ -234,22 +259,27 @@ void osn::SimpleRecording::SetLegacySettings(const Napi::CallbackInfo &info, con } Napi::Value osn::SimpleRecording::GetStreaming(const Napi::CallbackInfo &info) +{ + return streamingRef.IsEmpty() ? info.Env().Undefined() : streamingRef.Value(); +} + +void osn::SimpleRecording::SetStreaming(const Napi::CallbackInfo &info, const Napi::Value &value) { auto conn = GetConnection(info); if (!conn) - return info.Env().Undefined(); - - std::vector response = conn->call_synchronous_helper(className, "GetStreaming", {ipc::value(this->uid)}); + return; - if (!ValidateResponse(info, response)) - return info.Env().Undefined(); + if (value.IsNull() || value.IsUndefined()) { + if (!streamingRef.IsEmpty()) + streamingRef.Reset(); + conn->call(className, "SetStreaming", {ipc::value(this->uid), ipc::value(UINT64_MAX)}); + return; + } - auto instance = osn::SimpleStreaming::constructor.New({Napi::Number::New(info.Env(), static_cast(response[1].value_union.ui64))}); - return instance; -} + Napi::Object obj = value.As(); + if (!obj.InstanceOf(osn::SimpleStreaming::constructor.Value())) + Napi::TypeError::New(info.Env(), "Object is not a SimpleStreaming").ThrowAsJavaScriptException(); -void osn::SimpleRecording::SetStreaming(const Napi::CallbackInfo &info, const Napi::Value &value) -{ osn::SimpleStreaming *streaming = Napi::ObjectWrap::Unwrap(value.ToObject()); if (!streaming) { @@ -257,9 +287,10 @@ void osn::SimpleRecording::SetStreaming(const Napi::CallbackInfo &info, const Na return; } - auto conn = GetConnection(info); - if (!conn) - return; - conn->call(className, "SetStreaming", {ipc::value(this->uid), ipc::value(streaming->uid)}); + + if (!streamingRef.IsEmpty()) + streamingRef.Reset(); + + streamingRef = Napi::Persistent(obj); } \ No newline at end of file diff --git a/obs-studio-client/source/simple-recording.hpp b/obs-studio-client/source/simple-recording.hpp index f1913234a..7d6b28851 100644 --- a/obs-studio-client/source/simple-recording.hpp +++ b/obs-studio-client/source/simple-recording.hpp @@ -26,7 +26,8 @@ class SimpleRecording : public Napi::ObjectWrap, public os static Napi::FunctionReference constructor; static Napi::Object Init(Napi::Env env, Napi::Object exports); SimpleRecording(const Napi::CallbackInfo &info); - + void Finalize(Napi::Env); + void ReleaseObjects(); static Napi::Value Create(const Napi::CallbackInfo &info); static void Destroy(const Napi::CallbackInfo &info); diff --git a/obs-studio-client/source/simple-replay-buffer.cpp b/obs-studio-client/source/simple-replay-buffer.cpp index e238a6f9a..d6163f96e 100644 --- a/obs-studio-client/source/simple-replay-buffer.cpp +++ b/obs-studio-client/source/simple-replay-buffer.cpp @@ -77,6 +77,17 @@ osn::SimpleReplayBuffer::SimpleReplayBuffer(const Napi::CallbackInfo &info) : Na this->className = std::string("SimpleReplayBuffer"); } +void osn::SimpleReplayBuffer::Finalize(Napi::Env) +{ + ReleaseObjects(); +} + +void osn::SimpleReplayBuffer::ReleaseObjects() +{ + if (!parentOutputRef.IsEmpty()) + parentOutputRef.Reset(); +} + Napi::Value osn::SimpleReplayBuffer::Create(const Napi::CallbackInfo &info) { auto conn = GetConnection(info); @@ -102,6 +113,7 @@ void osn::SimpleReplayBuffer::Destroy(const Napi::CallbackInfo &info) replayBuffer->stopWorker(); replayBuffer->cb.Reset(); + replayBuffer->ReleaseObjects(); auto conn = GetConnection(info); if (!conn) @@ -150,62 +162,82 @@ void osn::SimpleReplayBuffer::SetLegacySettings(const Napi::CallbackInfo &info, Napi::Value osn::SimpleReplayBuffer::GetStreaming(const Napi::CallbackInfo &info) { - auto conn = GetConnection(info); - if (!conn) - return info.Env().Undefined(); - - std::vector response = conn->call_synchronous_helper(className, "GetStreaming", {ipc::value(this->uid)}); - - if (!ValidateResponse(info, response)) + if (usesStream) { + return parentOutputRef.IsEmpty() ? info.Env().Undefined() : parentOutputRef.Value(); + } else { return info.Env().Undefined(); - - auto instance = osn::SimpleStreaming::constructor.New({Napi::Number::New(info.Env(), static_cast(response[1].value_union.ui64))}); - return instance; + } } void osn::SimpleReplayBuffer::SetStreaming(const Napi::CallbackInfo &info, const Napi::Value &value) { - osn::SimpleStreaming *encoder = Napi::ObjectWrap::Unwrap(value.ToObject()); + auto conn = GetConnection(info); + if (!conn) + return; - if (!encoder) { - Napi::TypeError::New(info.Env(), "Invalid streaming argument").ThrowAsJavaScriptException(); + if (value.IsNull() || value.IsUndefined()) { + if (!parentOutputRef.IsEmpty()) + parentOutputRef.Reset(); + conn->call(className, "SetStreaming", {ipc::value(this->uid), ipc::value(UINT64_MAX)}); + usesStream = false; return; } - auto conn = GetConnection(info); - if (!conn) + Napi::Object obj = value.As(); + if (!obj.InstanceOf(osn::SimpleStreaming::constructor.Value())) + Napi::TypeError::New(info.Env(), "Object is not a SimpleStreaming").ThrowAsJavaScriptException(); + + osn::SimpleStreaming *streaming = Napi::ObjectWrap::Unwrap(value.ToObject()); + + if (!streaming) { + Napi::TypeError::New(info.Env(), "Invalid streaming argument").ThrowAsJavaScriptException(); return; + } + + conn->call(className, "SetStreaming", {ipc::value(this->uid), ipc::value(streaming->uid)}); + usesStream = true; + if (!parentOutputRef.IsEmpty()) + parentOutputRef.Reset(); - conn->call(className, "SetStreaming", {ipc::value(this->uid), ipc::value(encoder->uid)}); + parentOutputRef = Napi::Persistent(obj); } Napi::Value osn::SimpleReplayBuffer::GetRecording(const Napi::CallbackInfo &info) { - auto conn = GetConnection(info); - if (!conn) - return info.Env().Undefined(); - - std::vector response = conn->call_synchronous_helper(className, "GetRecording", {ipc::value(this->uid)}); - - if (!ValidateResponse(info, response)) + if (usesStream) { return info.Env().Undefined(); - - auto instance = osn::SimpleRecording::constructor.New({Napi::Number::New(info.Env(), static_cast(response[1].value_union.ui64))}); - return instance; + } else { + return parentOutputRef.IsEmpty() ? info.Env().Undefined() : parentOutputRef.Value(); + } } void osn::SimpleReplayBuffer::SetRecording(const Napi::CallbackInfo &info, const Napi::Value &value) { - osn::SimpleRecording *recording = Napi::ObjectWrap::Unwrap(value.ToObject()); + auto conn = GetConnection(info); + if (!conn) + return; - if (!recording) { - Napi::TypeError::New(info.Env(), "Invalid recording argument").ThrowAsJavaScriptException(); + if (value.IsNull() || value.IsUndefined()) { + if (!parentOutputRef.IsEmpty()) + parentOutputRef.Reset(); + conn->call(className, "SetRecording", {ipc::value(this->uid), ipc::value(UINT64_MAX)}); + usesStream = false; return; } - auto conn = GetConnection(info); - if (!conn) + Napi::Object obj = value.As(); + if (!obj.InstanceOf(osn::SimpleRecording::constructor.Value())) + Napi::TypeError::New(info.Env(), "Object is not a Valid Recording").ThrowAsJavaScriptException(); + + osn::SimpleRecording *recording = Napi::ObjectWrap::Unwrap(obj); + if (!recording) { + Napi::TypeError::New(info.Env(), "Invalid recording argument").ThrowAsJavaScriptException(); return; + } conn->call(className, "SetRecording", {ipc::value(this->uid), ipc::value(recording->uid)}); + usesStream = false; + if (!parentOutputRef.IsEmpty()) + parentOutputRef.Reset(); + parentOutputRef = Napi::Persistent(obj); } \ No newline at end of file diff --git a/obs-studio-client/source/simple-replay-buffer.hpp b/obs-studio-client/source/simple-replay-buffer.hpp index e1467d050..e8f8c364a 100644 --- a/obs-studio-client/source/simple-replay-buffer.hpp +++ b/obs-studio-client/source/simple-replay-buffer.hpp @@ -26,7 +26,8 @@ class SimpleReplayBuffer : public Napi::ObjectWrap, pub static Napi::FunctionReference constructor; static Napi::Object Init(Napi::Env env, Napi::Object exports); SimpleReplayBuffer(const Napi::CallbackInfo &info); - + void Finalize(Napi::Env); + void ReleaseObjects(); static Napi::Value Create(const Napi::CallbackInfo &info); static void Destroy(const Napi::CallbackInfo &info); diff --git a/obs-studio-client/source/simple-streaming.cpp b/obs-studio-client/source/simple-streaming.cpp index 8c3e56246..40a0f005d 100644 --- a/obs-studio-client/source/simple-streaming.cpp +++ b/obs-studio-client/source/simple-streaming.cpp @@ -35,7 +35,6 @@ Napi::Object osn::SimpleStreaming::Init(Napi::Env env, Napi::Object exports) StaticMethod("destroy", &osn::SimpleStreaming::Destroy), InstanceAccessor("videoEncoder", &osn::SimpleStreaming::GetVideoEncoder, &osn::SimpleStreaming::SetVideoEncoder), - InstanceAccessor("audioEncoder", &osn::SimpleStreaming::GetAudioEncoder, &osn::SimpleStreaming::SetAudioEncoder), InstanceAccessor("service", &osn::SimpleStreaming::GetService, &osn::SimpleStreaming::SetService), InstanceAccessor("enforceServiceBitrate", &osn::SimpleStreaming::GetEnforceServiceBirate, &osn::SimpleStreaming::SetEnforceServiceBirate), InstanceAccessor("enableTwitchVOD", &osn::SimpleStreaming::GetEnableTwitchVOD, &osn::SimpleStreaming::SetEnableTwitchVOD), @@ -79,6 +78,16 @@ osn::SimpleStreaming::SimpleStreaming(const Napi::CallbackInfo &info) : Napi::Ob this->className = std::string("SimpleStreaming"); } +void osn::SimpleStreaming::Finalize(Napi::Env) +{ + ReleaseObjects(); +} + +void osn::SimpleStreaming::ReleaseObjects() +{ + osn::Streaming::ReleaseObjects(); +} + Napi::Value osn::SimpleStreaming::Create(const Napi::CallbackInfo &info) { auto conn = GetConnection(info); @@ -105,6 +114,8 @@ void osn::SimpleStreaming::Destroy(const Napi::CallbackInfo &info) stream->stopWorker(); stream->cb.Reset(); + stream->ReleaseObjects(); + auto conn = GetConnection(info); if (!conn) return; @@ -117,17 +128,7 @@ void osn::SimpleStreaming::Destroy(const Napi::CallbackInfo &info) Napi::Value osn::SimpleStreaming::GetAudioEncoder(const Napi::CallbackInfo &info) { - auto conn = GetConnection(info); - if (!conn) - return info.Env().Undefined(); - - std::vector response = conn->call_synchronous_helper("SimpleStreaming", "GetAudioEncoder", {ipc::value(this->uid)}); - - if (!ValidateResponse(info, response)) - return info.Env().Undefined(); - - auto instance = osn::AudioEncoder::constructor.New({Napi::Number::New(info.Env(), static_cast(response[1].value_union.ui64))}); - return instance; + return audioEncoderRef.IsEmpty() ? info.Env().Undefined() : audioEncoderRef.Value(); } Napi::Value osn::SimpleStreaming::GetUseAdvanced(const Napi::CallbackInfo &info) @@ -178,6 +179,21 @@ void osn::SimpleStreaming::SetCustomEncSettings(const Napi::CallbackInfo &info, void osn::SimpleStreaming::SetAudioEncoder(const Napi::CallbackInfo &info, const Napi::Value &value) { + auto conn = GetConnection(info); + if (!conn) + return; + + if (value.IsNull() || value.IsUndefined()) { + if (!audioEncoderRef.IsEmpty()) + audioEncoderRef.Reset(); + conn->call(className, "SetAudioEncoder", {ipc::value(this->uid), ipc::value(UINT64_MAX)}); + return; + } + + Napi::Object obj = value.As(); + if (!obj.InstanceOf(osn::AudioEncoder::constructor.Value())) + Napi::TypeError::New(info.Env(), "Object is not a AudioEncoder").ThrowAsJavaScriptException(); + osn::AudioEncoder *encoder = Napi::ObjectWrap::Unwrap(value.ToObject()); if (!encoder) { @@ -185,11 +201,12 @@ void osn::SimpleStreaming::SetAudioEncoder(const Napi::CallbackInfo &info, const return; } - auto conn = GetConnection(info); - if (!conn) - return; - conn->call(className, "SetAudioEncoder", {ipc::value(this->uid), ipc::value(encoder->uid)}); + + if (!audioEncoderRef.IsEmpty()) + audioEncoderRef.Reset(); + + audioEncoderRef = Napi::Persistent(obj); } Napi::Value osn::SimpleStreaming::GetLegacySettings(const Napi::CallbackInfo &info) diff --git a/obs-studio-client/source/simple-streaming.hpp b/obs-studio-client/source/simple-streaming.hpp index 95edee1ab..78c8938a5 100644 --- a/obs-studio-client/source/simple-streaming.hpp +++ b/obs-studio-client/source/simple-streaming.hpp @@ -26,7 +26,8 @@ class SimpleStreaming : public Napi::ObjectWrap, public os static Napi::FunctionReference constructor; static Napi::Object Init(Napi::Env env, Napi::Object exports); SimpleStreaming(const Napi::CallbackInfo &info); - + void Finalize(Napi::Env); + void ReleaseObjects(); static Napi::Value Create(const Napi::CallbackInfo &info); static void Destroy(const Napi::CallbackInfo &info); diff --git a/obs-studio-client/source/streaming.cpp b/obs-studio-client/source/streaming.cpp index c51b906a6..fca86f56a 100644 --- a/obs-studio-client/source/streaming.cpp +++ b/obs-studio-client/source/streaming.cpp @@ -25,36 +25,61 @@ #include "network.hpp" #include "video.hpp" -Napi::Value osn::Streaming::GetService(const Napi::CallbackInfo &info) +void osn::Streaming::ReleaseObjects() { - auto conn = GetConnection(info); - if (!conn) - return info.Env().Undefined(); + if (!videoEncoderRef.IsEmpty()) + videoEncoderRef.Reset(); - std::vector response = conn->call_synchronous_helper(className, "GetService", {ipc::value(this->uid)}); + if (!serviceRef.IsEmpty()) + serviceRef.Reset(); - if (!ValidateResponse(info, response)) - return info.Env().Undefined(); + if (!delayRef.IsEmpty()) + delayRef.Reset(); - auto instance = osn::Service::constructor.New({Napi::Number::New(info.Env(), static_cast(response[1].value_union.ui64))}); + if (!reconnectRef.IsEmpty()) + reconnectRef.Reset(); - return instance; + if (!networkRef.IsEmpty()) + networkRef.Reset(); + + if (!audioEncoderRef.IsEmpty()) + audioEncoderRef.Reset(); +} + +Napi::Value osn::Streaming::GetService(const Napi::CallbackInfo &info) +{ + return serviceRef.IsEmpty() ? info.Env().Undefined() : serviceRef.Value(); } void osn::Streaming::SetService(const Napi::CallbackInfo &info, const Napi::Value &value) { - osn::Service *service = Napi::ObjectWrap::Unwrap(value.ToObject()); + auto conn = GetConnection(info); + if (!conn) + return; - if (!service) { - Napi::TypeError::New(info.Env(), "Invalid service argument").ThrowAsJavaScriptException(); + if (value.IsNull() || value.IsUndefined()) { + if (!serviceRef.IsEmpty()) + serviceRef.Reset(); + conn->call(className, "SetService", {ipc::value(this->uid), ipc::value(UINT64_MAX)}); return; } - auto conn = GetConnection(info); - if (!conn) + Napi::Object obj = value.As(); + if (!obj.InstanceOf(osn::Service::constructor.Value())) + Napi::TypeError::New(info.Env(), "Object is not a Service").ThrowAsJavaScriptException(); + + osn::Service *service = Napi::ObjectWrap::Unwrap(obj); + if (!service) { + Napi::TypeError::New(info.Env(), "Invalid service argument").ThrowAsJavaScriptException(); return; + } conn->call(className, "SetService", {ipc::value(this->uid), ipc::value(service->uid)}); + + if (!serviceRef.IsEmpty()) + serviceRef.Reset(); + + serviceRef = Napi::Persistent(obj); } Napi::Value osn::Streaming::GetCanvas(const Napi::CallbackInfo &info) @@ -88,24 +113,29 @@ void osn::Streaming::SetCanvas(const Napi::CallbackInfo &info, const Napi::Value conn->call(className, "SetVideoCanvas", {ipc::value(this->uid), ipc::value(canvas->canvasId)}); } + Napi::Value osn::Streaming::GetVideoEncoder(const Napi::CallbackInfo &info) +{ + return videoEncoderRef.IsEmpty() ? info.Env().Undefined() : videoEncoderRef.Value(); +} + +void osn::Streaming::SetVideoEncoder(const Napi::CallbackInfo &info, const Napi::Value &value) { auto conn = GetConnection(info); if (!conn) - return info.Env().Undefined(); - - std::vector response = conn->call_synchronous_helper(className, "GetVideoEncoder", {ipc::value(this->uid)}); + return; - if (!ValidateResponse(info, response)) - return info.Env().Undefined(); + if (value.IsNull() || value.IsUndefined()) { + if (!videoEncoderRef.IsEmpty()) + videoEncoderRef.Reset(); + conn->call(className, "SetVideoEncoder", {ipc::value(this->uid), ipc::value(UINT64_MAX)}); + return; + } - auto instance = osn::VideoEncoder::constructor.New({Napi::Number::New(info.Env(), static_cast(response[1].value_union.ui64))}); + Napi::Object obj = value.As(); + if (!obj.InstanceOf(osn::VideoEncoder::constructor.Value())) + Napi::TypeError::New(info.Env(), "Object is not a VideoEncoder").ThrowAsJavaScriptException(); - return instance; -} - -void osn::Streaming::SetVideoEncoder(const Napi::CallbackInfo &info, const Napi::Value &value) -{ osn::VideoEncoder *encoder = Napi::ObjectWrap::Unwrap(value.ToObject()); if (!encoder) { @@ -113,11 +143,12 @@ void osn::Streaming::SetVideoEncoder(const Napi::CallbackInfo &info, const Napi: return; } - auto conn = GetConnection(info); - if (!conn) - return; - conn->call(className, "SetVideoEncoder", {ipc::value(this->uid), ipc::value(encoder->uid)}); + + if (!videoEncoderRef.IsEmpty()) + videoEncoderRef.Reset(); + + videoEncoderRef = Napi::Persistent(obj); } Napi::Value osn::Streaming::GetEnforceServiceBirate(const Napi::CallbackInfo &info) @@ -168,98 +199,110 @@ void osn::Streaming::SetEnableTwitchVOD(const Napi::CallbackInfo &info, const Na Napi::Value osn::Streaming::GetDelay(const Napi::CallbackInfo &info) { - auto conn = GetConnection(info); - if (!conn) - return info.Env().Undefined(); - - std::vector response = conn->call_synchronous_helper(className, "GetDelay", {ipc::value(this->uid)}); - - if (!ValidateResponse(info, response)) - return info.Env().Undefined(); - - auto instance = osn::Delay::constructor.New({Napi::Number::New(info.Env(), static_cast(response[1].value_union.ui64))}); - - return instance; + return delayRef.IsEmpty() ? info.Env().Undefined() : delayRef.Value(); } void osn::Streaming::SetDelay(const Napi::CallbackInfo &info, const Napi::Value &value) { - osn::Delay *delay = Napi::ObjectWrap::Unwrap(value.ToObject()); + auto conn = GetConnection(info); + if (!conn) + return; - if (!delay) { - Napi::TypeError::New(info.Env(), "Invalid delay argument").ThrowAsJavaScriptException(); + if (value.IsNull() || value.IsUndefined()) { + if (!delayRef.IsEmpty()) + delayRef.Reset(); + conn->call(className, "SetDelay", {ipc::value(this->uid), ipc::value(UINT64_MAX)}); return; } - auto conn = GetConnection(info); - if (!conn) + Napi::Object obj = value.As(); + if (!obj.InstanceOf(osn::Delay::constructor.Value())) + Napi::TypeError::New(info.Env(), "Object is not a Delay").ThrowAsJavaScriptException(); + + osn::Delay *delay = Napi::ObjectWrap::Unwrap(obj); + if (!delay) { + Napi::TypeError::New(info.Env(), "Invalid delay argument").ThrowAsJavaScriptException(); return; + } conn->call(className, "SetDelay", {ipc::value(this->uid), ipc::value(delay->uid)}); + + if (!delayRef.IsEmpty()) + delayRef.Reset(); + + delayRef = Napi::Persistent(obj); } Napi::Value osn::Streaming::GetReconnect(const Napi::CallbackInfo &info) { - auto conn = GetConnection(info); - if (!conn) - return info.Env().Undefined(); - - std::vector response = conn->call_synchronous_helper(className, "GetReconnect", {ipc::value(this->uid)}); - - if (!ValidateResponse(info, response)) - return info.Env().Undefined(); - - auto instance = osn::Reconnect::constructor.New({Napi::Number::New(info.Env(), static_cast(response[1].value_union.ui64))}); - - return instance; + return reconnectRef.IsEmpty() ? info.Env().Undefined() : reconnectRef.Value(); } void osn::Streaming::SetReconnect(const Napi::CallbackInfo &info, const Napi::Value &value) { - osn::Reconnect *reconnect = Napi::ObjectWrap::Unwrap(value.ToObject()); + auto conn = GetConnection(info); + if (!conn) + return; - if (!reconnect) { - Napi::TypeError::New(info.Env(), "Invalid reconnect argument").ThrowAsJavaScriptException(); + if (value.IsNull() || value.IsUndefined()) { + if (!reconnectRef.IsEmpty()) + reconnectRef.Reset(); + conn->call(className, "SetReconnect", {ipc::value(this->uid), ipc::value(UINT64_MAX)}); return; } - auto conn = GetConnection(info); - if (!conn) + Napi::Object obj = value.As(); + if (!obj.InstanceOf(osn::Reconnect::constructor.Value())) + Napi::TypeError::New(info.Env(), "Object is not a Reconnect").ThrowAsJavaScriptException(); + + osn::Reconnect *reconnect = Napi::ObjectWrap::Unwrap(obj); + if (!reconnect) { + Napi::TypeError::New(info.Env(), "Invalid reconnect argument").ThrowAsJavaScriptException(); return; + } conn->call(className, "SetReconnect", {ipc::value(this->uid), ipc::value(reconnect->uid)}); + + if (!reconnectRef.IsEmpty()) + reconnectRef.Reset(); + + reconnectRef = Napi::Persistent(obj); } Napi::Value osn::Streaming::GetNetwork(const Napi::CallbackInfo &info) { - auto conn = GetConnection(info); - if (!conn) - return info.Env().Undefined(); - - std::vector response = conn->call_synchronous_helper(className, "GetNetwork", {ipc::value(this->uid)}); - - if (!ValidateResponse(info, response)) - return info.Env().Undefined(); - - auto instance = osn::Network::constructor.New({Napi::Number::New(info.Env(), static_cast(response[1].value_union.ui64))}); - - return instance; + return networkRef.IsEmpty() ? info.Env().Undefined() : networkRef.Value(); } void osn::Streaming::SetNetwork(const Napi::CallbackInfo &info, const Napi::Value &value) { - osn::Network *network = Napi::ObjectWrap::Unwrap(value.ToObject()); + auto conn = GetConnection(info); + if (!conn) + return; - if (!network) { - Napi::TypeError::New(info.Env(), "Invalid network argument").ThrowAsJavaScriptException(); + if (value.IsNull() || value.IsUndefined()) { + if (!networkRef.IsEmpty()) + networkRef.Reset(); + conn->call(className, "SetNetwork", {ipc::value(this->uid), ipc::value(UINT64_MAX)}); return; } - auto conn = GetConnection(info); - if (!conn) + Napi::Object obj = value.As(); + if (!obj.InstanceOf(osn::Network::constructor.Value())) + Napi::TypeError::New(info.Env(), "Object is not a Network").ThrowAsJavaScriptException(); + + osn::Network *network = Napi::ObjectWrap::Unwrap(obj); + if (!network) { + Napi::TypeError::New(info.Env(), "Invalid network argument").ThrowAsJavaScriptException(); return; + } conn->call(className, "SetNetwork", {ipc::value(this->uid), ipc::value(network->uid)}); + + if (!networkRef.IsEmpty()) + networkRef.Reset(); + + networkRef = Napi::Persistent(obj); } Napi::Value osn::Streaming::GetSignalHandler(const Napi::CallbackInfo &info) diff --git a/obs-studio-client/source/streaming.hpp b/obs-studio-client/source/streaming.hpp index bb5e6cee6..3e1fbe9be 100644 --- a/obs-studio-client/source/streaming.hpp +++ b/obs-studio-client/source/streaming.hpp @@ -30,6 +30,14 @@ class Streaming : public WorkerSignals { Napi::Function signalHandler; std::string className; + Napi::Reference videoEncoderRef; + Napi::Reference audioEncoderRef; + Napi::Reference serviceRef; + Napi::Reference delayRef; + Napi::Reference reconnectRef; + Napi::Reference networkRef; + void ReleaseObjects(); + Napi::Value GetService(const Napi::CallbackInfo &info); void SetService(const Napi::CallbackInfo &info, const Napi::Value &value); Napi::Value GetCanvas(const Napi::CallbackInfo &info); diff --git a/obs-studio-client/source/utility.hpp b/obs-studio-client/source/utility.hpp index ed856fce9..6f0040388 100644 --- a/obs-studio-client/source/utility.hpp +++ b/obs-studio-client/source/utility.hpp @@ -76,15 +76,15 @@ #define dstr(s) #s #define vstr(s) dstr(s) -static bool ValidateResponse(const Napi::CallbackInfo &info, std::vector &response) +static bool ValidateResponse(const Napi::Env &env, std::vector &response) { if (response.size() == 0) { - Napi::Error::New(info.Env(), "Failed to make IPC call, verify IPC status.").ThrowAsJavaScriptException(); + Napi::Error::New(env, "Failed to make IPC call, verify IPC status.").ThrowAsJavaScriptException(); return false; } if ((response.size() == 1) && (response[0].type == ipc::type::Null)) { - Napi::Error::New(info.Env(), response[0].value_str).ThrowAsJavaScriptException(); + Napi::Error::New(env, response[0].value_str).ThrowAsJavaScriptException(); return false; } @@ -94,40 +94,50 @@ static bool ValidateResponse(const Napi::CallbackInfo &info, std::vector GetConnection(const Napi::CallbackInfo &info) +static bool ValidateResponse(const Napi::CallbackInfo &info, std::vector &response) +{ + return ValidateResponse(info.Env(), response); +} + +static FORCE_INLINE std::shared_ptr GetConnection(const Napi::Env &env) { auto conn = Controller::GetInstance().GetConnection(); if (!conn) { - Napi::Error::New(info.Env(), "Failed to obtain IPC connection.").ThrowAsJavaScriptException(); + Napi::Error::New(env, "Failed to obtain IPC connection.").ThrowAsJavaScriptException(); exit(1); } return conn; } +static FORCE_INLINE std::shared_ptr GetConnection(const Napi::CallbackInfo &info) +{ + return GetConnection(info.Env()); +} + namespace utility { template inline std::string TypeOf(T v) { diff --git a/obs-studio-client/source/video-encoder.cpp b/obs-studio-client/source/video-encoder.cpp index 37a591fff..34c4e6a05 100644 --- a/obs-studio-client/source/video-encoder.cpp +++ b/obs-studio-client/source/video-encoder.cpp @@ -53,7 +53,8 @@ osn::VideoEncoder::VideoEncoder(const Napi::CallbackInfo &info) : Napi::ObjectWr Napi::Env env = info.Env(); Napi::HandleScope scope(env); size_t length = info.Length(); - this->uid = 0; + this->uid = UINT64_MAX; + this->encoderInitialized = false; if (length <= 0 || !info[0].IsNumber()) { Napi::TypeError::New(env, "Number expected").ThrowAsJavaScriptException(); @@ -61,6 +62,7 @@ osn::VideoEncoder::VideoEncoder(const Napi::CallbackInfo &info) : Napi::ObjectWr } this->uid = (uint64_t)info[0].ToNumber().Int64Value(); + this->encoderInitialized = true; } Napi::Value osn::VideoEncoder::Create(const Napi::CallbackInfo &info) @@ -92,6 +94,22 @@ Napi::Value osn::VideoEncoder::Create(const Napi::CallbackInfo &info) return instance; } +void osn::VideoEncoder::Finalize(Napi::Env env) +{ + if (!this->encoderInitialized) + return; + + auto conn = GetConnection(env); + if (!conn) + return; + + std::vector response = conn->call_synchronous_helper("VideoEncoder", "Finalize", {ipc::value(this->uid)}); + this->encoderInitialized = false; + this->uid = UINT64_MAX; + if (!ValidateResponse(env, response)) + return; +} + Napi::Value osn::VideoEncoder::GetTypes(const Napi::CallbackInfo &info) { auto conn = GetConnection(info); @@ -193,11 +211,18 @@ Napi::Value osn::VideoEncoder::GetLastError(const Napi::CallbackInfo &info) void osn::VideoEncoder::Release(const Napi::CallbackInfo &info) { + if (!this->encoderInitialized) + return; + auto conn = GetConnection(info); if (!conn) return; - conn->call("VideoEncoder", "Release", {}); + this->encoderInitialized = false; + std::vector response = conn->call_synchronous_helper("VideoEncoder", "Release", {ipc::value(this->uid)}); + this->uid = UINT64_MAX; + if (!ValidateResponse(info, response)) + return; } void osn::VideoEncoder::Update(const Napi::CallbackInfo &info) diff --git a/obs-studio-client/source/video-encoder.hpp b/obs-studio-client/source/video-encoder.hpp index 5a3f699ee..62c553474 100644 --- a/obs-studio-client/source/video-encoder.hpp +++ b/obs-studio-client/source/video-encoder.hpp @@ -24,6 +24,7 @@ namespace osn { class VideoEncoder : public Napi::ObjectWrap { public: uint64_t uid; + bool encoderInitialized; public: static Napi::FunctionReference constructor; @@ -31,6 +32,7 @@ class VideoEncoder : public Napi::ObjectWrap { VideoEncoder(const Napi::CallbackInfo &info); static Napi::Value Create(const Napi::CallbackInfo &info); + void Finalize(Napi::Env env); static Napi::Value GetTypes(const Napi::CallbackInfo &info); Napi::Value GetName(const Napi::CallbackInfo &info); diff --git a/obs-studio-server/source/osn-advanced-recording.cpp b/obs-studio-server/source/osn-advanced-recording.cpp index a4a949849..2b78325c8 100644 --- a/obs-studio-server/source/osn-advanced-recording.cpp +++ b/obs-studio-server/source/osn-advanced-recording.cpp @@ -440,15 +440,8 @@ void osn::IAdvancedRecording::SetLegacySettings(void *data, const int64_t id, co void osn::IAdvancedRecording::GetStreaming(void *data, const int64_t id, const std::vector &args, std::vector &rval) { - AdvancedRecording *recording = static_cast(osn::IFileOutput::Manager::GetInstance().find(args[0].value_union.ui64)); - if (!recording) { - PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Recording reference is not valid."); - } - - uint64_t uid = osn::IAdvancedStreaming::Manager::GetInstance().find(recording->streaming); - + blog(LOG_WARNING, "Function %s is deprecated", __func__); rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); - rval.push_back(ipc::value(uid)); AUTO_DEBUG; } @@ -459,6 +452,13 @@ void osn::IAdvancedRecording::SetStreaming(void *data, const int64_t id, const s PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Recording reference is not valid."); } + if (args[1].value_union.ui64 == UINT64_MAX) { + recording->streaming = nullptr; + rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); + AUTO_DEBUG; + return; + } + AdvancedStreaming *streaming = static_cast(osn::IAdvancedStreaming::Manager::GetInstance().find(args[1].value_union.ui64)); if (!streaming) { PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Streaming reference is not valid."); diff --git a/obs-studio-server/source/osn-advanced-replay-buffer.cpp b/obs-studio-server/source/osn-advanced-replay-buffer.cpp index 1d8a7ee5d..03054882d 100644 --- a/obs-studio-server/source/osn-advanced-replay-buffer.cpp +++ b/obs-studio-server/source/osn-advanced-replay-buffer.cpp @@ -267,15 +267,8 @@ void osn::IAdvancedReplayBuffer::SetLegacySettings(void *data, const int64_t id, void osn::IAdvancedReplayBuffer::GetStreaming(void *data, const int64_t id, const std::vector &args, std::vector &rval) { - AdvancedReplayBuffer *replayBuffer = static_cast(osn::IFileOutput::Manager::GetInstance().find(args[0].value_union.ui64)); - if (!replayBuffer) { - PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Advanced replay buffer reference is not valid."); - } - - uint64_t uid = osn::IAdvancedStreaming::Manager::GetInstance().find(replayBuffer->streaming); - + blog(LOG_WARNING, "Function %s is deprecated", __func__); rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); - rval.push_back(ipc::value(uid)); AUTO_DEBUG; } @@ -286,6 +279,13 @@ void osn::IAdvancedReplayBuffer::SetStreaming(void *data, const int64_t id, cons PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Advanced replay buffer reference is not valid."); } + if (args[1].value_union.ui64 == UINT64_MAX) { + replayBuffer->streaming = nullptr; + rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); + AUTO_DEBUG; + return; + } + AdvancedStreaming *streaming = static_cast(osn::IAdvancedStreaming::Manager::GetInstance().find(args[1].value_union.ui64)); if (!streaming) { PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Streaming reference is not valid."); @@ -299,15 +299,8 @@ void osn::IAdvancedReplayBuffer::SetStreaming(void *data, const int64_t id, cons void osn::IAdvancedReplayBuffer::GetRecording(void *data, const int64_t id, const std::vector &args, std::vector &rval) { - AdvancedReplayBuffer *replayBuffer = static_cast(osn::IFileOutput::Manager::GetInstance().find(args[0].value_union.ui64)); - if (!replayBuffer) { - PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Advanced replay buffer reference is not valid."); - } - - uint64_t uid = osn::IAdvancedRecording::Manager::GetInstance().find(replayBuffer->recording); - + blog(LOG_WARNING, "Function %s is deprecated", __func__); rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); - rval.push_back(ipc::value(uid)); AUTO_DEBUG; } @@ -318,6 +311,13 @@ void osn::IAdvancedReplayBuffer::SetRecording(void *data, const int64_t id, cons PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Advanced replay buffer reference is not valid."); } + if (args[1].value_union.ui64 == UINT64_MAX) { + replayBuffer->streaming = nullptr; + rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); + AUTO_DEBUG; + return; + } + AdvancedRecording *recording = static_cast(osn::IAdvancedRecording::Manager::GetInstance().find(args[1].value_union.ui64)); if (!recording) { PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Recording reference is not valid."); diff --git a/obs-studio-server/source/osn-audio-encoder.cpp b/obs-studio-server/source/osn-audio-encoder.cpp index 4ab7f211a..e49d41865 100644 --- a/obs-studio-server/source/osn-audio-encoder.cpp +++ b/obs-studio-server/source/osn-audio-encoder.cpp @@ -25,6 +25,9 @@ void osn::AudioEncoder::Register(ipc::server &srv) std::shared_ptr cls = std::make_shared("AudioEncoder"); cls->register_function( std::make_shared("Create", std::vector{ipc::type::String, ipc::type::String, ipc::type::String}, Create)); + + cls->register_function(std::make_shared("Release", std::vector{ipc::type::UInt64}, Release)); + cls->register_function(std::make_shared("Finalize", std::vector{ipc::type::UInt64}, Finalize)); cls->register_function(std::make_shared("GetName", std::vector{ipc::type::UInt64}, GetName)); cls->register_function(std::make_shared("SetName", std::vector{ipc::type::UInt64, ipc::type::String}, SetName)); cls->register_function(std::make_shared("GetBitrate", std::vector{ipc::type::UInt64}, GetBitrate)); @@ -35,7 +38,10 @@ void osn::AudioEncoder::Register(ipc::server &srv) void osn::AudioEncoder::Create(void *data, const int64_t id, const std::vector &args, std::vector &rval) { - obs_encoder_t *audioEncoder = obs_audio_encoder_create("ffmpeg_aac", "audio", nullptr, 0, nullptr); + std::string encoderId = args[0].value_str; + std::string name = args[1].value_str; + + obs_encoder_t *audioEncoder = obs_audio_encoder_create(encoderId.c_str(), name.c_str(), nullptr, 0, nullptr); if (!audioEncoder) { PRETTY_ERROR_RETURN(ErrorCode::Error, "Failed to create the audio encoder."); @@ -51,6 +57,36 @@ void osn::AudioEncoder::Create(void *data, const int64_t id, const std::vector &args, std::vector &rval) +{ + obs_encoder_t *encoder = osn::AudioEncoder::Manager::GetInstance().find(args[0].value_union.ui64); + blog(LOG_INFO, "Release encoder %p, %d", encoder, args[0].value_union.ui64); + if (!encoder) { + PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Release. Audio encoder reference is not valid."); + } + + obs_encoder_release(encoder); + osn::AudioEncoder::Manager::GetInstance().free(encoder); + + rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); + AUTO_DEBUG; +} + +void osn::AudioEncoder::Finalize(void *data, const int64_t id, const std::vector &args, std::vector &rval) +{ + obs_encoder_t *encoder = osn::AudioEncoder::Manager::GetInstance().find(args[0].value_union.ui64); + blog(LOG_INFO, "Finalize encoder %p, %d", encoder, args[0].value_union.ui64); + if (!encoder) { + PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Finalize. Audio encoder reference is not valid."); + } + + obs_encoder_release(encoder); + osn::AudioEncoder::Manager::GetInstance().free(encoder); + + rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); + AUTO_DEBUG; +} + void osn::AudioEncoder::GetName(void *data, const int64_t id, const std::vector &args, std::vector &rval) { obs_encoder_t *audioEncoder = osn::AudioEncoder::Manager::GetInstance().find(args[0].value_union.ui64); diff --git a/obs-studio-server/source/osn-audio-encoder.hpp b/obs-studio-server/source/osn-audio-encoder.hpp index 41bba0c03..a2d838b0a 100644 --- a/obs-studio-server/source/osn-audio-encoder.hpp +++ b/obs-studio-server/source/osn-audio-encoder.hpp @@ -46,5 +46,7 @@ class AudioEncoder { static void SetName(void *data, const int64_t id, const std::vector &args, std::vector &rval); static void GetBitrate(void *data, const int64_t id, const std::vector &args, std::vector &rval); static void SetBitrate(void *data, const int64_t id, const std::vector &args, std::vector &rval); + static void Release(void *data, const int64_t id, const std::vector &args, std::vector &rval); + static void Finalize(void *data, const int64_t id, const std::vector &args, std::vector &rval); }; } diff --git a/obs-studio-server/source/osn-delay.cpp b/obs-studio-server/source/osn-delay.cpp index 259c87e11..8bf3b2361 100644 --- a/obs-studio-server/source/osn-delay.cpp +++ b/obs-studio-server/source/osn-delay.cpp @@ -25,6 +25,7 @@ void osn::IDelay::Register(ipc::server &srv) std::shared_ptr cls = std::make_shared("Delay"); cls->register_function(std::make_shared("Create", std::vector{}, Create)); + cls->register_function(std::make_shared("Destroy", std::vector{}, Destroy)); cls->register_function(std::make_shared("GetEnabled", std::vector{ipc::type::UInt64}, GetEnabled)); cls->register_function(std::make_shared("SetEnabled", std::vector{ipc::type::UInt64, ipc::type::UInt32}, SetEnabled)); @@ -49,6 +50,20 @@ void osn::IDelay::Create(void *data, const int64_t id, const std::vector &args, std::vector &rval) +{ + uint64_t uid = args[0].value_union.ui64; + Delay *delay = osn::IDelay::Manager::GetInstance().find(uid); + if (!delay) { + PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Delay reference is not valid."); + } + + osn::IDelay::Manager::GetInstance().free(uid); + + rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); + AUTO_DEBUG; +} + void osn::IDelay::GetEnabled(void *data, const int64_t id, const std::vector &args, std::vector &rval) { Delay *delay = osn::IDelay::Manager::GetInstance().find(args[0].value_union.ui64); diff --git a/obs-studio-server/source/osn-delay.hpp b/obs-studio-server/source/osn-delay.hpp index a3515e39c..0a38c1730 100644 --- a/obs-studio-server/source/osn-delay.hpp +++ b/obs-studio-server/source/osn-delay.hpp @@ -58,6 +58,7 @@ class IDelay { static void Register(ipc::server &); static void Create(void *data, const int64_t id, const std::vector &args, std::vector &rval); + static void Destroy(void *data, const int64_t id, const std::vector &args, std::vector &rval); static void GetEnabled(void *data, const int64_t id, const std::vector &args, std::vector &rval); static void SetEnabled(void *data, const int64_t id, const std::vector &args, std::vector &rval); static void GetDelaySec(void *data, const int64_t id, const std::vector &args, std::vector &rval); diff --git a/obs-studio-server/source/osn-network.cpp b/obs-studio-server/source/osn-network.cpp index 6e4a335d4..426b598b4 100644 --- a/obs-studio-server/source/osn-network.cpp +++ b/obs-studio-server/source/osn-network.cpp @@ -24,6 +24,7 @@ void osn::INetwork::Register(ipc::server &srv) std::shared_ptr cls = std::make_shared("Network"); cls->register_function(std::make_shared("Create", std::vector{}, Create)); + cls->register_function(std::make_shared("Destroy", std::vector{}, Destroy)); cls->register_function(std::make_shared("GetBindIP", std::vector{ipc::type::UInt64}, GetBindIP)); cls->register_function(std::make_shared("SetBindIP", std::vector{ipc::type::UInt64, ipc::type::UInt32}, SetBindIP)); @@ -53,6 +54,20 @@ void osn::INetwork::Create(void *data, const int64_t id, const std::vector &args, std::vector &rval) +{ + uint64_t uid = args[0].value_union.ui64; + Network *network = osn::INetwork::Manager::GetInstance().find(uid); + if (!network) { + PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Network reference is not valid."); + } + + osn::INetwork::Manager::GetInstance().free(uid); + + rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); + AUTO_DEBUG; +} + void osn::INetwork::GetBindIP(void *data, const int64_t id, const std::vector &args, std::vector &rval) { Network *network = osn::INetwork::Manager::GetInstance().find(args[0].value_union.ui64); diff --git a/obs-studio-server/source/osn-network.hpp b/obs-studio-server/source/osn-network.hpp index 95281a5ff..82bd7c8d3 100644 --- a/obs-studio-server/source/osn-network.hpp +++ b/obs-studio-server/source/osn-network.hpp @@ -60,6 +60,7 @@ class INetwork { static void Register(ipc::server &); static void Create(void *data, const int64_t id, const std::vector &args, std::vector &rval); + static void Destroy(void *data, const int64_t id, const std::vector &args, std::vector &rval); static void GetBindIP(void *data, const int64_t id, const std::vector &args, std::vector &rval); static void SetBindIP(void *data, const int64_t id, const std::vector &args, std::vector &rval); static void GetNetworkInterfaces(void *data, const int64_t id, const std::vector &args, std::vector &rval); diff --git a/obs-studio-server/source/osn-reconnect.cpp b/obs-studio-server/source/osn-reconnect.cpp index 4b405e9e2..c3f5fe088 100644 --- a/obs-studio-server/source/osn-reconnect.cpp +++ b/obs-studio-server/source/osn-reconnect.cpp @@ -25,6 +25,7 @@ void osn::IReconnect::Register(ipc::server &srv) std::shared_ptr cls = std::make_shared("Reconnect"); cls->register_function(std::make_shared("Create", std::vector{}, Create)); + cls->register_function(std::make_shared("Destroy", std::vector{}, Destroy)); cls->register_function(std::make_shared("GetEnabled", std::vector{ipc::type::UInt64}, GetEnabled)); cls->register_function(std::make_shared("SetEnabled", std::vector{ipc::type::UInt64, ipc::type::UInt32}, SetEnabled)); @@ -48,6 +49,20 @@ void osn::IReconnect::Create(void *data, const int64_t id, const std::vector &args, std::vector &rval) +{ + uint64_t uid = args[0].value_union.ui64; + Reconnect *reconnect = osn::IReconnect::Manager::GetInstance().find(uid); + if (!reconnect) { + PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Reconnect reference is not valid."); + } + + osn::IReconnect::Manager::GetInstance().free(uid); + + rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); + AUTO_DEBUG; +} + void osn::IReconnect::GetEnabled(void *data, const int64_t id, const std::vector &args, std::vector &rval) { Reconnect *reconnect = osn::IReconnect::Manager::GetInstance().find(args[0].value_union.ui64); diff --git a/obs-studio-server/source/osn-reconnect.hpp b/obs-studio-server/source/osn-reconnect.hpp index f93507060..f6902d505 100644 --- a/obs-studio-server/source/osn-reconnect.hpp +++ b/obs-studio-server/source/osn-reconnect.hpp @@ -58,6 +58,7 @@ class IReconnect { static void Register(ipc::server &); static void Create(void *data, const int64_t id, const std::vector &args, std::vector &rval); + static void Destroy(void *data, const int64_t id, const std::vector &args, std::vector &rval); static void GetEnabled(void *data, const int64_t id, const std::vector &args, std::vector &rval); static void SetEnabled(void *data, const int64_t id, const std::vector &args, std::vector &rval); static void GetRetryDelay(void *data, const int64_t id, const std::vector &args, std::vector &rval); diff --git a/obs-studio-server/source/osn-recording.cpp b/obs-studio-server/source/osn-recording.cpp index 77da3582d..86ca53cc9 100644 --- a/obs-studio-server/source/osn-recording.cpp +++ b/obs-studio-server/source/osn-recording.cpp @@ -31,15 +31,8 @@ osn::Recording::~Recording() void osn::IRecording::GetVideoEncoder(void *data, const int64_t id, const std::vector &args, std::vector &rval) { - Recording *recording = static_cast(osn::IFileOutput::Manager::GetInstance().find(args[0].value_union.ui64)); - if (!recording) { - PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Simple recording reference is not valid."); - } - - uint64_t uid = osn::VideoEncoder::Manager::GetInstance().find(recording->videoEncoder); - + blog(LOG_WARNING, "Function %s is deprecated", __func__); rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); - rval.push_back(ipc::value(uid)); AUTO_DEBUG; } @@ -50,6 +43,13 @@ void osn::IRecording::SetVideoEncoder(void *data, const int64_t id, const std::v PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Simple recording reference is not valid."); } + if (args[1].value_union.ui64 == UINT64_MAX) { + recording->videoEncoder = nullptr; + rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); + AUTO_DEBUG; + return; + } + obs_encoder_t *encoder = osn::VideoEncoder::Manager::GetInstance().find(args[1].value_union.ui64); if (!encoder) { PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Encoder reference is not valid."); diff --git a/obs-studio-server/source/osn-simple-recording.cpp b/obs-studio-server/source/osn-simple-recording.cpp index 27addc6d3..c59a3e2c1 100644 --- a/obs-studio-server/source/osn-simple-recording.cpp +++ b/obs-studio-server/source/osn-simple-recording.cpp @@ -116,15 +116,8 @@ void osn::ISimpleRecording::SetQuality(void *data, const int64_t id, const std:: void osn::ISimpleRecording::GetAudioEncoder(void *data, const int64_t id, const std::vector &args, std::vector &rval) { - SimpleRecording *recording = static_cast(osn::IFileOutput::Manager::GetInstance().find(args[0].value_union.ui64)); - if (!recording) { - PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Recording reference is not valid."); - } - - uint64_t uid = osn::AudioEncoder::Manager::GetInstance().find(recording->audioEncoder); - + blog(LOG_WARNING, "Function %s is deprecated", __func__); rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); - rval.push_back(ipc::value(uid)); AUTO_DEBUG; } @@ -135,6 +128,13 @@ void osn::ISimpleRecording::SetAudioEncoder(void *data, const int64_t id, const PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Recording reference is not valid."); } + if (args[1].value_union.ui64 == UINT64_MAX) { + recording->audioEncoder = nullptr; + rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); + AUTO_DEBUG; + return; + } + obs_encoder_t *encoder = osn::AudioEncoder::Manager::GetInstance().find(args[1].value_union.ui64); if (!encoder) { PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Encoder reference is not valid."); @@ -551,15 +551,8 @@ void osn::ISimpleRecording::GetLegacySettings(void *data, const int64_t id, cons void osn::ISimpleRecording::GetStreaming(void *data, const int64_t id, const std::vector &args, std::vector &rval) { - SimpleRecording *recording = static_cast(osn::IFileOutput::Manager::GetInstance().find(args[0].value_union.ui64)); - if (!recording) { - PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Recording reference is not valid."); - } - - uint64_t uid = osn::ISimpleStreaming::Manager::GetInstance().find(recording->streaming); - + blog(LOG_WARNING, "Function %s is deprecated", __func__); rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); - rval.push_back(ipc::value(uid)); AUTO_DEBUG; } @@ -570,6 +563,13 @@ void osn::ISimpleRecording::SetStreaming(void *data, const int64_t id, const std PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Recording reference is not valid."); } + if (args[1].value_union.ui64 == UINT64_MAX) { + recording->streaming = nullptr; + rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); + AUTO_DEBUG; + return; + } + SimpleStreaming *streaming = static_cast(osn::ISimpleStreaming::Manager::GetInstance().find(args[1].value_union.ui64)); if (!streaming) { PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Streaming reference is not valid."); diff --git a/obs-studio-server/source/osn-simple-replay-buffer.cpp b/obs-studio-server/source/osn-simple-replay-buffer.cpp index 30af1d273..04a140100 100644 --- a/obs-studio-server/source/osn-simple-replay-buffer.cpp +++ b/obs-studio-server/source/osn-simple-replay-buffer.cpp @@ -235,15 +235,8 @@ void osn::ISimpleReplayBuffer::SetLegacySettings(void *data, const int64_t id, c void osn::ISimpleReplayBuffer::GetStreaming(void *data, const int64_t id, const std::vector &args, std::vector &rval) { - SimpleReplayBuffer *replayBuffer = static_cast(osn::IFileOutput::Manager::GetInstance().find(args[0].value_union.ui64)); - if (!replayBuffer) { - PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Simple replay buffer reference is not valid."); - } - - uint64_t uid = osn::ISimpleStreaming::Manager::GetInstance().find(replayBuffer->streaming); - + blog(LOG_WARNING, "Function %s is deprecated", __func__); rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); - rval.push_back(ipc::value(uid)); AUTO_DEBUG; } @@ -254,6 +247,13 @@ void osn::ISimpleReplayBuffer::SetStreaming(void *data, const int64_t id, const PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Simple replay buffer reference is not valid."); } + if (args[1].value_union.ui64 == UINT64_MAX) { + replayBuffer->streaming = nullptr; + rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); + AUTO_DEBUG; + return; + } + SimpleStreaming *streaming = static_cast(osn::ISimpleStreaming::Manager::GetInstance().find(args[1].value_union.ui64)); if (!streaming) { PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Streaming reference is not valid."); @@ -267,15 +267,8 @@ void osn::ISimpleReplayBuffer::SetStreaming(void *data, const int64_t id, const void osn::ISimpleReplayBuffer::GetRecording(void *data, const int64_t id, const std::vector &args, std::vector &rval) { - SimpleReplayBuffer *replayBuffer = static_cast(osn::IFileOutput::Manager::GetInstance().find(args[0].value_union.ui64)); - if (!replayBuffer) { - PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Simple replay buffer reference is not valid."); - } - - uint64_t uid = osn::ISimpleRecording::Manager::GetInstance().find(replayBuffer->recording); - + blog(LOG_WARNING, "Function %s is deprecated", __func__); rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); - rval.push_back(ipc::value(uid)); AUTO_DEBUG; } @@ -286,6 +279,13 @@ void osn::ISimpleReplayBuffer::SetRecording(void *data, const int64_t id, const PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Simple replay buffer reference is not valid."); } + if (args[1].value_union.ui64 == UINT64_MAX) { + replayBuffer->recording = nullptr; + rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); + AUTO_DEBUG; + return; + } + SimpleRecording *recording = static_cast(osn::ISimpleRecording::Manager::GetInstance().find(args[1].value_union.ui64)); if (!recording) { PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Recording reference is not valid."); diff --git a/obs-studio-server/source/osn-simple-streaming.cpp b/obs-studio-server/source/osn-simple-streaming.cpp index a50e3bbcc..c1bbe54e6 100644 --- a/obs-studio-server/source/osn-simple-streaming.cpp +++ b/obs-studio-server/source/osn-simple-streaming.cpp @@ -96,15 +96,8 @@ void osn::ISimpleStreaming::Destroy(void *data, const int64_t id, const std::vec void osn::ISimpleStreaming::GetAudioEncoder(void *data, const int64_t id, const std::vector &args, std::vector &rval) { - SimpleStreaming *streaming = static_cast(osn::ISimpleStreaming::Manager::GetInstance().find(args[0].value_union.ui64)); - if (!streaming) { - PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Streaming reference is not valid."); - } - - uint64_t uid = osn::AudioEncoder::Manager::GetInstance().find(streaming->audioEncoder); - + blog(LOG_WARNING, "Function %s is deprecated", __func__); rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); - rval.push_back(ipc::value(uid)); AUTO_DEBUG; } @@ -115,6 +108,13 @@ void osn::ISimpleStreaming::SetAudioEncoder(void *data, const int64_t id, const PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Streaming reference is not valid."); } + if (args[1].value_union.ui64 == UINT64_MAX) { + streaming->audioEncoder = nullptr; + rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); + AUTO_DEBUG; + return; + } + obs_encoder_t *encoder = osn::AudioEncoder::Manager::GetInstance().find(args[1].value_union.ui64); if (!encoder) { PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Encoder reference is not valid."); diff --git a/obs-studio-server/source/osn-streaming.cpp b/obs-studio-server/source/osn-streaming.cpp index f447c28c3..6ab829d6c 100644 --- a/obs-studio-server/source/osn-streaming.cpp +++ b/obs-studio-server/source/osn-streaming.cpp @@ -35,15 +35,8 @@ osn::Streaming::~Streaming() void osn::IStreaming::GetService(void *data, const int64_t id, const std::vector &args, std::vector &rval) { - Streaming *streaming = osn::IStreaming::Manager::GetInstance().find(args[0].value_union.ui64); - if (!streaming) { - PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Service reference is not valid."); - } - - uint64_t uid = osn::Service::Manager::GetInstance().find(streaming->service); - + blog(LOG_WARNING, "Function %s is deprecated", __func__); rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); - rval.push_back(ipc::value(uid)); AUTO_DEBUG; } @@ -54,6 +47,13 @@ void osn::IStreaming::SetService(void *data, const int64_t id, const std::vector PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Streaming reference is not valid."); } + if (args[1].value_union.ui64 == UINT64_MAX) { + streaming->service = nullptr; + rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); + AUTO_DEBUG; + return; + } + obs_service_t *service = osn::Service::Manager::GetInstance().find(args[1].value_union.ui64); if (!service) { PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Service reference is not valid."); @@ -101,15 +101,8 @@ void osn::IStreaming::SetVideoCanvas(void *data, const int64_t id, const std::ve void osn::IStreaming::GetVideoEncoder(void *data, const int64_t id, const std::vector &args, std::vector &rval) { - Streaming *streaming = osn::IStreaming::Manager::GetInstance().find(args[0].value_union.ui64); - if (!streaming) { - PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Streaming reference is not valid."); - } - - uint64_t uid = osn::VideoEncoder::Manager::GetInstance().find(streaming->videoEncoder); - + blog(LOG_WARNING, "Function %s is deprecated", __func__); rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); - rval.push_back(ipc::value(uid)); AUTO_DEBUG; } @@ -120,6 +113,13 @@ void osn::IStreaming::SetVideoEncoder(void *data, const int64_t id, const std::v PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Streaming reference is not valid."); } + if (args[1].value_union.ui64 == UINT64_MAX) { + streaming->videoEncoder = nullptr; + rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); + AUTO_DEBUG; + return; + } + obs_encoder_t *encoder = osn::VideoEncoder::Manager::GetInstance().find(args[1].value_union.ui64); if (!encoder) { PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Encoder reference is not valid."); @@ -183,15 +183,8 @@ void osn::IStreaming::SetEnableTwitchVOD(void *data, const int64_t id, const std void osn::IStreaming::GetDelay(void *data, const int64_t id, const std::vector &args, std::vector &rval) { - Streaming *streaming = osn::IStreaming::Manager::GetInstance().find(args[0].value_union.ui64); - if (!streaming) { - PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Streaming reference is not valid."); - } - - uint64_t uid = osn::IDelay::Manager::GetInstance().find(streaming->delay); - + blog(LOG_WARNING, "Function %s is deprecated", __func__); rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); - rval.push_back(ipc::value(uid)); AUTO_DEBUG; } @@ -202,6 +195,13 @@ void osn::IStreaming::SetDelay(void *data, const int64_t id, const std::vectordelay = nullptr; + rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); + AUTO_DEBUG; + return; + } + Delay *delay = osn::IDelay::Manager::GetInstance().find(args[1].value_union.ui64); if (!delay) { PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Encoder reference is not valid."); @@ -215,15 +215,8 @@ void osn::IStreaming::SetDelay(void *data, const int64_t id, const std::vector &args, std::vector &rval) { - Streaming *streaming = osn::IStreaming::Manager::GetInstance().find(args[0].value_union.ui64); - if (!streaming) { - PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Streaming reference is not valid."); - } - - uint64_t uid = osn::IReconnect::Manager::GetInstance().find(streaming->reconnect); - + blog(LOG_WARNING, "Function %s is deprecated", __func__); rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); - rval.push_back(ipc::value(uid)); AUTO_DEBUG; } @@ -234,6 +227,13 @@ void osn::IStreaming::SetReconnect(void *data, const int64_t id, const std::vect PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Streaming reference is not valid."); } + if (args[1].value_union.ui64 == UINT64_MAX) { + streaming->reconnect = nullptr; + rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); + AUTO_DEBUG; + return; + } + Reconnect *reconnect = osn::IReconnect::Manager::GetInstance().find(args[1].value_union.ui64); if (!reconnect) { PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Reconnect reference is not valid."); @@ -247,15 +247,8 @@ void osn::IStreaming::SetReconnect(void *data, const int64_t id, const std::vect void osn::IStreaming::GetNetwork(void *data, const int64_t id, const std::vector &args, std::vector &rval) { - Streaming *streaming = osn::IStreaming::Manager::GetInstance().find(args[0].value_union.ui64); - if (!streaming) { - PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Streaming reference is not valid."); - } - - uint64_t uid = osn::INetwork::Manager::GetInstance().find(streaming->network); - + blog(LOG_WARNING, "Function %s is deprecated", __func__); rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); - rval.push_back(ipc::value(uid)); AUTO_DEBUG; } @@ -266,6 +259,13 @@ void osn::IStreaming::SetNetwork(void *data, const int64_t id, const std::vector PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Streaming reference is not valid."); } + if (args[1].value_union.ui64 == UINT64_MAX) { + streaming->network = nullptr; + rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); + AUTO_DEBUG; + return; + } + Network *network = osn::INetwork::Manager::GetInstance().find(args[1].value_union.ui64); if (!network) { PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Network reference is not valid."); diff --git a/obs-studio-server/source/osn-video-encoder.cpp b/obs-studio-server/source/osn-video-encoder.cpp index 71a52e480..39b31a53b 100644 --- a/obs-studio-server/source/osn-video-encoder.cpp +++ b/obs-studio-server/source/osn-video-encoder.cpp @@ -33,6 +33,7 @@ void osn::VideoEncoder::Register(ipc::server &srv) cls->register_function(std::make_shared("GetId", std::vector{ipc::type::UInt64}, GetId)); cls->register_function(std::make_shared("GetLastError", std::vector{ipc::type::UInt64}, GetLastError)); cls->register_function(std::make_shared("Release", std::vector{ipc::type::UInt64}, Release)); + cls->register_function(std::make_shared("Finalize", std::vector{ipc::type::UInt64}, Finalize)); cls->register_function(std::make_shared("Update", std::vector{ipc::type::UInt64, ipc::type::String}, Update)); cls->register_function(std::make_shared("GetProperties", std::vector{ipc::type::UInt64}, GetProperties)); cls->register_function(std::make_shared("GetSettings", std::vector{ipc::type::UInt64}, GetSettings)); @@ -81,7 +82,7 @@ void osn::VideoEncoder::GetName(void *data, const int64_t id, const std::vector< { obs_encoder_t *encoder = osn::VideoEncoder::Manager::GetInstance().find(args[0].value_union.ui64); if (!encoder) { - PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Video encoder reference is not valid."); + PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "GetName. Video encoder reference is not valid."); } const char *name = obs_encoder_get_name(encoder); @@ -94,7 +95,7 @@ void osn::VideoEncoder::SetName(void *data, const int64_t id, const std::vector< { obs_encoder_t *encoder = osn::VideoEncoder::Manager::GetInstance().find(args[0].value_union.ui64); if (!encoder) { - PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Video encoder reference is not valid."); + PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "SetName. Video encoder reference is not valid."); } std::string name = args[1].value_str; @@ -107,7 +108,7 @@ void osn::VideoEncoder::GetType(void *data, const int64_t id, const std::vector< { obs_encoder_t *encoder = osn::VideoEncoder::Manager::GetInstance().find(args[0].value_union.ui64); if (!encoder) { - PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Video encoder reference is not valid."); + PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "GetType. Video encoder reference is not valid."); } uint32_t type = (uint32_t)obs_encoder_get_type(encoder); @@ -120,7 +121,7 @@ void osn::VideoEncoder::GetActive(void *data, const int64_t id, const std::vecto { obs_encoder_t *encoder = osn::VideoEncoder::Manager::GetInstance().find(args[0].value_union.ui64); if (!encoder) { - PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Video encoder reference is not valid."); + PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "GetActive. Video encoder reference is not valid."); } bool active = obs_encoder_active(encoder); @@ -133,7 +134,7 @@ void osn::VideoEncoder::GetId(void *data, const int64_t id, const std::vector &args, std::vector &rval) { obs_encoder_t *encoder = osn::VideoEncoder::Manager::GetInstance().find(args[0].value_union.ui64); + blog(LOG_INFO, "Release encoder %p, %d", encoder, args[0].value_union.ui64); if (!encoder) { - PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Video encoder reference is not valid."); + PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Release. Video encoder reference is not valid."); } obs_encoder_release(encoder); - encoder = nullptr; + osn::VideoEncoder::Manager::GetInstance().free(encoder); + + rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); + AUTO_DEBUG; +} + +void osn::VideoEncoder::Finalize(void *data, const int64_t id, const std::vector &args, std::vector &rval) +{ + obs_encoder_t *encoder = osn::VideoEncoder::Manager::GetInstance().find(args[0].value_union.ui64); + blog(LOG_INFO, "Finalize encoder %p, %d", encoder, args[0].value_union.ui64); + if (!encoder) { + PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Finalize. Video encoder reference is not valid."); + } + + obs_encoder_release(encoder); + osn::VideoEncoder::Manager::GetInstance().free(encoder); + rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); AUTO_DEBUG; } @@ -172,7 +190,7 @@ void osn::VideoEncoder::Update(void *data, const int64_t id, const std::vector &args, std::vector &rval); static void GetLastError(void *data, const int64_t id, const std::vector &args, std::vector &rval); static void Release(void *data, const int64_t id, const std::vector &args, std::vector &rval); + static void Finalize(void *data, const int64_t id, const std::vector &args, std::vector &rval); static void Update(void *data, const int64_t id, const std::vector &args, std::vector &rval); static void GetProperties(void *data, const int64_t id, const std::vector &args, std::vector &rval); static void GetSettings(void *data, const int64_t id, const std::vector &args, std::vector &rval); diff --git a/package.json b/package.json index f82886171..ec1bd4b9c 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "local:config": "yarn install && git submodule update --init --recursive --force && cmake -Bbuild -H. -G\"Visual Studio 16 2019\" -A\"x64\" -DCMAKE_INSTALL_PREFIX=\"./obs-studio-node\" -DLIBOBS_BUILD_TYPE=\"debug\" -DCMAKE_PREFIX_PATH=%CD%/build/libobs-src/cmake/", "local:build": "cmake --build build --target install --config Debug", "local:clean": "rm -rf build/*", - "test": "electron-mocha -t 80000 -c true -r ts-node/register tests/osn-tests/src/**/*.ts --reporter tests/osn-tests/util/list-reporter.js" + "test": "electron-mocha -t 80000 --js-flags=\"--expose-gc\" -c true -r ts-node/register tests/osn-tests/src/**/*.ts --reporter tests/osn-tests/util/list-reporter.js" }, "devDependencies": { "@types/chai": "^4.1.7", @@ -35,6 +35,7 @@ "colors": "^1.4.0", "electron": "29.4.3", "electron-mocha": "^12.1.0", + "get-func-name": "^3.0.0", "mocha": "^7.1.0", "mocha-junit-reporter": "^1.22.0", "node-addon-api": "^7.1.1", @@ -45,7 +46,6 @@ "typedoc-plugin-markdown": "^2.2.17", "typescript": "^4.0.7", "uuid": "^3.0.1", - "get-func-name": "^3.0.0", "wait-queue": "^1.1.4" }, "keywords": [ @@ -68,6 +68,5 @@ "audio", "mixer", "beam" - ], - "dependencies": {} + ] } diff --git a/tests/osn-tests/src/test_audio_encoder.ts b/tests/osn-tests/src/test_audio_encoder.ts index 1de67cdf0..4633c8250 100644 --- a/tests/osn-tests/src/test_audio_encoder.ts +++ b/tests/osn-tests/src/test_audio_encoder.ts @@ -43,9 +43,9 @@ describe(testName, () => { }); it('Create an audio encoder', () => { - const encoder = osn.AudioEncoderFactory.create(); + const encoder = osn.AudioEncoderFactory.create("ffmpeg_aac", "audio-encoder-test-1"); expect(encoder).to.not.equal(undefined, 'Invalid audio encoder creation'); - expect(encoder.name).to.equal('audio', "Invalid default name value"); + expect(encoder.name).to.equal('audio-encoder-test-1', "Invalid default name value"); expect(encoder.bitrate).to.equal(128, "Invalid default bitrate value"); encoder.name = 'audio track'; @@ -53,5 +53,7 @@ describe(testName, () => { expect(encoder.name).to.equal('audio track', "Invalid name value"); expect(encoder.bitrate).to.equal(256, "Invalid bitrate value"); + + encoder.release(); }); }); diff --git a/tests/osn-tests/src/test_osn_advanced_recording.ts b/tests/osn-tests/src/test_osn_advanced_recording.ts index 59a3a56f1..059848e9d 100644 --- a/tests/osn-tests/src/test_osn_advanced_recording.ts +++ b/tests/osn-tests/src/test_osn_advanced_recording.ts @@ -82,7 +82,7 @@ describe(testName, () => { recording.path = path.join(path.normalize(__dirname), '..', 'osnData'); recording.format = ERecordingFormat.MOV; recording.videoEncoder = - osn.VideoEncoderFactory.create('obs_x264', 'video-encoder'); + osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-adv-recording-1'); recording.overwrite = true; recording.noSpace = false; recording.video = obs.defaultVideoContext; @@ -111,7 +111,9 @@ describe(testName, () => { expect(recording.useStreamEncoders).to.equal( false, "Invalid useStreamEncoders default value"); + const videoEncoder = recording.videoEncoder; osn.AdvancedRecordingFactory.destroy(recording); + videoEncoder.release(); }); it('Start advanced recording - Stream', async function () { @@ -128,7 +130,7 @@ describe(testName, () => { recording.useStreamEncoders = true; const stream = osn.AdvancedStreamingFactory.create(); stream.videoEncoder = - osn.VideoEncoderFactory.create('obs_x264', 'video-encoder'); + osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-adv-streaming-1'); stream.service = osn.ServiceFactory.legacySettings; stream.video = obs.defaultVideoContext; stream.signalHandler = (signal) => {obs.signals.push(signal)}; @@ -243,8 +245,10 @@ describe(testName, () => { expect(signalInfo.signal).to.equal( EOBSOutputSignal.Deactivate, GetErrorMessage(ETestErrorMsg.StreamOutput)); + const videoEncoder = stream.videoEncoder; osn.AdvancedRecordingFactory.destroy(recording); osn.AdvancedStreamingFactory.destroy(stream); + videoEncoder.release(); }); it('Start advanced recording - Custom encoders', async function () { @@ -256,7 +260,7 @@ describe(testName, () => { recording.format = ERecordingFormat.MP4; recording.useStreamEncoders = false; recording.videoEncoder = - osn.VideoEncoderFactory.create('obs_x264', 'video-encoder'); + osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-adv-recording-2'); recording.overwrite = false; recording.noSpace = false; recording.video = obs.defaultVideoContext; @@ -316,6 +320,8 @@ describe(testName, () => { expect(signalInfo.signal).to.equal( EOBSOutputSignal.Wrote, GetErrorMessage(ETestErrorMsg.RecordingOutput)); + const videoEncoder = recording.videoEncoder; osn.AdvancedRecordingFactory.destroy(recording); + videoEncoder.release(); }); }); diff --git a/tests/osn-tests/src/test_osn_advanced_replayBuffer.ts b/tests/osn-tests/src/test_osn_advanced_replayBuffer.ts index a20737460..582cfba9a 100644 --- a/tests/osn-tests/src/test_osn_advanced_replayBuffer.ts +++ b/tests/osn-tests/src/test_osn_advanced_replayBuffer.ts @@ -131,7 +131,7 @@ describe(testName, () => { recording.useStreamEncoders = false; recording.video = obs.defaultVideoContext; recording.videoEncoder = - osn.VideoEncoderFactory.create('obs_x264', 'video-encoder'); + osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-adv-recording-1'); const track1 = osn.AudioTrackFactory.create(160, 'track1'); osn.AudioTrackFactory.setAtIndex(track1, 1); recording.overwrite = false; @@ -239,8 +239,10 @@ describe(testName, () => { expect(signalInfo.signal).to.equal( EOBSOutputSignal.Wrote, GetErrorMessage(ETestErrorMsg.RecordingOutput)); + const streamEncoder = recording.videoEncoder; osn.AdvancedReplayBufferFactory.destroy(replayBuffer); osn.AdvancedRecordingFactory.destroy(recording); + streamEncoder.release(); }); it('Start advanced replay buffer - Use Stream through Recording', async function() { @@ -264,14 +266,14 @@ describe(testName, () => { recording.useStreamEncoders = true; recording.overwrite = false; recording.noSpace = false; - recording .video = obs.defaultVideoContext; + recording.video = obs.defaultVideoContext; recording.useStreamEncoders = true; recording.signalHandler = (signal) => {obs.signals.push(signal)}; const stream = osn.AdvancedStreamingFactory.create(); stream.video = obs.defaultVideoContext; stream.videoEncoder = - osn.VideoEncoderFactory.create('obs_x264', 'video-encoder'); + osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-adv-stream-1'); stream.service = osn.ServiceFactory.legacySettings; const track1 = osn.AudioTrackFactory.create(160, 'track1'); osn.AudioTrackFactory.setAtIndex(track1, 1); @@ -433,8 +435,10 @@ describe(testName, () => { expect(signalInfo.signal).to.equal( EOBSOutputSignal.Deactivate, GetErrorMessage(ETestErrorMsg.StreamOutput)); + const videoEncoder = stream.videoEncoder; osn.AdvancedReplayBufferFactory.destroy(replayBuffer); osn.AdvancedRecordingFactory.destroy(recording); osn.AdvancedStreamingFactory.destroy(stream); + videoEncoder.release(); }); }); diff --git a/tests/osn-tests/src/test_osn_advanced_streaming.ts b/tests/osn-tests/src/test_osn_advanced_streaming.ts index 8c1ffea18..a9a931804 100644 --- a/tests/osn-tests/src/test_osn_advanced_streaming.ts +++ b/tests/osn-tests/src/test_osn_advanced_streaming.ts @@ -101,7 +101,7 @@ describe(testName, () => { } const stream = osn.AdvancedStreamingFactory.create(); stream.videoEncoder = - osn.VideoEncoderFactory.create('obs_x264', 'video-encoder'); + osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-adv-streaming-1'); stream.service = osn.ServiceFactory.legacySettings; stream.delay = osn.DelayFactory.create(); @@ -180,7 +180,9 @@ describe(testName, () => { expect(signalInfo.signal).to.equal( EOBSOutputSignal.Deactivate, GetErrorMessage(ETestErrorMsg.StreamOutput)); + const videoEncoder = stream.videoEncoder; osn.AdvancedStreamingFactory.destroy(stream); + videoEncoder.release(); }); it('Stream with invalid stream key', async function() { @@ -189,7 +191,7 @@ describe(testName, () => { } const stream = osn.AdvancedStreamingFactory.create(); stream.videoEncoder = - osn.VideoEncoderFactory.create('obs_x264', 'video-encoder'); + osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-adv-streaming-2'); stream.service = osn.ServiceFactory.legacySettings; stream.service.update({ key: 'invalid' }); stream.delay = @@ -222,6 +224,8 @@ describe(testName, () => { stream.service.update({ key: obs.userStreamKey }); + const streamingEncoder = stream.videoEncoder; osn.AdvancedStreamingFactory.destroy(stream); + streamingEncoder.release(); }); }); diff --git a/tests/osn-tests/src/test_osn_dual_output.ts b/tests/osn-tests/src/test_osn_dual_output.ts index a145b0b5d..108fd1c85 100644 --- a/tests/osn-tests/src/test_osn_dual_output.ts +++ b/tests/osn-tests/src/test_osn_dual_output.ts @@ -123,7 +123,7 @@ describe(testName, () => { recording.path = path.join(path.normalize(__dirname), '..', 'osnData'); recording.format = ERecordingFormat.MP4; recording.useStreamEncoders = false; - recording.videoEncoder = osn.VideoEncoderFactory.create('obs_x264', 'video-encoder'); + recording.videoEncoder = osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-test-recording-1'); recording.overwrite = false; recording.noSpace = false; recording.video = obs.defaultVideoContext; @@ -135,7 +135,7 @@ describe(testName, () => { recording2.path = path.join(path.normalize(__dirname), '..', 'osnData'); recording2.format = ERecordingFormat.MP4; recording2.useStreamEncoders = false; - recording2.videoEncoder = osn.VideoEncoderFactory.create('obs_x264', 'video-encoder'); + recording2.videoEncoder = osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-test-recording-2'); recording2.overwrite = false; recording2.noSpace = false; recording2.video = secondContext; @@ -161,9 +161,13 @@ describe(testName, () => { await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stop, ETestErrorMsg.RecordingOutput); await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Wrote, ETestErrorMsg.RecordingOutput); + const recordingEncoder = recording.videoEncoder; osn.AdvancedRecordingFactory.destroy(recording); + recordingEncoder.release(); + const recording2Encoder = recording2.videoEncoder; osn.AdvancedRecordingFactory.destroy(recording2); + recording2Encoder.release(); }); it('Start Dual Output with recording and scene items', async function() { @@ -176,7 +180,7 @@ describe(testName, () => { recording.path = path.join(path.normalize(__dirname), '..', 'osnData'); recording.format = ERecordingFormat.MP4; recording.useStreamEncoders = false; - recording.videoEncoder = osn.VideoEncoderFactory.create('obs_x264', 'video-encoder'); + recording.videoEncoder = osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-test-recording-3'); recording.overwrite = false; recording.noSpace = false; recording.video = obs.defaultVideoContext; @@ -214,7 +218,7 @@ describe(testName, () => { recording2.path = path.join(path.normalize(__dirname), '..', 'osnData'); recording2.format = ERecordingFormat.MP4; recording2.useStreamEncoders = false; - recording2.videoEncoder = osn.VideoEncoderFactory.create('obs_x264', 'video-encoder'); + recording2.videoEncoder = osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-test-recording-4'); recording2.overwrite = false; recording2.noSpace = false; recording2.video = secondContext; @@ -240,9 +244,13 @@ describe(testName, () => { await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stop, ETestErrorMsg.RecordingOutput); await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Wrote, ETestErrorMsg.RecordingOutput); + const recordingEncoder = recording.videoEncoder; osn.AdvancedRecordingFactory.destroy(recording); + recordingEncoder.release(); + const recording2Encoder = recording2.videoEncoder; osn.AdvancedRecordingFactory.destroy(recording2); + recording2Encoder.release(); osn.Global.setOutputSource(0, returnSource); @@ -262,7 +270,7 @@ describe(testName, () => { recording.path = path.join(path.normalize(__dirname), '..', 'osnData'); recording.format = ERecordingFormat.MP4; recording.useStreamEncoders = false; - recording.videoEncoder = osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-test-recording-1'); + recording.videoEncoder = osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-test-recording-5'); recording.overwrite = false; recording.noSpace = false; recording.mixer = 1; @@ -275,7 +283,7 @@ describe(testName, () => { recording2.path = path.join(path.normalize(__dirname), '..', 'osnData'); recording2.format = ERecordingFormat.MP4; recording2.useStreamEncoders = false; - recording2.videoEncoder = osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-test-recording-2'); + recording2.videoEncoder = osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-test-recording-6'); recording2.overwrite = false; recording2.noSpace = false; recording2.mixer = 2; @@ -335,12 +343,17 @@ describe(testName, () => { await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stop, ETestErrorMsg.RecordingOutput); await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Wrote, ETestErrorMsg.RecordingOutput); + const recordingEncoder = recording.videoEncoder; osn.AdvancedRecordingFactory.destroy(recording); + const recording2Encoder = recording2.videoEncoder; osn.AdvancedRecordingFactory.destroy(recording2); osn.Global.setOutputSource(0, returnSource); + recordingEncoder.release(); + recording2Encoder.release(); + sceneItem1.source.release(); sceneItem1.remove(); @@ -360,8 +373,8 @@ describe(testName, () => { const recording = osn.SimpleRecordingFactory.create(); recording.path = path.join(path.normalize(__dirname), '..', 'osnData'); recording.format = ERecordingFormat.MP4; - recording.videoEncoder = osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-test-recording-1'); - recording.audioEncoder = osn.AudioEncoderFactory.create(); + recording.videoEncoder = osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-test-recording-7'); + recording.audioEncoder = osn.AudioEncoderFactory.create("ffmpeg_aac", "audio-encoder-test-recording-1"); recording.audioEncoder.name = 'audio-encoder-test-recording-1'; recording.audioEncoder.bitrate = 160; recording.overwrite = false; @@ -375,8 +388,8 @@ describe(testName, () => { const recording2 = osn.SimpleRecordingFactory.create(); recording2.path = path.join(path.normalize(__dirname), '..', 'osnData'); recording2.format = ERecordingFormat.MP4; - recording2.videoEncoder = osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-test-recording-2'); - recording2.audioEncoder = osn.AudioEncoderFactory.create(); + recording2.videoEncoder = osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-test-recording-8'); + recording2.audioEncoder = osn.AudioEncoderFactory.create("ffmpeg_aac", "audio-encoder-test-recording-2"); recording2.audioEncoder.name = 'audio-encoder-test-recording-2'; recording2.audioEncoder.bitrate = 160; recording2.overwrite = false; @@ -438,8 +451,12 @@ describe(testName, () => { await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stop, ETestErrorMsg.RecordingOutput); await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Wrote, ETestErrorMsg.RecordingOutput); + const recordingEncoder = recording.videoEncoder; + const recordingAudioEncoder = recording.audioEncoder; osn.SimpleRecordingFactory.destroy(recording); + const recording2Encoder = recording2.videoEncoder; + const recording2AudioEncoder = recording2.audioEncoder; osn.SimpleRecordingFactory.destroy(recording2); osn.Global.setOutputSource(0, returnSource); @@ -451,6 +468,11 @@ describe(testName, () => { sceneItem2.remove(); scene.release(); + + recordingEncoder.release(); + recording2Encoder.release(); + recordingAudioEncoder.release(); + recording2AudioEncoder.release(); }); it('Start Dual Output with legacy streaming to two services', async function() { diff --git a/tests/osn-tests/src/test_osn_simple_recording.ts b/tests/osn-tests/src/test_osn_simple_recording.ts index d427d5641..3be49b7d1 100644 --- a/tests/osn-tests/src/test_osn_simple_recording.ts +++ b/tests/osn-tests/src/test_osn_simple_recording.ts @@ -79,9 +79,9 @@ describe(testName, () => { recording.quality = ERecordingQuality.HighQuality; recording.video = obs.defaultVideoContext; recording.videoEncoder = - osn.VideoEncoderFactory.create('obs_x264', 'video-encoder'); + osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-recording-1'); recording.lowCPU = true; - recording.audioEncoder = osn.AudioEncoderFactory.create(); + recording.audioEncoder = osn.AudioEncoderFactory.create("ffmpeg_aac", "audio-encoder-simple-recording-1"); recording.overwrite = true; recording.noSpace = false; @@ -98,7 +98,11 @@ describe(testName, () => { expect(recording.noSpace).to.equal( false, "Invalid noSpace value"); + const videoEncoder = recording.videoEncoder; + const audioEncoder = recording.audioEncoder; osn.SimpleRecordingFactory.destroy(recording); + videoEncoder.release(); + audioEncoder.release(); }); it('Start simple recording - Stream', async function () { @@ -118,9 +122,9 @@ describe(testName, () => { const stream = osn.SimpleStreamingFactory.create(); stream.video = obs.defaultVideoContext; stream.videoEncoder = - osn.VideoEncoderFactory.create('obs_x264', 'video-encoder'); + osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-stream-1'); stream.service = osn.ServiceFactory.legacySettings; - stream.audioEncoder = osn.AudioEncoderFactory.create(); + stream.audioEncoder = osn.AudioEncoderFactory.create("ffmpeg_aac", "audio-encoder-simple-streaming-1"); stream.signalHandler = (signal) => {obs.signals.push(signal)}; recording.streaming = stream; @@ -230,8 +234,12 @@ describe(testName, () => { expect(signalInfo.signal).to.equal( EOBSOutputSignal.Deactivate, GetErrorMessage(ETestErrorMsg.StreamOutput)); + const streamEncoder = stream.videoEncoder; + const audioEncoder = stream.audioEncoder; osn.SimpleRecordingFactory.destroy(recording); osn.SimpleStreamingFactory.destroy(stream); + streamEncoder.release(); + audioEncoder.release(); }); it('Start simple recording - HighQuality', async function () { @@ -244,9 +252,9 @@ describe(testName, () => { recording.quality = ERecordingQuality.HighQuality; recording.video = obs.defaultVideoContext; recording.videoEncoder = - osn.VideoEncoderFactory.create('obs_x264', 'video-encoder'); + osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-recording-2'); recording.lowCPU = false; - recording.audioEncoder = osn.AudioEncoderFactory.create(); + recording.audioEncoder = osn.AudioEncoderFactory.create("ffmpeg_aac", "audio-encoder-simple-recording-2"); recording.overwrite = false; recording.noSpace = false; recording.signalHandler = (signal) => {obs.signals.push(signal)}; @@ -303,7 +311,147 @@ describe(testName, () => { expect(signalInfo.signal).to.equal( EOBSOutputSignal.Wrote, GetErrorMessage(ETestErrorMsg.RecordingOutput)); + const videoEncoder = recording.videoEncoder; + const audioEncoder = recording.audioEncoder; osn.SimpleRecordingFactory.destroy(recording); + videoEncoder.release(); + audioEncoder.release(); + }); + + it('Start simple recording - mpegts', async function () { + if (obs.isDarwin()) { + this.skip(); + } + + const formats: ERecordingFormat[] = [ + ERecordingFormat.MP4, + ERecordingFormat.MOV, + ERecordingFormat.MKV, + ERecordingFormat.FLV, + ERecordingFormat.MPEGTS, + ERecordingFormat.HLS, + ]; + for (const format of formats) { + const recording = osn.SimpleRecordingFactory.create(); + + recording.path = path.join(path.normalize(__dirname), "..", "osnData"); + recording.format = format as ERecordingFormat; + recording.quality = ERecordingQuality.HighQuality; + recording.video = obs.defaultVideoContext; + recording.videoEncoder = osn.VideoEncoderFactory.create( + "obs_x264", + `video-encoder-recording-${format}` + ); + recording.lowCPU = false; + recording.audioEncoder = osn.AudioEncoderFactory.create( + "ffmpeg_aac", + `audio-encoder-simple-recording-${format}` + ); + recording.overwrite = false; + recording.noSpace = false; + recording.signalHandler = (signal) => obs.signals.push(signal); + + /* ---------- start ---------- */ + recording.start(); + + let signalInfo = await obs.getNextSignalInfo( + EOBSOutputType.Recording, + EOBSOutputSignal.Start + ); + + if (signalInfo.signal === EOBSOutputSignal.Stop) { + throw Error( + GetErrorMessage( + ETestErrorMsg.RecordOutputDidNotStart, + signalInfo.code.toString(), + signalInfo.error + ) + ); + } + + expect(signalInfo.type).to.equal( + EOBSOutputType.Recording, + GetErrorMessage(ETestErrorMsg.RecordingOutput) + ); + expect(signalInfo.signal).to.equal( + EOBSOutputSignal.Start, + GetErrorMessage(ETestErrorMsg.RecordingOutput) + ); + + await sleep(2500); + + /* ---------- stop ---------- */ + recording.stop(); + + signalInfo = await obs.getNextSignalInfo( + EOBSOutputType.Recording, + EOBSOutputSignal.Stopping + ); + + expect(signalInfo.type).to.equal( + EOBSOutputType.Recording, + GetErrorMessage(ETestErrorMsg.RecordingOutput) + ); + expect(signalInfo.signal).to.equal( + EOBSOutputSignal.Stopping, + GetErrorMessage(ETestErrorMsg.RecordingOutput) + ); + + signalInfo = await obs.getNextSignalInfo( + EOBSOutputType.Recording, + EOBSOutputSignal.Stop + ); + + if (signalInfo.code !== 0) { + throw Error( + GetErrorMessage( + ETestErrorMsg.RecordOutputStoppedWithError, + signalInfo.code.toString(), + signalInfo.error + ) + ); + } + + expect(signalInfo.type).to.equal( + EOBSOutputType.Recording, + GetErrorMessage(ETestErrorMsg.RecordingOutput) + ); + expect(signalInfo.signal).to.equal( + EOBSOutputSignal.Stop, + GetErrorMessage(ETestErrorMsg.RecordingOutput) + ); + + signalInfo = await obs.getNextSignalInfo( + EOBSOutputType.Recording, + EOBSOutputSignal.Wrote + ); + + if (signalInfo.code !== 0) { + throw Error( + GetErrorMessage( + ETestErrorMsg.RecordOutputStoppedWithError, + signalInfo.code.toString(), + signalInfo.error + ) + ); + } + + expect(signalInfo.type).to.equal( + EOBSOutputType.Recording, + GetErrorMessage(ETestErrorMsg.RecordingOutput) + ); + expect(signalInfo.signal).to.equal( + EOBSOutputSignal.Wrote, + GetErrorMessage(ETestErrorMsg.RecordingOutput) + ); + + // cleanup for this format + const videoEncoder = recording.videoEncoder; + const audioEncoder = recording.audioEncoder; + osn.SimpleRecordingFactory.destroy(recording); + videoEncoder.release(); + audioEncoder.release(); + } }); it('Start simple recording - HigherQuality', async function () { @@ -316,9 +464,9 @@ describe(testName, () => { recording.quality = ERecordingQuality.HigherQuality; recording.video = obs.defaultVideoContext; recording.videoEncoder = - osn.VideoEncoderFactory.create('obs_x264', 'video-encoder'); + osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-recording-3'); recording.lowCPU = false; - recording.audioEncoder = osn.AudioEncoderFactory.create(); + recording.audioEncoder = osn.AudioEncoderFactory.create("ffmpeg_aac", "audio-encoder-simple-recording-1"); recording.overwrite = false; recording.noSpace = false; recording.signalHandler = (signal) => {obs.signals.push(signal)}; @@ -375,7 +523,11 @@ describe(testName, () => { expect(signalInfo.signal).to.equal( EOBSOutputSignal.Wrote, GetErrorMessage(ETestErrorMsg.RecordingOutput)); + const videoEncoder = recording.videoEncoder; + const audioEncoder = recording.audioEncoder; osn.SimpleRecordingFactory.destroy(recording); + videoEncoder.release(); + audioEncoder.release(); }); it('Start simple recording - Lossless', async function () { @@ -471,8 +623,8 @@ describe(testName, () => { recording.format = ERecordingFormat.MP4; recording.quality = ERecordingQuality.HighQuality; recording.video = obs.defaultVideoContext; - recording.videoEncoder = osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-browser-rec', ); - recording.audioEncoder = osn.AudioEncoderFactory.create(); + recording.videoEncoder = osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-browser-rec'); + recording.audioEncoder = osn.AudioEncoderFactory.create('ffmpeg_aac', 'audio-encoder-browser-rec') recording.overwrite = true; recording.noSpace = false; recording.signalHandler = (sig) => obs.signals.push(sig); @@ -519,7 +671,12 @@ describe(testName, () => { } expect(sig.signal).to.equal(EOBSOutputSignal.Wrote,GetErrorMessage(ETestErrorMsg.RecordingOutput),); + const videoEncoder = recording.videoEncoder; + const audioEncoder = recording.audioEncoder; osn.SimpleRecordingFactory.destroy(recording); + videoEncoder.release(); + audioEncoder.release(); + browserInput.release(); sceneItem1.source.release(); sceneItem1.remove(); diff --git a/tests/osn-tests/src/test_osn_simple_replayBuffer.ts b/tests/osn-tests/src/test_osn_simple_replayBuffer.ts index 8cd527e89..58fe6daed 100644 --- a/tests/osn-tests/src/test_osn_simple_replayBuffer.ts +++ b/tests/osn-tests/src/test_osn_simple_replayBuffer.ts @@ -126,8 +126,8 @@ describe(testName, () => { recording.quality = osn.ERecordingQuality.HighQuality; recording.video = obs.defaultVideoContext; recording.videoEncoder = - osn.VideoEncoderFactory.create('obs_x264', 'video-encoder'); - recording.audioEncoder = osn.AudioEncoderFactory.create(); + osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-simple-recording-1'); + recording.audioEncoder = osn.AudioEncoderFactory.create("ffmpeg_aac", "audio-encoder-simple-recording-1"); recording.lowCPU = false; recording.overwrite = false; recording.noSpace = false; @@ -185,8 +185,12 @@ describe(testName, () => { expect(expectedPrefix).to.equal(true, 'Wrong prefix when saving the simple replay buffer'); expect(expectedSuffix).to.equal(true, 'Wrong suffix when saving the simple replay buffer'); + const videoEncoder = recording.videoEncoder; + const audioEncoder = recording.audioEncoder; osn.SimpleReplayBufferFactory.destroy(replayBuffer); osn.SimpleRecordingFactory.destroy(recording); + videoEncoder.release(); + audioEncoder.release(); }); it('Start simple replay buffer - Use Stream through Recording', async function() { @@ -217,9 +221,9 @@ describe(testName, () => { const stream = osn.SimpleStreamingFactory.create(); stream.video = obs.defaultVideoContext; stream.videoEncoder = - osn.VideoEncoderFactory.create('obs_x264', 'video-encoder'); + osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-simple-streaming-1'); stream.service = osn.ServiceFactory.legacySettings; - stream.audioEncoder = osn.AudioEncoderFactory.create(); + stream.audioEncoder = osn.AudioEncoderFactory.create("ffmpeg_aac", "audio-encoder-simple-streaming-1"); stream.signalHandler = (signal) => {obs.signals.push(signal)}; recording.streaming = stream; @@ -377,8 +381,12 @@ describe(testName, () => { expect(expectedPrefix).to.equal(true, 'Wrong prefix when saving the simple replay buffer'); expect(expectedSuffix).to.equal(true, 'Wrong suffix when saving the simple replay buffer'); + const videoEncoder = stream.videoEncoder; + const audioEncoder = stream.audioEncoder; osn.SimpleReplayBufferFactory.destroy(replayBuffer); osn.SimpleRecordingFactory.destroy(recording); osn.SimpleStreamingFactory.destroy(stream); + videoEncoder.release(); + audioEncoder.release(); }); }); diff --git a/tests/osn-tests/src/test_osn_simple_streaming.ts b/tests/osn-tests/src/test_osn_simple_streaming.ts index 97471ddd0..a1383b709 100644 --- a/tests/osn-tests/src/test_osn_simple_streaming.ts +++ b/tests/osn-tests/src/test_osn_simple_streaming.ts @@ -87,7 +87,7 @@ describe(testName, () => { } const stream = osn.SimpleStreamingFactory.create(); stream.videoEncoder = - osn.VideoEncoderFactory.create('obs_x264', 'video-encoder'); + osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-simple-streaming-1'); stream.service = osn.ServiceFactory.legacySettings; stream.delay = osn.DelayFactory.create(); @@ -96,7 +96,7 @@ describe(testName, () => { stream.network = osn.NetworkFactory.create(); stream.video = obs.defaultVideoContext; - stream.audioEncoder = osn.AudioEncoderFactory.create(); + stream.audioEncoder = osn.AudioEncoderFactory.create("ffmpeg_aac", "audio-encoder-simple-streaming-1"); stream.signalHandler = (signal) => {obs.signals.push(signal)}; stream.start(); @@ -164,7 +164,11 @@ describe(testName, () => { expect(signalInfo.signal).to.equal( EOBSOutputSignal.Deactivate, GetErrorMessage(ETestErrorMsg.StreamOutput)); + const streamEncoder = stream.videoEncoder; + const audioEncoder = stream.audioEncoder; osn.SimpleStreamingFactory.destroy(stream); + streamEncoder.release(); + audioEncoder.release(); }); it('Stream with invalid stream key', async function() { @@ -173,7 +177,7 @@ describe(testName, () => { } const stream = osn.SimpleStreamingFactory.create(); stream.videoEncoder = - osn.VideoEncoderFactory.create('obs_x264', 'video-encoder'); + osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-simple-streaming-2'); stream.service = osn.ServiceFactory.legacySettings; stream.service.update({ key: 'invalid' }); stream.delay = @@ -183,7 +187,7 @@ describe(testName, () => { stream.network = osn.NetworkFactory.create(); stream.video = obs.defaultVideoContext; - stream.audioEncoder = osn.AudioEncoderFactory.create(); + stream.audioEncoder = osn.AudioEncoderFactory.create("ffmpeg_aac", "audio-encoder-simple-streaming-2"); stream.signalHandler = (signal) => {obs.signals.push(signal)}; stream.start(); @@ -204,7 +208,11 @@ describe(testName, () => { expect(signalInfo.code).to.equal(-3, GetErrorMessage(ETestErrorMsg.StreamOutput)); stream.service.update({ key: obs.userStreamKey }); - + + const videoEncoder = stream.videoEncoder; + const audioEncoder = stream.audioEncoder; osn.SimpleStreamingFactory.destroy(stream); + videoEncoder.release(); + audioEncoder.release(); }); }); diff --git a/tests/osn-tests/src/test_osn_video_encoder.ts b/tests/osn-tests/src/test_osn_video_encoder.ts index 86976e44b..83aa1d9b8 100644 --- a/tests/osn-tests/src/test_osn_video_encoder.ts +++ b/tests/osn-tests/src/test_osn_video_encoder.ts @@ -47,7 +47,7 @@ describe(testName, () => { }); it('Create a video encoder', () => { - const encoder = osn.VideoEncoderFactory.create('obs_x264', "My x264", {}); + const encoder = osn.VideoEncoderFactory.create('obs_x264', "video-encoder-test-1", {}); expect(encoder).to.not.equal(undefined, 'Invalid x264 video encoder creation'); expect(encoder.active).to.equal(false, "Invalid active value"); expect(encoder.lastError).to.equal('', "Error while creating the video encoder"); @@ -96,10 +96,12 @@ describe(testName, () => { expect(propsArray[10].name).to.equal('repeat_headers', "Invalid repeat_headers name property"); expect(propsArray[10].value).to.equal(false, "Invalid repeat_headers value property"); + + encoder.release(); }); it('Update video encoder properties', () => { - const encoder = osn.VideoEncoderFactory.create('obs_x264', "My x264", {}); + const encoder = osn.VideoEncoderFactory.create('obs_x264', "video-encoder-test-2", {}); encoder.update({ rate_control: 'VBR', bitrate: 5000, @@ -157,5 +159,7 @@ describe(testName, () => { expect(propsArray[10].name).to.equal('repeat_headers', "Invalid repeat_headers name property"); expect(propsArray[10].value).to.equal(true, "Invalid repeat_headers value property"); + + encoder.release(); }); });