From d39796b8ecbdd7b5af782e6b9dc6800c80e19f53 Mon Sep 17 00:00:00 2001 From: Richard Osborne Date: Wed, 29 Apr 2026 10:42:41 -0500 Subject: [PATCH] Make tests less flaky MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * enable recording, streaming, dual-output tests on MacOS * set CI env var for MacOS github runner for obs_handler.ts * fix warning we cant assign null: `let secondContext: osn.IVideo = null;` * do not invoke `AddVideoContext` on CI builds in enhanced broadcasting tests since these are disabled. * main.yml: upgrade to macos-15 for arm64 ARCH (same OS Intel ARCH is using). * main.yml: upload OBS logs to troubleshoot unit tests. main.yml: set fail-fast to false * turn off fail-fast so the tests can continue running which will fix issue where if macos-15-intel fails, macos-arm64 is cancelled before it can finish running. osn_handler: log time delta for long duration * add warning when signal received took longer then expectedDeadline obs_handler: assign explicit log filename for tests Fix orphaned worker thread: The WorkerSignals::worker keeps polling Query. When a test fails before calling destroy(), the server-side recording gets freed when OBS shuts down, but the orphaned worker thread continues. When the next test file creates a fresh OBS connection, Controller::GetInstance().GetConnection() now returns the new session's connection. The orphaned worker sends Query(oldUID) to the new server, which has no knowledge of that uid → "Recording reference is not valid." Remove source message listener when test ends * stops worker thread properly * test_osn_simple_recording: remove lamda to enable timeout Tests: simple_recording,test_osn_advanced_replayBuffer,test_osn_advanced_recording,dual_output - wrap with try/finally * update any test that starts a worker thread via create() to properly invoke destroy() within a finally block to guanrantee the worker thread is stopped even if the test fails. Prevents orphaned worker threads from invoking Query() every 33ms on the new IPC connection created by other tests. guarantees cleanup even when tests throw so they tests will clean up proactively, and any orphaned workers that slip through will stop themselves rather than polluting subsequent test runs. validate totalSleepMS does not have a negative value When the IPC call takes longer than sleepIntervalMS on an overloaded CI runner), sleepIntervalMS - dur.count() produces a negative result that wraps to a huge size_t value, causing the worker to sleep for an effectively infinite duration. After that, no more signals are ever polled — any test waiting for a signal hits the 30-second timeout. do not share stdout/stderr with parent process On macOS, the server is spawned with posix_spawnp with NULL for file_actions, which means it inherits the parent's stdout/stderr. On Windows this doesn't happen because CreateProcessW uses DETACHED_PROCESS | CREATE_NO_WINDOW, which cuts the server off from the parent's console entirely. obs_handler: add more retryable timeouts for flaky tests advanced-recording fix * Removed extraneous set_video_mix since audio encoders don't have a video mix which produced the "encoder 'track1' is not a video encoder" warning * Reduce defaultVideoContext from 1280x720@60fps to 1280x720@30fps to reduce CPU load on the slow x86_64 CI machine. osn-replay-buffer: invoke output handler to save * makes test more reliable * follows similar pattern used by OBS::ReplayBufferSave --- .github/workflows/main.yml | 25 +- obs-studio-client/source/controller.cpp | 27 +- obs-studio-client/source/nodeobs_api.cpp | 6 +- .../source/nodeobs_autoconfig.cpp | 3 +- obs-studio-client/source/nodeobs_service.cpp | 3 +- obs-studio-client/source/worker-signals.hpp | 27 +- obs-studio-server/source/nodeobs_api.cpp | 23 +- .../source/osn-replay-buffer.cpp | 30 +- package.json | 2 +- .../osn-tests/src/test_nodeobs_autoconfig.ts | 4 - tests/osn-tests/src/test_nodeobs_service.ts | 37 - .../src/test_osn_advanced_recording.ts | 548 ++++++------- .../src/test_osn_advanced_replayBuffer.ts | 729 +++++++++--------- .../src/test_osn_advanced_streaming.ts | 15 - tests/osn-tests/src/test_osn_dual_output.ts | 674 ++++++++-------- ...nhanced_broadcasting_advanced_streaming.ts | 57 +- ..._enhanced_broadcasting_simple_streaming.ts | 50 +- tests/osn-tests/src/test_osn_input.ts | 3 - .../src/test_osn_simple_recording.ts | 661 ++++++++-------- .../src/test_osn_simple_replayBuffer.ts | 6 - .../src/test_osn_simple_streaming.ts | 162 ++-- tests/osn-tests/util/obs_handler.ts | 36 +- 22 files changed, 1589 insertions(+), 1539 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e803aa57d..516022f5c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -42,7 +42,7 @@ jobs: ReleaseName: release Architecture: x86_64 - BuildReleases: Release-arm64 - image: macos-14 + image: macos-15 BuildConfig: RelWithDebInfo ReleaseName: release Architecture: arm64 @@ -99,6 +99,7 @@ jobs: needs: build-macos runs-on: ${{ matrix.image }} strategy: + fail-fast: false # Don't cancel the other architecture's tests if one fails, so we can get test results for both. matrix: BuildReleases: [Release-x86_64, Release-arm64] include: @@ -108,7 +109,7 @@ jobs: ReleaseName: release Architecture: x86_64 - BuildReleases: Release-arm64 - image: macos-14 + image: macos-15 BuildConfig: RelWithDebInfo ReleaseName: release Architecture: arm64 @@ -151,6 +152,15 @@ jobs: OSN_ACCESS_KEY_ID: ${{secrets.AWS_RELEASE_ACCESS_KEY_ID}} OSN_SECRET_ACCESS_KEY: ${{secrets.AWS_RELEASE_SECRET_ACCESS_KEY}} RELEASE_NAME: ${{matrix.ReleaseName}} + CI: true + SUPPRESS_STREAMLABS_OBS_LOGS: true # Prevents logs from being printed in the test output, but still generates log files that can be uploaded as artifacts. + - name: Upload OBS logs + if: ${{ always() }} + uses: actions/upload-artifact@v4 + with: + name: obs-logs-mac-${{ matrix.Architecture }} + path: tests/osn-tests/osnData/slobs-client/node-obs/logs/ + if-no-files-found: ignore # Run even after test failures so the PR still gets the flaky summary. - name: Publish flaky test check if: ${{ always() }} @@ -181,7 +191,7 @@ jobs: ReleaseName: release Architecture: x86_64 - BuildReleases: Release-arm64 - image: macos-14 + image: macos-15 BuildConfig: RelWithDebInfo ReleaseName: release Architecture: arm64 @@ -234,7 +244,7 @@ jobs: ReleaseName: release Architecture: x86_64 - BuildReleases: Release-arm64 - image: macos-14 + image: macos-15 BuildConfig: RelWithDebInfo ReleaseName: release Architecture: arm64 @@ -360,6 +370,13 @@ jobs: OSN_ACCESS_KEY_ID: ${{secrets.AWS_RELEASE_ACCESS_KEY_ID}} OSN_SECRET_ACCESS_KEY: ${{secrets.AWS_RELEASE_SECRET_ACCESS_KEY}} RELEASE_NAME: release + - name: Upload OBS logs + if: ${{ always() }} + uses: actions/upload-artifact@v4 + with: + name: obs-logs-windows + path: tests/osn-tests/osnData/slobs-client/node-obs/logs/ + if-no-files-found: ignore # Run even after test failures so the PR still gets the flaky summary. - name: Publish flaky test check if: ${{ always() }} diff --git a/obs-studio-client/source/controller.cpp b/obs-studio-client/source/controller.cpp index 3d78408c1..7d9fb74f6 100644 --- a/obs-studio-client/source/controller.cpp +++ b/obs-studio-client/source/controller.cpp @@ -49,10 +49,16 @@ std::wstring utfWorkingDir = L""; #include #include #else +#include +#include #include #include #include #include +#include +#include +#include +#include extern char **environ; #endif @@ -301,8 +307,9 @@ std::shared_ptr Controller::host(const std::string &uri) int st = proc_pidinfo(pids[i], PROC_PIDTBSDINFO, 0, &proc, PROC_PIDTBSDINFO_SIZE); if (st == PROC_PIDTBSDINFO_SIZE) { if (strcmp("obs64", proc.pbi_name) == 0) { - if (pids[i] != 0) - kill(pids[i], SIGKILL); + if (pids[i] != 0 && kill(pids[i], SIGKILL) != 0) { + std::cout << "Warning: could not kill orphaned/former obs64 process" << std::endl; + } } } } @@ -310,7 +317,21 @@ std::shared_ptr Controller::host(const std::string &uri) pid_t pid; std::vector argv = {"obs64", uri.c_str(), version.c_str(), serverBinaryPath.c_str(), nullptr}; - int ret = posix_spawnp(&pid, serverBinaryPath.c_str(), NULL, NULL, const_cast(argv.data()), environ); + const char *suppressLogsEnv = std::getenv("SUPPRESS_STREAMLABS_OBS_LOGS"); + int ret = 0; + if (suppressLogsEnv == nullptr || strcasecmp(suppressLogsEnv, "false") == 0) { + // For development, it can be helpful for process to share stdout/stderr. + ret = posix_spawnp(&pid, serverBinaryPath.c_str(), NULL, NULL, const_cast(argv.data()), environ); + } else { + // Do not send the logs to stdout/stderr. + posix_spawn_file_actions_t file_actions; + posix_spawn_file_actions_init(&file_actions); + posix_spawn_file_actions_addopen(&file_actions, STDOUT_FILENO, "/dev/null", O_WRONLY, 0); + posix_spawn_file_actions_addopen(&file_actions, STDERR_FILENO, "/dev/null", O_WRONLY, 0); + ret = posix_spawnp(&pid, serverBinaryPath.c_str(), &file_actions, NULL, const_cast(argv.data()), environ); + posix_spawn_file_actions_destroy(&file_actions); + } + if (ret != 0) { std::cerr << "Could not spawn the server at " << serverBinaryPath.c_str() << " with error code: " << ret << std::endl; return nullptr; diff --git a/obs-studio-client/source/nodeobs_api.cpp b/obs-studio-client/source/nodeobs_api.cpp index aa667140b..db9a0c40f 100644 --- a/obs-studio-client/source/nodeobs_api.cpp +++ b/obs-studio-client/source/nodeobs_api.cpp @@ -17,6 +17,7 @@ ******************************************************************************/ #include "controller.hpp" +#include #include "osn-error.hpp" #include "nodeobs_api.hpp" #include @@ -36,12 +37,15 @@ Napi::Value api::OBS_API_initAPI(const Napi::CallbackInfo &info) std::string language; std::string version; std::string crashserverurl; + std::string logFilename; ASSERT_GET_VALUE(info, info[0], language); ASSERT_GET_VALUE(info, info[1], path); ASSERT_GET_VALUE(info, info[2], version); if (info.Length() > 3) ASSERT_GET_VALUE(info, info[3], crashserverurl); + if (info.Length() > 4) + ASSERT_GET_VALUE(info, info[4], logFilename); auto conn = GetConnection(info); if (!conn) @@ -50,7 +54,7 @@ Napi::Value api::OBS_API_initAPI(const Napi::CallbackInfo &info) conn->set_freeze_callback(ipc_freeze_callback, path); std::vector response = conn->call_synchronous_helper( - "API", "OBS_API_initAPI", {ipc::value(path), ipc::value(language), ipc::value(version), ipc::value(crashserverurl)}); + "API", "OBS_API_initAPI", {ipc::value(path), ipc::value(language), ipc::value(version), ipc::value(crashserverurl), ipc::value(logFilename)}); // The API init method will return a response error + graphical error // If there is a problem with the IPC the number of responses here will be zero so we must validate the diff --git a/obs-studio-client/source/nodeobs_autoconfig.cpp b/obs-studio-client/source/nodeobs_autoconfig.cpp index 7652c5b57..a83b54f24 100644 --- a/obs-studio-client/source/nodeobs_autoconfig.cpp +++ b/obs-studio-client/source/nodeobs_autoconfig.cpp @@ -66,7 +66,8 @@ void autoConfig::worker() do_sleep: auto tp_end = std::chrono::high_resolution_clock::now(); auto dur = std::chrono::duration_cast(tp_end - tp_start); - totalSleepMS = sleepIntervalMS - dur.count(); + auto durCount = dur.count(); + totalSleepMS = durCount < sleepIntervalMS ? sleepIntervalMS - durCount : 0; std::this_thread::sleep_for(std::chrono::milliseconds(totalSleepMS)); } return; diff --git a/obs-studio-client/source/nodeobs_service.cpp b/obs-studio-client/source/nodeobs_service.cpp index 874b6df71..7fd1c9347 100644 --- a/obs-studio-client/source/nodeobs_service.cpp +++ b/obs-studio-client/source/nodeobs_service.cpp @@ -348,7 +348,8 @@ void service::worker() auto tp_end = std::chrono::high_resolution_clock::now(); auto dur = std::chrono::duration_cast(tp_end - tp_start); - totalSleepMS = sleepIntervalMS - dur.count(); + auto durCount = dur.count(); + totalSleepMS = durCount < sleepIntervalMS ? sleepIntervalMS - durCount : 0; std::this_thread::sleep_for(std::chrono::milliseconds(totalSleepMS)); } diff --git a/obs-studio-client/source/worker-signals.hpp b/obs-studio-client/source/worker-signals.hpp index 7ce2d9290..cd0e6f908 100644 --- a/obs-studio-client/source/worker-signals.hpp +++ b/obs-studio-client/source/worker-signals.hpp @@ -17,6 +17,8 @@ ******************************************************************************/ #pragma once +#include +#include #include #include "osn-error.hpp" #include "utility.hpp" @@ -42,8 +44,9 @@ class WorkerSignals { ~WorkerSignals(){}; protected: - bool isWorkerRunning; - bool workerStop; + std::atomic isWorkerRunning; + std::atomic workerStop; + std::atomic isOrphaned; uint32_t sleepIntervalMS; std::thread *workerThread; Napi::ThreadSafeFunction jsThread; @@ -51,9 +54,11 @@ class WorkerSignals { void startWorker(napi_env env, Napi::Function asyncCallback, const std::string &name, const uint64_t &refID) { - if (!workerStop || isWorkerRunning) + // If worker has been orphaned; allow it to be rejoined + if (!isOrphaned && (!workerStop || isWorkerRunning)) return; + isOrphaned = false; isWorkerRunning = true; workerStop = false; jsThread = Napi::ThreadSafeFunction::New(env, asyncCallback, name.c_str(), 0, 1, [](Napi::Env) {}); @@ -88,6 +93,17 @@ class WorkerSignals { auto conn = Controller::GetInstance().GetConnection(); if (conn) { std::vector response = conn->call_synchronous_helper(name, "Query", {ipc::value(refID)}); + if (!response.empty()) { + ErrorCode firstError = (ErrorCode)response[0].value_union.ui64; + if (firstError == ErrorCode::InvalidReference) { + // This typically happens if the worker thread is orphaned. + std::string errorMessage = response.size() > 1 ? response[1].value_str : ""; + std::cout << "Worker thread exiting due to Invalid reference error encountered: " << errorMessage << std::endl; + isWorkerRunning = false; + isOrphaned = true; + break; + } + } if ((response.size() == 5) && signalsList.size() < maximum_signals_in_queue) { ErrorCode error = (ErrorCode)response[0].value_union.ui64; if (error == ErrorCode::Ok) { @@ -122,7 +138,8 @@ class WorkerSignals { auto tp_end = std::chrono::high_resolution_clock::now(); auto dur = std::chrono::duration_cast(tp_end - tp_start); - totalSleepMS = sleepIntervalMS - dur.count(); + auto durCount = dur.count(); + totalSleepMS = durCount < sleepIntervalMS ? sleepIntervalMS - durCount : 0; std::this_thread::sleep_for(std::chrono::milliseconds(totalSleepMS)); } @@ -140,4 +157,4 @@ class WorkerSignals { workerThread->join(); } } -}; \ No newline at end of file +}; diff --git a/obs-studio-server/source/nodeobs_api.cpp b/obs-studio-server/source/nodeobs_api.cpp index b1d4eeae3..ef028a2a5 100644 --- a/obs-studio-server/source/nodeobs_api.cpp +++ b/obs-studio-server/source/nodeobs_api.cpp @@ -835,6 +835,17 @@ void addModulePaths() #endif } +std::filesystem::path sanitize_path(const std::filesystem::path &input) +{ + std::filesystem::path normalized = input.lexically_normal(); + + if (normalized.is_absolute() || normalized.string().find("..") != std::string::npos) { + return {}; + } + + return normalized; +} + static void listEncoders(obs_encoder_type type) { constexpr uint32_t hide_flags = OBS_ENCODER_CAP_DEPRECATED | OBS_ENCODER_CAP_INTERNAL; @@ -874,10 +885,20 @@ void OBS_API::OBS_API_initAPI(void *data, const int64_t id, const std::vector 4) { + std::string logname = sanitize_path(args[4].value_str).string(); + if (logname.size() > 0) { + std::ostringstream ss; + ss << logname << '-' << GenerateTimeDateFilename("txt"); + logFilename = ss.str(); + } + } utility::osn_current_version(currentVersion); /* Logging */ - std::string filename = GenerateTimeDateFilename("txt"); + std::string filename = logFilename.size() > 0 ? logFilename : GenerateTimeDateFilename("txt"); std::string log_path = appdata; log_path.append("/node-obs/logs/"); diff --git a/obs-studio-server/source/osn-replay-buffer.cpp b/obs-studio-server/source/osn-replay-buffer.cpp index 6e19198a2..7e60a4b64 100644 --- a/obs-studio-server/source/osn-replay-buffer.cpp +++ b/obs-studio-server/source/osn-replay-buffer.cpp @@ -152,19 +152,23 @@ void osn::IReplayBuffer::Query(void *data, const int64_t id, const std::vector &args, std::vector &rval) { - obs_enum_hotkeys( - [](void *data, obs_hotkey_id id, obs_hotkey_t *key) { - if (obs_hotkey_get_registerer_type(key) == OBS_HOTKEY_REGISTERER_OUTPUT) { - std::string key_name = obs_hotkey_get_name(key); - if (key_name.compare("ReplayBuffer.Save") == 0) { - obs_hotkey_enable_callback_rerouting(true); - obs_hotkey_trigger_routed_callback(id, true); - } - } - return true; - }, - nullptr); + ReplayBuffer *replayBuffer = static_cast(osn::IFileOutput::Manager::GetInstance().find(args.at(0).value_union.ui64)); + if (!replayBuffer) { + PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "ReplayBuffer reference is not valid."); + } + obs_output_t *output = replayBuffer->GetOutput(); + if (!output) { + PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Invalid replay buffer output."); + } + + calldata_t cd = {0}; + proc_handler_t *ph = obs_output_get_proc_handler(output); + bool hasInvoked = proc_handler_call(ph, "save", &cd); + calldata_free(&cd); + + if (!hasInvoked) + PRETTY_ERROR_RETURN(ErrorCode::NotFound, "Could not find ReplayBuffer::Save"); rval.push_back(ipc::value((uint64_t)ErrorCode::Ok)); AUTO_DEBUG; -} \ No newline at end of file +} diff --git a/package.json b/package.json index 7dcdfd9eb..1be419f56 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "local:build": "cmake --build build --target install --config Debug", "local:clean": "rm -rf build/*", "test": "electron-mocha -t 80000 --js-flags=\"--expose-gc\" --color -r ts-node/register tests/osn-tests/src/**/*.ts --reporter tests/osn-tests/util/list-reporter.js", - "test:ci": "yarn run test --retries 2" + "test:ci": "yarn run test --retries 3" }, "devDependencies": { "@aws-sdk/client-s3": "^3.0.0", diff --git a/tests/osn-tests/src/test_nodeobs_autoconfig.ts b/tests/osn-tests/src/test_nodeobs_autoconfig.ts index bbcf52c99..b506a59e9 100644 --- a/tests/osn-tests/src/test_nodeobs_autoconfig.ts +++ b/tests/osn-tests/src/test_nodeobs_autoconfig.ts @@ -9,7 +9,6 @@ import { deleteConfigFiles } from '../util/general'; const testName = 'nodeobs_autoconfig'; describe(testName, function() { - this.timeout(30000) let obs: OBSHandler; let hasTestFailed: boolean = false; @@ -50,9 +49,6 @@ describe(testName, function() { }); it('Run autoconfig', async function() { - if (obs.isDarwin()) { - this.skip(); - } const start = performance.now(); let progressInfo: IConfigProgress; let settingValue: any; diff --git a/tests/osn-tests/src/test_nodeobs_service.ts b/tests/osn-tests/src/test_nodeobs_service.ts index 78248bb65..506dccad0 100644 --- a/tests/osn-tests/src/test_nodeobs_service.ts +++ b/tests/osn-tests/src/test_nodeobs_service.ts @@ -100,9 +100,6 @@ describe(testName, function() { }); it('Simple mode - Start recording and stop', async function() { - if (obs.isDarwin()) { - this.skip(); - } // Preparing environment obs.setSetting(EOBSSettingsCategories.Output, 'Mode', 'Simple'); obs.setSetting(EOBSSettingsCategories.Output, 'StreamEncoder', obs.os === 'win32' ? 'x264' : 'obs_x264'); @@ -150,9 +147,6 @@ describe(testName, function() { }); it('Simple mode - Start replay buffer, save replay and stop', async function() { - if (obs.isDarwin()) { - this.skip(); - } // Preparing environment obs.setSetting(EOBSSettingsCategories.Output, 'Mode', 'Simple'); obs.setSetting(EOBSSettingsCategories.Output, 'StreamEncoder', obs.os === 'win32' ? 'x264' : 'obs_x264'); @@ -200,9 +194,6 @@ describe(testName, function() { }); it('Simple mode - Record while streaming', async function() { - if (obs.isDarwin()) { - this.skip(); - } // Preparing environment obs.setSetting(EOBSSettingsCategories.Output, 'Mode', 'Simple'); obs.setSetting(EOBSSettingsCategories.Output, 'StreamEncoder', obs.os === 'win32' ? 'x264' : 'obs_x264'); @@ -290,9 +281,6 @@ describe(testName, function() { }); it('Simple mode - Record replay while streaming and save', async function() { - if (obs.isDarwin()) { - this.skip(); - } // Preparing environment obs.setSetting(EOBSSettingsCategories.Output, 'Mode', 'Simple'); obs.setSetting(EOBSSettingsCategories.Output, 'StreamEncoder', obs.os === 'win32' ? 'x264' : 'obs_x264'); @@ -380,9 +368,6 @@ describe(testName, function() { }); it('Simple mode - Record and use replay buffer while streaming', async function() { - if (obs.isDarwin()) { - this.skip(); - } // Preparing environment obs.setSetting(EOBSSettingsCategories.Output, 'Mode', 'Simple'); obs.setSetting(EOBSSettingsCategories.Output, 'StreamEncoder', obs.os === 'win32' ? 'x264' : 'obs_x264'); @@ -510,9 +495,6 @@ describe(testName, function() { }); it('Advanced mode - Start and stop streaming', async function() { - if (obs.isDarwin()) { - this.skip(); - } // Preparing environment obs.setSetting(EOBSSettingsCategories.Output, 'Mode', 'Advanced'); obs.setSetting(EOBSSettingsCategories.Output, 'Encoder', 'obs_x264'); @@ -564,9 +546,6 @@ describe(testName, function() { }); it('Advanced mode - Start recording and stop', async function() { - if (obs.isDarwin()) { - this.skip(); - } // Preparing environment obs.setSetting(EOBSSettingsCategories.Output, 'Mode', 'Advanced'); obs.setSetting(EOBSSettingsCategories.Output, 'Encoder', 'obs_x264'); @@ -614,9 +593,6 @@ describe(testName, function() { }); it('Advanced mode - Start replay buffer, save replay and stop', async function() { - if (obs.isDarwin()) { - this.skip(); - } // Preparing environment obs.setSetting(EOBSSettingsCategories.Output, 'Mode', 'Advanced'); obs.setSetting(EOBSSettingsCategories.Output, 'Encoder', 'obs_x264'); @@ -665,9 +641,6 @@ describe(testName, function() { }); it('Advanced mode - Record while streaming', async function() { - if (obs.isDarwin()) { - this.skip(); - } // Preparing environment obs.setSetting(EOBSSettingsCategories.Output, 'Mode', 'Advanced'); obs.setSetting(EOBSSettingsCategories.Output, 'Encoder', 'obs_x264'); @@ -756,9 +729,6 @@ describe(testName, function() { }); it('Advanced mode - Record replay while streaming and save', async function() { - if (obs.isDarwin()) { - this.skip(); - } // Preparing environment obs.setSetting(EOBSSettingsCategories.Output, 'Mode', 'Advanced'); obs.setSetting(EOBSSettingsCategories.Output, 'Encoder', 'obs_x264'); @@ -847,9 +817,6 @@ describe(testName, function() { }); it('Advanced mode - Record and use replay buffer while streaming', async function() { - if (obs.isDarwin()) { - this.skip(); - } // Preparing environment obs.setSetting(EOBSSettingsCategories.Output, 'Mode', 'Advanced'); obs.setSetting(EOBSSettingsCategories.Output, 'Encoder', 'obs_x264'); @@ -978,10 +945,6 @@ describe(testName, function() { }); it('Fail test - Stream with invalid stream key', async function() { - if (obs.isDarwin()) { - this.skip(); - } - let signalInfo: IOBSOutputSignalInfo; try { diff --git a/tests/osn-tests/src/test_osn_advanced_recording.ts b/tests/osn-tests/src/test_osn_advanced_recording.ts index 471304ea3..1028ac88e 100644 --- a/tests/osn-tests/src/test_osn_advanced_recording.ts +++ b/tests/osn-tests/src/test_osn_advanced_recording.ts @@ -13,7 +13,7 @@ import path = require('path'); const testName = 'osn-advanced-recording'; const customFilenamePattern = '%CCYY-%MM-%DD_%hh-%mm-%ss-%s-%%'; -describe(testName, () => { +describe(testName, function() { let obs: OBSHandler; let hasTestFailed: boolean = false; // Initialize OBS process @@ -52,285 +52,291 @@ describe(testName, () => { it('Create advanced recording', async () => { const recording = osn.AdvancedRecordingFactory.create(); - expect(recording).to.not.equal( - undefined, "Error while creating the simple recording output"); - - expect(recording.path).to.equal( - '', "Invalid path default value"); - expect(recording.format).to.equal( - ERecordingFormat.MP4, "Invalid format default value"); - expect(recording.fileFormat).to.equal( - '%CCYY-%MM-%DD %hh-%mm-%ss', "Invalid fileFormat default value"); - expect(recording.overwrite).to.equal( - false, "Invalid overwrite default value"); - expect(recording.noSpace).to.equal( - false, "Invalid noSpace default value"); - expect(recording.muxerSettings).to.equal( - '', "Invalid muxerSettings default value"); - expect(recording.mixer).to.equal( - 1, "Invalid mixer default value"); - expect(recording.rescaling).to.equal( - false, "Invalid rescaling default value"); - expect(recording.outputWidth).to.equal( - 1280, "Invalid outputWidth default value"); - expect(recording.outputHeight).to.equal( - 720, "Invalid outputHeight default value"); - expect(recording.useStreamEncoders).to.equal( - true, "Invalid useStreamEncoders default value"); - - recording.path = path.join(path.normalize(__dirname), '..', 'osnData'); - recording.format = ERecordingFormat.MOV; - recording.fileFormat = customFilenamePattern; - recording.videoEncoder = - osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-adv-recording-1'); - recording.overwrite = true; - recording.noSpace = false; - recording.video = obs.defaultVideoContext; - recording.mixer = 7; - recording.rescaling = true; - recording.outputWidth = 1920; - recording.outputHeight = 1080; - recording.useStreamEncoders = false; - - expect(recording.path).to.equal( - path.join(path.normalize(__dirname), '..', 'osnData'), "Invalid path value"); - expect(recording.format).to.equal( - ERecordingFormat.MOV, "Invalid format value"); - expect(recording.fileFormat).to.equal( - customFilenamePattern, "Invalid fileFormat value"); - expect(recording.overwrite).to.equal( - true, "Invalid overwrite value"); - expect(recording.noSpace).to.equal( - false, "Invalid noSpace value"); - expect(recording.mixer).to.equal( - 7, "Invalid mixer default value"); - expect(recording.rescaling).to.equal( - true, "Invalid rescaling default value"); - expect(recording.outputWidth).to.equal( - 1920, "Invalid outputWidth default value"); - expect(recording.outputHeight).to.equal( - 1080, "Invalid outputHeight default value"); - expect(recording.useStreamEncoders).to.equal( - false, "Invalid useStreamEncoders default value"); - - const videoEncoder = recording.videoEncoder; - osn.AdvancedRecordingFactory.destroy(recording); - videoEncoder.release(); + try { + expect(recording).to.not.equal( + undefined, "Error while creating the simple recording output"); + + expect(recording.path).to.equal( + '', "Invalid path default value"); + expect(recording.format).to.equal( + ERecordingFormat.MP4, "Invalid format default value"); + expect(recording.fileFormat).to.equal( + '%CCYY-%MM-%DD %hh-%mm-%ss', "Invalid fileFormat default value"); + expect(recording.overwrite).to.equal( + false, "Invalid overwrite default value"); + expect(recording.noSpace).to.equal( + false, "Invalid noSpace default value"); + expect(recording.muxerSettings).to.equal( + '', "Invalid muxerSettings default value"); + expect(recording.mixer).to.equal( + 1, "Invalid mixer default value"); + expect(recording.rescaling).to.equal( + false, "Invalid rescaling default value"); + expect(recording.outputWidth).to.equal( + 1280, "Invalid outputWidth default value"); + expect(recording.outputHeight).to.equal( + 720, "Invalid outputHeight default value"); + expect(recording.useStreamEncoders).to.equal( + true, "Invalid useStreamEncoders default value"); + + recording.path = path.join(path.normalize(__dirname), '..', 'osnData'); + recording.format = ERecordingFormat.MOV; + recording.fileFormat = customFilenamePattern; + recording.videoEncoder = + osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-adv-recording-1'); + recording.overwrite = true; + recording.noSpace = false; + recording.video = obs.defaultVideoContext; + recording.mixer = 7; + recording.rescaling = true; + recording.outputWidth = 1920; + recording.outputHeight = 1080; + recording.useStreamEncoders = false; + + expect(recording.path).to.equal( + path.join(path.normalize(__dirname), '..', 'osnData'), "Invalid path value"); + expect(recording.format).to.equal( + ERecordingFormat.MOV, "Invalid format value"); + expect(recording.fileFormat).to.equal( + customFilenamePattern, "Invalid fileFormat value"); + expect(recording.overwrite).to.equal( + true, "Invalid overwrite value"); + expect(recording.noSpace).to.equal( + false, "Invalid noSpace value"); + expect(recording.mixer).to.equal( + 7, "Invalid mixer default value"); + expect(recording.rescaling).to.equal( + true, "Invalid rescaling default value"); + expect(recording.outputWidth).to.equal( + 1920, "Invalid outputWidth default value"); + expect(recording.outputHeight).to.equal( + 1080, "Invalid outputHeight default value"); + expect(recording.useStreamEncoders).to.equal( + false, "Invalid useStreamEncoders default value"); + } finally { + const videoEncoder = recording.videoEncoder; + osn.AdvancedRecordingFactory.destroy(recording); + if (videoEncoder) { + videoEncoder.release(); + } + } }); it('Start advanced recording - Stream', async function () { - if (obs.isDarwin()) { - this.skip(); - } const recording = osn.AdvancedRecordingFactory.create(); - recording.path = path.join(path.normalize(__dirname), '..', 'osnData'); - recording.format = ERecordingFormat.MP4; - recording.fileFormat = customFilenamePattern; - recording.overwrite = false; - recording.noSpace = false; - recording.video = obs.defaultVideoContext; - recording.signalHandler = (signal) => {obs.signals.push(signal)}; - recording.useStreamEncoders = true; const stream = osn.AdvancedStreamingFactory.create(); - stream.videoEncoder = - 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)}; - const track1 = osn.AudioTrackFactory.create(160, 'track1'); - osn.AudioTrackFactory.setAtIndex(track1, 1); - recording.streaming = stream; - - 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)); - - stream.start(); - - signalInfo = await obs.getNextSignalInfo( - EOBSOutputType.Streaming, EOBSOutputSignal.Starting); - expect(signalInfo.type).to.equal( - EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); - expect(signalInfo.signal).to.equal( - EOBSOutputSignal.Starting, GetErrorMessage(ETestErrorMsg.StreamOutput)); - - signalInfo = await obs.getNextSignalInfo( - EOBSOutputType.Streaming, EOBSOutputSignal.Activate); - - if (signalInfo.signal == EOBSOutputSignal.Stop) { - throw Error(GetErrorMessage( - ETestErrorMsg.StreamOutputDidNotStart, signalInfo.code.toString())); - } - - expect(signalInfo.type).to.equal(EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); - expect(signalInfo.signal).to.equal(EOBSOutputSignal.Activate, GetErrorMessage(ETestErrorMsg.StreamOutput)); - - signalInfo = await obs.getNextSignalInfo(EOBSOutputType.Streaming, EOBSOutputSignal.Start); - expect(signalInfo.type).to.equal(EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); - expect(signalInfo.signal).to.equal(EOBSOutputSignal.Start, GetErrorMessage(ETestErrorMsg.StreamOutput)); - - - await sleep(500); - - 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)); - - const lastFile = path.basename(recording.lastFile()); - expect(lastFile).to.match( - /^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}-\d+-%\.mp4$/, - 'Wrong recording filename formatting', - ); - - stream.stop(); - - signalInfo = await obs.getNextSignalInfo( - EOBSOutputType.Streaming, EOBSOutputSignal.Stopping); - - expect(signalInfo.type).to.equal( - EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); - expect(signalInfo.signal).to.equal( - EOBSOutputSignal.Stopping, GetErrorMessage(ETestErrorMsg.StreamOutput)); - - signalInfo = await obs.getNextSignalInfo(EOBSOutputType.Streaming, EOBSOutputSignal.Stop); - - if (signalInfo.code != 0) { - throw Error(GetErrorMessage( - ETestErrorMsg.StreamOutputStoppedWithError, - signalInfo.code.toString(), signalInfo.error)); + try { + recording.path = path.join(path.normalize(__dirname), '..', 'osnData'); + recording.format = ERecordingFormat.MP4; + recording.fileFormat = customFilenamePattern; + recording.overwrite = false; + recording.noSpace = false; + recording.video = obs.defaultVideoContext; + recording.signalHandler = (signal) => {obs.signals.push(signal)}; + recording.useStreamEncoders = true; + + stream.videoEncoder = + 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)}; + const track1 = osn.AudioTrackFactory.create(160, 'track1'); + osn.AudioTrackFactory.setAtIndex(track1, 1); + recording.streaming = stream; + + 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)); + + stream.start(); + + signalInfo = await obs.getNextSignalInfo( + EOBSOutputType.Streaming, EOBSOutputSignal.Starting); + expect(signalInfo.type).to.equal( + EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); + expect(signalInfo.signal).to.equal( + EOBSOutputSignal.Starting, GetErrorMessage(ETestErrorMsg.StreamOutput)); + + signalInfo = await obs.getNextSignalInfo( + EOBSOutputType.Streaming, EOBSOutputSignal.Activate); + + if (signalInfo.signal == EOBSOutputSignal.Stop) { + throw Error(GetErrorMessage( + ETestErrorMsg.StreamOutputDidNotStart, signalInfo.code.toString())); + } + + expect(signalInfo.type).to.equal(EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); + expect(signalInfo.signal).to.equal(EOBSOutputSignal.Activate, GetErrorMessage(ETestErrorMsg.StreamOutput)); + + signalInfo = await obs.getNextSignalInfo(EOBSOutputType.Streaming, EOBSOutputSignal.Start); + expect(signalInfo.type).to.equal(EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); + expect(signalInfo.signal).to.equal(EOBSOutputSignal.Start, GetErrorMessage(ETestErrorMsg.StreamOutput)); + + + await sleep(500); + + 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)); + const lastFile = path.basename(recording.lastFile()); + expect(lastFile).to.match( + /^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}-\d+-%\.mp4$/, + 'Wrong recording filename formatting', + ); + + stream.stop(); + + signalInfo = await obs.getNextSignalInfo( + EOBSOutputType.Streaming, EOBSOutputSignal.Stopping); + + expect(signalInfo.type).to.equal( + EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); + expect(signalInfo.signal).to.equal( + EOBSOutputSignal.Stopping, GetErrorMessage(ETestErrorMsg.StreamOutput)); + + signalInfo = await obs.getNextSignalInfo(EOBSOutputType.Streaming, EOBSOutputSignal.Stop); + + if (signalInfo.code != 0) { + throw Error(GetErrorMessage( + ETestErrorMsg.StreamOutputStoppedWithError, + signalInfo.code.toString(), signalInfo.error)); + } + + expect(signalInfo.type).to.equal( + EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); + expect(signalInfo.signal).to.equal( + EOBSOutputSignal.Stop, GetErrorMessage(ETestErrorMsg.StreamOutput)); + + signalInfo = await obs.getNextSignalInfo( + EOBSOutputType.Streaming, EOBSOutputSignal.Deactivate); + expect(signalInfo.type).to.equal( + EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); + expect(signalInfo.signal).to.equal( + EOBSOutputSignal.Deactivate, GetErrorMessage(ETestErrorMsg.StreamOutput)); + } finally { + const videoEncoder = stream.videoEncoder; + osn.AdvancedRecordingFactory.destroy(recording); + osn.AdvancedStreamingFactory.destroy(stream); + if (videoEncoder) { + videoEncoder.release(); + } } - - expect(signalInfo.type).to.equal( - EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); - expect(signalInfo.signal).to.equal( - EOBSOutputSignal.Stop, GetErrorMessage(ETestErrorMsg.StreamOutput)); - - signalInfo = await obs.getNextSignalInfo( - EOBSOutputType.Streaming, EOBSOutputSignal.Deactivate); - expect(signalInfo.type).to.equal( - EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); - 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 () { - if (obs.isDarwin()) { - this.skip(); - } const recording = osn.AdvancedRecordingFactory.create(); - recording.path = path.join(path.normalize(__dirname), '..', 'osnData'); - recording.format = ERecordingFormat.MP4; - recording.useStreamEncoders = false; - recording.videoEncoder = - osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-adv-recording-2'); - recording.overwrite = false; - recording.noSpace = false; - recording.video = obs.defaultVideoContext; - const track1 = osn.AudioTrackFactory.create(160, 'track1'); - osn.AudioTrackFactory.setAtIndex(track1, 1); - recording.signalHandler = (signal) => {obs.signals.push(signal)}; - - 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(500); - - 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)); + try { + recording.path = path.join(path.normalize(__dirname), '..', 'osnData'); + recording.format = ERecordingFormat.MP4; + recording.useStreamEncoders = false; + recording.videoEncoder = + osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-adv-recording-2'); + recording.overwrite = false; + recording.noSpace = false; + recording.video = obs.defaultVideoContext; + const track1 = osn.AudioTrackFactory.create(160, 'track1'); + osn.AudioTrackFactory.setAtIndex(track1, 1); + recording.signalHandler = (signal) => {obs.signals.push(signal)}; + + 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(500); + + 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)); + } finally { + const videoEncoder = recording.videoEncoder; + osn.AdvancedRecordingFactory.destroy(recording); + if (videoEncoder) { + videoEncoder.release(); + } } - - 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)); - - const videoEncoder = recording.videoEncoder; - osn.AdvancedRecordingFactory.destroy(recording); - videoEncoder.release(); }); -}); +}); \ No newline at end of file diff --git a/tests/osn-tests/src/test_osn_advanced_replayBuffer.ts b/tests/osn-tests/src/test_osn_advanced_replayBuffer.ts index 41d0d544e..057ca1e66 100644 --- a/tests/osn-tests/src/test_osn_advanced_replayBuffer.ts +++ b/tests/osn-tests/src/test_osn_advanced_replayBuffer.ts @@ -10,7 +10,7 @@ import path = require('path'); const testName = 'osn-advanced-replay-buffer'; -describe(testName, () => { +describe(testName, function() { let obs: OBSHandler; let hasTestFailed: boolean = false; // Initialize OBS process @@ -49,394 +49,395 @@ describe(testName, () => { it('Create advanced replay buffer', async () => { const replayBuffer = osn.AdvancedReplayBufferFactory.create(); - expect(replayBuffer).to.not.equal( - undefined, "Error while creating the simple replayBuffer output"); - - expect(replayBuffer.path).to.equal( - '', "Invalid path default value"); - expect(replayBuffer.format).to.equal( - osn.ERecordingFormat.MP4, "Invalid format default value"); - expect(replayBuffer.fileFormat).to.equal( - '%CCYY-%MM-%DD %hh-%mm-%ss', "Invalid fileFormat default value"); - expect(replayBuffer.overwrite).to.equal( - false, "Invalid overwrite default value"); - expect(replayBuffer.noSpace).to.equal( - false, "Invalid noSpace default value"); - expect(replayBuffer.muxerSettings).to.equal( - '', "Invalid muxerSettings default value"); - expect(replayBuffer.duration).to.equal( - 20, "Invalid duration default value"); - expect(replayBuffer.prefix).to.equal( - 'Replay', "Invalid prefix default value"); - expect(replayBuffer.suffix).to.equal( - '', "Invalid suffix default value"); - expect(replayBuffer.usesStream).to.equal( - false, "Invalid usesStream default value"); - expect(replayBuffer.mixer).to.equal( - 1, "Invalid mixer default value"); - - replayBuffer.path = path.join(path.normalize(__dirname), '..', 'osnData'); - replayBuffer.format = osn.ERecordingFormat.MOV; - replayBuffer.overwrite = true; - replayBuffer.noSpace = false; - replayBuffer.duration = 60; - replayBuffer.video = obs.defaultVideoContext; - replayBuffer.prefix = 'Prefix'; - replayBuffer.suffix = 'Suffix'; - replayBuffer.usesStream = true; - replayBuffer.mixer = 7; - - expect(replayBuffer.path).to.equal( - path.join(path.normalize(__dirname), '..', 'osnData'), "Invalid path value"); - expect(replayBuffer.format).to.equal( - osn.ERecordingFormat.MOV, "Invalid format value"); - expect(replayBuffer.overwrite).to.equal( - true, "Invalid overwrite value"); - expect(replayBuffer.noSpace).to.equal( - false, "Invalid noSpace value"); - expect(replayBuffer.duration).to.equal( - 60, "Invalid duration value"); - expect(replayBuffer.prefix).to.equal( - 'Prefix', "Invalid prefix value"); - expect(replayBuffer.suffix).to.equal( - 'Suffix', "Invalid suffix value"); - expect(replayBuffer.usesStream).to.equal( - true, "Invalid usesStream value"); - expect(replayBuffer.mixer).to.equal( - 7, "Invalid mixer default value"); - - osn.AdvancedReplayBufferFactory.destroy(replayBuffer); + try { + expect(replayBuffer).to.not.equal( + undefined, "Error while creating the simple replayBuffer output"); + + expect(replayBuffer.path).to.equal( + '', "Invalid path default value"); + expect(replayBuffer.format).to.equal( + osn.ERecordingFormat.MP4, "Invalid format default value"); + expect(replayBuffer.fileFormat).to.equal( + '%CCYY-%MM-%DD %hh-%mm-%ss', "Invalid fileFormat default value"); + expect(replayBuffer.overwrite).to.equal( + false, "Invalid overwrite default value"); + expect(replayBuffer.noSpace).to.equal( + false, "Invalid noSpace default value"); + expect(replayBuffer.muxerSettings).to.equal( + '', "Invalid muxerSettings default value"); + expect(replayBuffer.duration).to.equal( + 20, "Invalid duration default value"); + expect(replayBuffer.prefix).to.equal( + 'Replay', "Invalid prefix default value"); + expect(replayBuffer.suffix).to.equal( + '', "Invalid suffix default value"); + expect(replayBuffer.usesStream).to.equal( + false, "Invalid usesStream default value"); + expect(replayBuffer.mixer).to.equal( + 1, "Invalid mixer default value"); + + replayBuffer.path = path.join(path.normalize(__dirname), '..', 'osnData'); + replayBuffer.format = osn.ERecordingFormat.MOV; + replayBuffer.overwrite = true; + replayBuffer.noSpace = false; + replayBuffer.duration = 60; + replayBuffer.video = obs.defaultVideoContext; + replayBuffer.prefix = 'Prefix'; + replayBuffer.suffix = 'Suffix'; + replayBuffer.usesStream = true; + replayBuffer.mixer = 7; + + expect(replayBuffer.path).to.equal( + path.join(path.normalize(__dirname), '..', 'osnData'), "Invalid path value"); + expect(replayBuffer.format).to.equal( + osn.ERecordingFormat.MOV, "Invalid format value"); + expect(replayBuffer.overwrite).to.equal( + true, "Invalid overwrite value"); + expect(replayBuffer.noSpace).to.equal( + false, "Invalid noSpace value"); + expect(replayBuffer.duration).to.equal( + 60, "Invalid duration value"); + expect(replayBuffer.prefix).to.equal( + 'Prefix', "Invalid prefix value"); + expect(replayBuffer.suffix).to.equal( + 'Suffix', "Invalid suffix value"); + expect(replayBuffer.usesStream).to.equal( + true, "Invalid usesStream value"); + expect(replayBuffer.mixer).to.equal( + 7, "Invalid mixer default value"); + } finally { + osn.AdvancedReplayBufferFactory.destroy(replayBuffer); + } }); it('Start advanced replay buffer - Use Recording', async function() { - if (obs.isDarwin()) { - this.skip(); - } const replayBuffer = osn.AdvancedReplayBufferFactory.create(); - replayBuffer.path = path.join(path.normalize(__dirname), '..', 'osnData'); - replayBuffer.format = osn.ERecordingFormat.MP4; - replayBuffer.overwrite = false; - replayBuffer.noSpace = false; - replayBuffer.video = obs.defaultVideoContext; - replayBuffer.signalHandler = (signal) => {obs.signals.push(signal)}; - replayBuffer.duration = 60; - replayBuffer.prefix = 'Prefix'; - replayBuffer.suffix = 'Suffix'; - const recording = osn.AdvancedRecordingFactory.create(); - recording.path = path.join(path.normalize(__dirname), '..', 'osnData'); - recording.format = osn.ERecordingFormat.MP4; - recording.useStreamEncoders = false; - recording.video = obs.defaultVideoContext; - recording.videoEncoder = - 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; - recording.noSpace = false; - recording.signalHandler = (signal) => {obs.signals.push(signal)}; - - replayBuffer.recording = recording; - - replayBuffer.start(); - - let signalInfo = await obs.getNextSignalInfo( - EOBSOutputType.ReplayBuffer, EOBSOutputSignal.Start); - - if (signalInfo.signal == EOBSOutputSignal.Stop) { - throw Error(GetErrorMessage( - ETestErrorMsg.ReplayBufferDidNotStart, signalInfo.code.toString(), signalInfo.error)); - } - - expect(signalInfo.type).to.equal( - EOBSOutputType.ReplayBuffer, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); - expect(signalInfo.signal).to.equal( - EOBSOutputSignal.Start, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); - - recording.start(); - - 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(500); - - replayBuffer.save(); - - signalInfo = await obs.getNextSignalInfo(EOBSOutputType.ReplayBuffer, EOBSOutputSignal.Writing); - expect(signalInfo.type).to.equal(EOBSOutputType.ReplayBuffer, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); - expect(signalInfo.signal).to.equal(EOBSOutputSignal.Writing, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); - - signalInfo = await obs.getNextSignalInfo(EOBSOutputType.ReplayBuffer, EOBSOutputSignal.Wrote); - expect(signalInfo.type).to.equal(EOBSOutputType.ReplayBuffer, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); - expect(signalInfo.signal).to.equal(EOBSOutputSignal.Wrote, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); - - replayBuffer.stop(); - - signalInfo = await obs.getNextSignalInfo(EOBSOutputType.ReplayBuffer, EOBSOutputSignal.Stopping); - expect(signalInfo.type).to.equal(EOBSOutputType.ReplayBuffer, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); - expect(signalInfo.signal).to.equal(EOBSOutputSignal.Stopping, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); - - signalInfo = await obs.getNextSignalInfo(EOBSOutputType.ReplayBuffer, EOBSOutputSignal.Stop); + try { + replayBuffer.path = path.join(path.normalize(__dirname), '..', 'osnData'); + replayBuffer.format = osn.ERecordingFormat.MP4; + replayBuffer.overwrite = false; + replayBuffer.noSpace = false; + replayBuffer.video = obs.defaultVideoContext; + replayBuffer.signalHandler = (signal) => {obs.signals.push(signal)}; + replayBuffer.duration = 60; + replayBuffer.prefix = 'Prefix'; + replayBuffer.suffix = 'Suffix'; - if (signalInfo.code != 0) { - throw Error(GetErrorMessage(ETestErrorMsg.ReplayBufferStoppedWithError, signalInfo.code.toString(), signalInfo.error)); - } - - expect(signalInfo.type).to.equal(EOBSOutputType.ReplayBuffer, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); - expect(signalInfo.signal).to.equal(EOBSOutputSignal.Stop, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); - - const lastFile = replayBuffer.lastFile().split('/'); - const expectedPrefix = lastFile[lastFile.length - 1].startsWith('Prefix'); - const expectedSuffix = lastFile[lastFile.length - 1].endsWith('Suffix.mp4'); - - expect(expectedPrefix).to.equal(true, 'Wrong prefix when saving the replay buffer'); - expect(expectedSuffix).to.equal(true, 'Wrong suffix when saving the replay buffer'); - - 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)); - } + recording.path = path.join(path.normalize(__dirname), '..', 'osnData'); + recording.format = osn.ERecordingFormat.MP4; + recording.useStreamEncoders = false; + recording.video = obs.defaultVideoContext; + recording.videoEncoder = + 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; + recording.noSpace = false; + recording.signalHandler = (signal) => {obs.signals.push(signal)}; + + replayBuffer.recording = recording; + + replayBuffer.start(); + + let signalInfo = await obs.getNextSignalInfo( + EOBSOutputType.ReplayBuffer, EOBSOutputSignal.Start); + + if (signalInfo.signal == EOBSOutputSignal.Stop) { + throw Error(GetErrorMessage( + ETestErrorMsg.ReplayBufferDidNotStart, signalInfo.code.toString(), signalInfo.error)); + } + + expect(signalInfo.type).to.equal( + EOBSOutputType.ReplayBuffer, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); + expect(signalInfo.signal).to.equal( + EOBSOutputSignal.Start, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); + + recording.start(); + + 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(500); + + replayBuffer.save(); + + signalInfo = await obs.getNextSignalInfo(EOBSOutputType.ReplayBuffer, EOBSOutputSignal.Writing); + expect(signalInfo.type).to.equal(EOBSOutputType.ReplayBuffer, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); + expect(signalInfo.signal).to.equal(EOBSOutputSignal.Writing, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); + + signalInfo = await obs.getNextSignalInfo(EOBSOutputType.ReplayBuffer, EOBSOutputSignal.Wrote); + expect(signalInfo.type).to.equal(EOBSOutputType.ReplayBuffer, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); + expect(signalInfo.signal).to.equal(EOBSOutputSignal.Wrote, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); + + replayBuffer.stop(); + + signalInfo = await obs.getNextSignalInfo(EOBSOutputType.ReplayBuffer, EOBSOutputSignal.Stopping); + expect(signalInfo.type).to.equal(EOBSOutputType.ReplayBuffer, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); + expect(signalInfo.signal).to.equal(EOBSOutputSignal.Stopping, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); + + signalInfo = await obs.getNextSignalInfo(EOBSOutputType.ReplayBuffer, EOBSOutputSignal.Stop); + + if (signalInfo.code != 0) { + throw Error(GetErrorMessage(ETestErrorMsg.ReplayBufferStoppedWithError, signalInfo.code.toString(), signalInfo.error)); + } + + expect(signalInfo.type).to.equal(EOBSOutputType.ReplayBuffer, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); + expect(signalInfo.signal).to.equal(EOBSOutputSignal.Stop, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); + + const lastFile = replayBuffer.lastFile().split('/'); + const expectedPrefix = lastFile[lastFile.length - 1].startsWith('Prefix'); + const expectedSuffix = lastFile[lastFile.length - 1].endsWith('Suffix.mp4'); + + expect(expectedPrefix).to.equal(true, 'Wrong prefix when saving the replay buffer'); + expect(expectedSuffix).to.equal(true, 'Wrong suffix when saving the replay buffer'); + + 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)); - 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); - signalInfo = await obs.getNextSignalInfo( - EOBSOutputType.Recording, EOBSOutputSignal.Wrote); + if (signalInfo.code != 0) { + throw Error(GetErrorMessage( + ETestErrorMsg.RecordOutputStoppedWithError, signalInfo.code.toString(), signalInfo.error)); + } - 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)); + } finally { + const streamEncoder = recording.videoEncoder; + osn.AdvancedReplayBufferFactory.destroy(replayBuffer); + osn.AdvancedRecordingFactory.destroy(recording); + if (streamEncoder)streamEncoder.release(); } - - expect(signalInfo.type).to.equal( - EOBSOutputType.Recording, GetErrorMessage(ETestErrorMsg.RecordingOutput)); - 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() { - if (obs.isDarwin()) { - this.skip(); - } const replayBuffer = osn.AdvancedReplayBufferFactory.create(); - replayBuffer.path = path.join(path.normalize(__dirname), '..', 'osnData'); - replayBuffer.format = osn.ERecordingFormat.MP4; - replayBuffer.overwrite = false; - replayBuffer.noSpace = false; - replayBuffer.video = obs.defaultVideoContext; - replayBuffer.signalHandler = (signal) => {obs.signals.push(signal)}; - replayBuffer.duration = 60; - replayBuffer.prefix = 'Prefix'; - replayBuffer.suffix = 'Suffix'; - const recording = osn.AdvancedRecordingFactory.create(); - recording.path = path.join(path.normalize(__dirname), '..', 'osnData'); - recording.format = osn.ERecordingFormat.MP4; - recording.useStreamEncoders = true; - recording.overwrite = false; - recording.noSpace = false; - 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-adv-stream-1'); - stream.service = osn.ServiceFactory.legacySettings; - const track1 = osn.AudioTrackFactory.create(160, 'track1'); - osn.AudioTrackFactory.setAtIndex(track1, 1); - stream.signalHandler = (signal) => {obs.signals.push(signal)}; - - recording.streaming = stream; - replayBuffer.recording = recording; - - replayBuffer.start(); - - let signalInfo = await obs.getNextSignalInfo( - EOBSOutputType.ReplayBuffer, EOBSOutputSignal.Start); + try { + replayBuffer.path = path.join(path.normalize(__dirname), '..', 'osnData'); + replayBuffer.format = osn.ERecordingFormat.MP4; + replayBuffer.overwrite = false; + replayBuffer.noSpace = false; + replayBuffer.video = obs.defaultVideoContext; + replayBuffer.signalHandler = (signal) => {obs.signals.push(signal)}; + replayBuffer.duration = 60; + replayBuffer.prefix = 'Prefix'; + replayBuffer.suffix = 'Suffix'; + + recording.path = path.join(path.normalize(__dirname), '..', 'osnData'); + recording.format = osn.ERecordingFormat.MP4; + recording.useStreamEncoders = true; + recording.overwrite = false; + recording.noSpace = false; + recording.video = obs.defaultVideoContext; + recording.useStreamEncoders = true; + recording.signalHandler = (signal) => {obs.signals.push(signal)}; + + stream.video = obs.defaultVideoContext; + stream.videoEncoder = + 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); + stream.signalHandler = (signal) => {obs.signals.push(signal)}; + + recording.streaming = stream; + replayBuffer.recording = recording; - if (signalInfo.signal == EOBSOutputSignal.Stop) { - throw Error(GetErrorMessage( - ETestErrorMsg.ReplayBufferDidNotStart, signalInfo.code.toString(), signalInfo.error)); - } - - expect(signalInfo.type).to.equal( - EOBSOutputType.ReplayBuffer, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); - expect(signalInfo.signal).to.equal( - EOBSOutputSignal.Start, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); - - recording.start(); - - 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)); - - stream.start(); - - signalInfo = await obs.getNextSignalInfo( - EOBSOutputType.Streaming, EOBSOutputSignal.Starting); - expect(signalInfo.type).to.equal( - EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); - expect(signalInfo.signal).to.equal( - EOBSOutputSignal.Starting, GetErrorMessage(ETestErrorMsg.StreamOutput)); - - signalInfo = await obs.getNextSignalInfo( - EOBSOutputType.Streaming, EOBSOutputSignal.Activate); - - if (signalInfo.signal == EOBSOutputSignal.Stop) { - throw Error(GetErrorMessage( - ETestErrorMsg.StreamOutputDidNotStart, signalInfo.code.toString())); - } - - expect(signalInfo.type).to.equal(EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); - expect(signalInfo.signal).to.equal(EOBSOutputSignal.Activate, GetErrorMessage(ETestErrorMsg.StreamOutput)); - - signalInfo = await obs.getNextSignalInfo(EOBSOutputType.Streaming, EOBSOutputSignal.Start); - expect(signalInfo.type).to.equal(EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); - expect(signalInfo.signal).to.equal(EOBSOutputSignal.Start, GetErrorMessage(ETestErrorMsg.StreamOutput)); - - await sleep(500); - - replayBuffer.save(); - - signalInfo = await obs.getNextSignalInfo(EOBSOutputType.ReplayBuffer, EOBSOutputSignal.Writing); - expect(signalInfo.type).to.equal(EOBSOutputType.ReplayBuffer, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); - expect(signalInfo.signal).to.equal(EOBSOutputSignal.Writing, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); + replayBuffer.start(); + + let signalInfo = await obs.getNextSignalInfo( + EOBSOutputType.ReplayBuffer, EOBSOutputSignal.Start); + + if (signalInfo.signal == EOBSOutputSignal.Stop) { + throw Error(GetErrorMessage( + ETestErrorMsg.ReplayBufferDidNotStart, signalInfo.code.toString(), signalInfo.error)); + } + + expect(signalInfo.type).to.equal( + EOBSOutputType.ReplayBuffer, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); + expect(signalInfo.signal).to.equal( + EOBSOutputSignal.Start, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); + + recording.start(); + + 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)); + + stream.start(); + + signalInfo = await obs.getNextSignalInfo( + EOBSOutputType.Streaming, EOBSOutputSignal.Starting); + expect(signalInfo.type).to.equal( + EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); + expect(signalInfo.signal).to.equal( + EOBSOutputSignal.Starting, GetErrorMessage(ETestErrorMsg.StreamOutput)); + + signalInfo = await obs.getNextSignalInfo( + EOBSOutputType.Streaming, EOBSOutputSignal.Activate); + + if (signalInfo.signal == EOBSOutputSignal.Stop) { + throw Error(GetErrorMessage( + ETestErrorMsg.StreamOutputDidNotStart, signalInfo.code.toString())); + } + + expect(signalInfo.type).to.equal(EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); + expect(signalInfo.signal).to.equal(EOBSOutputSignal.Activate, GetErrorMessage(ETestErrorMsg.StreamOutput)); + + signalInfo = await obs.getNextSignalInfo(EOBSOutputType.Streaming, EOBSOutputSignal.Start); + expect(signalInfo.type).to.equal(EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); + expect(signalInfo.signal).to.equal(EOBSOutputSignal.Start, GetErrorMessage(ETestErrorMsg.StreamOutput)); + + await sleep(500); + + replayBuffer.save(); + + signalInfo = await obs.getNextSignalInfo(EOBSOutputType.ReplayBuffer, EOBSOutputSignal.Writing); + expect(signalInfo.type).to.equal(EOBSOutputType.ReplayBuffer, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); + expect(signalInfo.signal).to.equal(EOBSOutputSignal.Writing, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); + + signalInfo = await obs.getNextSignalInfo(EOBSOutputType.ReplayBuffer, EOBSOutputSignal.Wrote); + expect(signalInfo.type).to.equal(EOBSOutputType.ReplayBuffer, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); + expect(signalInfo.signal).to.equal(EOBSOutputSignal.Wrote, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); + + replayBuffer.stop(); + + signalInfo = await obs.getNextSignalInfo(EOBSOutputType.ReplayBuffer, EOBSOutputSignal.Stopping); + expect(signalInfo.type).to.equal(EOBSOutputType.ReplayBuffer, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); + expect(signalInfo.signal).to.equal(EOBSOutputSignal.Stopping, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); + + signalInfo = await obs.getNextSignalInfo(EOBSOutputType.ReplayBuffer, EOBSOutputSignal.Stop); + + if (signalInfo.code != 0) { + throw Error(GetErrorMessage(ETestErrorMsg.ReplayBufferStoppedWithError, signalInfo.code.toString(), signalInfo.error)); + } + + expect(signalInfo.type).to.equal(EOBSOutputType.ReplayBuffer, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); + expect(signalInfo.signal).to.equal(EOBSOutputSignal.Stop, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); + + const lastFile = replayBuffer.lastFile().split('/'); + const expectedPrefix = lastFile[lastFile.length - 1].startsWith('Prefix'); + const expectedSuffix = lastFile[lastFile.length - 1].endsWith('Suffix.mp4'); + + expect(expectedPrefix).to.equal(true, 'Wrong prefix when saving the replay buffer'); + expect(expectedSuffix).to.equal(true, 'Wrong suffix when saving the replay buffer'); + + 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.ReplayBuffer, EOBSOutputSignal.Wrote); - expect(signalInfo.type).to.equal(EOBSOutputType.ReplayBuffer, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); - expect(signalInfo.signal).to.equal(EOBSOutputSignal.Wrote, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); + signalInfo = await obs.getNextSignalInfo( + EOBSOutputType.Recording, EOBSOutputSignal.Stop); - replayBuffer.stop(); + if (signalInfo.code != 0) { + throw Error(GetErrorMessage( + ETestErrorMsg.RecordOutputStoppedWithError, signalInfo.code.toString(), signalInfo.error)); + } - signalInfo = await obs.getNextSignalInfo(EOBSOutputType.ReplayBuffer, EOBSOutputSignal.Stopping); - expect(signalInfo.type).to.equal(EOBSOutputType.ReplayBuffer, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); - expect(signalInfo.signal).to.equal(EOBSOutputSignal.Stopping, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); + 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.ReplayBuffer, EOBSOutputSignal.Stop); + signalInfo = await obs.getNextSignalInfo( + EOBSOutputType.Recording, EOBSOutputSignal.Wrote); - if (signalInfo.code != 0) { - throw Error(GetErrorMessage(ETestErrorMsg.ReplayBufferStoppedWithError, signalInfo.code.toString(), signalInfo.error)); - } - - expect(signalInfo.type).to.equal(EOBSOutputType.ReplayBuffer, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); - expect(signalInfo.signal).to.equal(EOBSOutputSignal.Stop, GetErrorMessage(ETestErrorMsg.ReplayBuffer)); - - const lastFile = replayBuffer.lastFile().split('/'); - const expectedPrefix = lastFile[lastFile.length - 1].startsWith('Prefix'); - const expectedSuffix = lastFile[lastFile.length - 1].endsWith('Suffix.mp4'); - - expect(expectedPrefix).to.equal(true, 'Wrong prefix when saving the replay buffer'); - expect(expectedSuffix).to.equal(true, 'Wrong suffix when saving the replay buffer'); - - 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)); - } + 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)); + + stream.stop(); - 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.Streaming, EOBSOutputSignal.Stopping); + + expect(signalInfo.type).to.equal( + EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); + expect(signalInfo.signal).to.equal( + EOBSOutputSignal.Stopping, GetErrorMessage(ETestErrorMsg.StreamOutput)); + + signalInfo = await obs.getNextSignalInfo(EOBSOutputType.Streaming, EOBSOutputSignal.Stop); + + if (signalInfo.code != 0) { + throw Error(GetErrorMessage( + ETestErrorMsg.StreamOutputStoppedWithError, + signalInfo.code.toString(), signalInfo.error)); + } - signalInfo = await obs.getNextSignalInfo( - EOBSOutputType.Recording, EOBSOutputSignal.Wrote); + expect(signalInfo.type).to.equal( + EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); + expect(signalInfo.signal).to.equal( + EOBSOutputSignal.Stop, GetErrorMessage(ETestErrorMsg.StreamOutput)); - if (signalInfo.code != 0) { - throw Error(GetErrorMessage( - ETestErrorMsg.RecordOutputStoppedWithError, signalInfo.code.toString(), signalInfo.error)); + signalInfo = await obs.getNextSignalInfo( + EOBSOutputType.Streaming, EOBSOutputSignal.Deactivate); + expect(signalInfo.type).to.equal( + EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); + expect(signalInfo.signal).to.equal( + EOBSOutputSignal.Deactivate, GetErrorMessage(ETestErrorMsg.StreamOutput)); + } finally { + const videoEncoder = stream.videoEncoder; + osn.AdvancedReplayBufferFactory.destroy(replayBuffer); + osn.AdvancedRecordingFactory.destroy(recording); + osn.AdvancedStreamingFactory.destroy(stream); + if (videoEncoder) + videoEncoder.release(); } - - expect(signalInfo.type).to.equal( - EOBSOutputType.Recording, GetErrorMessage(ETestErrorMsg.RecordingOutput)); - expect(signalInfo.signal).to.equal( - EOBSOutputSignal.Wrote, GetErrorMessage(ETestErrorMsg.RecordingOutput)); - - stream.stop(); - - signalInfo = await obs.getNextSignalInfo( - EOBSOutputType.Streaming, EOBSOutputSignal.Stopping); - - expect(signalInfo.type).to.equal( - EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); - expect(signalInfo.signal).to.equal( - EOBSOutputSignal.Stopping, GetErrorMessage(ETestErrorMsg.StreamOutput)); - - signalInfo = await obs.getNextSignalInfo(EOBSOutputType.Streaming, EOBSOutputSignal.Stop); - - if (signalInfo.code != 0) { - throw Error(GetErrorMessage( - ETestErrorMsg.StreamOutputStoppedWithError, - signalInfo.code.toString(), signalInfo.error)); - } - - expect(signalInfo.type).to.equal( - EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); - expect(signalInfo.signal).to.equal( - EOBSOutputSignal.Stop, GetErrorMessage(ETestErrorMsg.StreamOutput)); - - signalInfo = await obs.getNextSignalInfo( - EOBSOutputType.Streaming, EOBSOutputSignal.Deactivate); - expect(signalInfo.type).to.equal( - EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); - 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 6c22087c2..cbb4812bd 100644 --- a/tests/osn-tests/src/test_osn_advanced_streaming.ts +++ b/tests/osn-tests/src/test_osn_advanced_streaming.ts @@ -98,9 +98,6 @@ describe(testName, () => { }); it('Stream with missing video encoder', async function() { - if (obs.isDarwin()) { - this.skip(); - } const stream = osn.AdvancedStreamingFactory.create(); stream.service = osn.ServiceFactory.legacySettings; stream.video = obs.defaultVideoContext; @@ -117,9 +114,6 @@ describe(testName, () => { }); it('Stream with missing service', async function() { - if (obs.isDarwin()) { - this.skip(); - } const stream = osn.AdvancedStreamingFactory.create(); stream.videoEncoder = osn.VideoEncoderFactory.create('obs_x264', 'video-encoder'); stream.video = obs.defaultVideoContext; @@ -136,9 +130,6 @@ describe(testName, () => { }); it('Stream with missing canvas', async function() { - if (obs.isDarwin()) { - this.skip(); - } const stream = osn.AdvancedStreamingFactory.create(); stream.service = osn.ServiceFactory.legacySettings; stream.videoEncoder = osn.VideoEncoderFactory.create('obs_x264', 'video-encoder'); @@ -155,9 +146,6 @@ describe(testName, () => { }); it('Start streaming', async function() { - if (obs.isDarwin()) { - this.skip(); - } const stream = osn.AdvancedStreamingFactory.create(); stream.videoEncoder = osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-adv-streaming-1'); @@ -245,9 +233,6 @@ describe(testName, () => { }); it('Stream with invalid stream key', async function() { - if (obs.isDarwin()) { - this.skip(); - } const stream = osn.AdvancedStreamingFactory.create(); stream.videoEncoder = osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-adv-streaming-2'); diff --git a/tests/osn-tests/src/test_osn_dual_output.ts b/tests/osn-tests/src/test_osn_dual_output.ts index 51aef4c79..4032bd056 100644 --- a/tests/osn-tests/src/test_osn_dual_output.ts +++ b/tests/osn-tests/src/test_osn_dual_output.ts @@ -17,13 +17,13 @@ import { randomUUID } from 'crypto'; const testName = 'osn-dual-output'; -describe(testName, () => { +describe(testName, function() { let obs: OBSHandler; let hasTestFailed: boolean = false; let newSceneName = 'scene_' + randomUUID(); let newSourceName: string = 'image_source_' + randomUUID(); const media_path = path.join(path.normalize(__dirname), '..', 'media'); - let secondContext; + let secondContext : osn.IVideo; // Initialize OBS process before(async () => { @@ -114,65 +114,70 @@ describe(testName, () => { } it('Start Dual Output with advanced recording', async function() { - if (obs.isDarwin()) { + if (obs.isOnDarwinIntelCI()) { this.skip(); } const recording = osn.AdvancedRecordingFactory.create(); - 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.overwrite = false; - recording.noSpace = false; - recording.video = obs.defaultVideoContext; - const track1 = osn.AudioTrackFactory.create(160, 'track1'); - osn.AudioTrackFactory.setAtIndex(track1, 1); - recording.signalHandler = (signal) => { obs.signals.push(signal) }; - - const recording2 = osn.AdvancedRecordingFactory.create(); - 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.overwrite = false; - recording2.noSpace = false; - recording2.video = secondContext; - const track2 = osn.AudioTrackFactory.create(160, 'track2'); - osn.AudioTrackFactory.setAtIndex(track2, 2); - recording2.signalHandler = (signal) => { obs.signals.push(signal) }; - - recording.start(); - await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Start, ETestErrorMsg.RecordingOutput); - - recording2.start(); - await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Start, ETestErrorMsg.RecordingOutput); - - await sleep(1500); - - recording.stop(); - await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stopping, ETestErrorMsg.RecordingOutput); - await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stop, ETestErrorMsg.RecordingOutput); - await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Wrote, ETestErrorMsg.RecordingOutput); - - recording2.stop(); - await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stopping, ETestErrorMsg.RecordingOutput); - 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(); + const recording2= osn.AdvancedRecordingFactory.create(); + try { + 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.overwrite = false; + recording.noSpace = false; + recording.video = obs.defaultVideoContext; + const track1 = osn.AudioTrackFactory.create(160, 'track1'); + osn.AudioTrackFactory.setAtIndex(track1, 1); + recording.signalHandler = (signal) => { obs.signals.push(signal) }; + + 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.overwrite = false; + recording2.noSpace = false; + recording2.video = secondContext; + const track2 = osn.AudioTrackFactory.create(160, 'track2'); + osn.AudioTrackFactory.setAtIndex(track2, 2); + recording2.signalHandler = (signal) => { obs.signals.push(signal) }; + + recording.start(); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Start, ETestErrorMsg.RecordingOutput); + + recording2.start(); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Start, ETestErrorMsg.RecordingOutput); + + await sleep(1500); + + recording.stop(); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stopping, ETestErrorMsg.RecordingOutput); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stop, ETestErrorMsg.RecordingOutput); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Wrote, ETestErrorMsg.RecordingOutput); + + recording2.stop(); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stopping, ETestErrorMsg.RecordingOutput); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stop, ETestErrorMsg.RecordingOutput); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Wrote, ETestErrorMsg.RecordingOutput); + } + finally { + if (recording) { + const recordingEncoder = recording.videoEncoder; + osn.AdvancedRecordingFactory.destroy(recording); + recordingEncoder?.release(); + } + if (recording2) { + const recording2Encoder = recording2.videoEncoder; + osn.AdvancedRecordingFactory.destroy(recording2); + recording2Encoder?.release(); + } + } }); it('Dual canvas recording avoids name collision', async function() { - if (obs.isDarwin()) { + if (obs.isOnDarwinIntelCI()) { this.skip(); } - const outputDir = path.join(path.normalize(__dirname), '..', 'osnData'); const sharedFilename = 'dual-output-collision-' + randomUUID(); const firstExpectedFile = path.join(outputDir, `${sharedFilename}.mp4`); @@ -203,45 +208,49 @@ describe(testName, () => { osn.AudioTrackFactory.setAtIndex(track2, 2); recording2.signalHandler = (signal) => { obs.signals.push(signal) }; - recording.start(); - await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Start, ETestErrorMsg.RecordingOutput); - await waitForFile(firstExpectedFile); - - recording2.start(); - await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Start, ETestErrorMsg.RecordingOutput); - - await sleep(1500); - - recording.stop(); - await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stopping, ETestErrorMsg.RecordingOutput); - await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stop, ETestErrorMsg.RecordingOutput); - await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Wrote, ETestErrorMsg.RecordingOutput); - - recording2.stop(); - await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stopping, ETestErrorMsg.RecordingOutput); - await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stop, ETestErrorMsg.RecordingOutput); - await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Wrote, ETestErrorMsg.RecordingOutput); - - const firstLastFile = path.basename(recording.lastFile()); - const secondLastFile = path.basename(recording2.lastFile()); - expect([firstLastFile, secondLastFile]).to.have.members([ - `${sharedFilename}.mp4`, - `${sharedFilename} (2).mp4`, - ]); - expect(firstLastFile).to.not.match(/\d+x\d+-\d{2}\.mp4$/, 'Unexpected resolution suffix in first recording filename'); - expect(secondLastFile).to.not.match(/\d+x\d+-\d{2}\.mp4$/, 'Unexpected resolution suffix in second recording filename'); - - const recordingEncoder = recording.videoEncoder; - osn.AdvancedRecordingFactory.destroy(recording); - recordingEncoder.release(); - - const recording2Encoder = recording2.videoEncoder; - osn.AdvancedRecordingFactory.destroy(recording2); - recording2Encoder.release(); + try { + recording.start(); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Start, ETestErrorMsg.RecordingOutput); + await waitForFile(firstExpectedFile); + + recording2.start(); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Start, ETestErrorMsg.RecordingOutput); + + await sleep(1500); + + recording.stop(); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stopping, ETestErrorMsg.RecordingOutput); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stop, ETestErrorMsg.RecordingOutput); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Wrote, ETestErrorMsg.RecordingOutput); + + recording2.stop(); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stopping, ETestErrorMsg.RecordingOutput); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stop, ETestErrorMsg.RecordingOutput); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Wrote, ETestErrorMsg.RecordingOutput); + + const firstLastFile = path.basename(recording.lastFile()); + const secondLastFile = path.basename(recording2.lastFile()); + expect([firstLastFile, secondLastFile]).to.have.members([ + `${sharedFilename}.mp4`, + `${sharedFilename} (2).mp4`, + ]); + expect(firstLastFile).to.not.match(/\d+x\d+-\d{2}\.mp4$/, 'Unexpected resolution suffix in first recording filename'); + expect(secondLastFile).to.not.match(/\d+x\d+-\d{2}\.mp4$/, 'Unexpected resolution suffix in second recording filename'); + } + finally + { + 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() { - if (obs.isDarwin()) { + if (obs.isOnDarwinIntelCI()) { this.skip(); } const returnSource = osn.Global.getOutputSource(0); @@ -297,258 +306,282 @@ describe(testName, () => { recording2.signalHandler = (signal) => { obs.signals.push(signal) }; recording.start(); - await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Start, ETestErrorMsg.RecordingOutput); - - recording2.start(); - await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Start, ETestErrorMsg.RecordingOutput); - - await sleep(1500); + try { + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Start, ETestErrorMsg.RecordingOutput); - recording.stop(); - await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stopping, ETestErrorMsg.RecordingOutput); - await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stop, ETestErrorMsg.RecordingOutput); - await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Wrote, ETestErrorMsg.RecordingOutput); + recording2.start(); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Start, ETestErrorMsg.RecordingOutput); - recording2.stop(); - await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stopping, ETestErrorMsg.RecordingOutput); - await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stop, ETestErrorMsg.RecordingOutput); - await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Wrote, ETestErrorMsg.RecordingOutput); + await sleep(1500); - const recordingEncoder = recording.videoEncoder; - osn.AdvancedRecordingFactory.destroy(recording); - recordingEncoder.release(); + recording.stop(); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stopping, ETestErrorMsg.RecordingOutput); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stop, ETestErrorMsg.RecordingOutput); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Wrote, ETestErrorMsg.RecordingOutput); - const recording2Encoder = recording2.videoEncoder; - osn.AdvancedRecordingFactory.destroy(recording2); - recording2Encoder.release(); - - osn.Global.setOutputSource(0, returnSource); - - sceneItem1.source.release(); - sceneItem1.remove(); - sceneItem2.remove(); - scene.release(); + recording2.stop(); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stopping, ETestErrorMsg.RecordingOutput); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stop, ETestErrorMsg.RecordingOutput); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Wrote, ETestErrorMsg.RecordingOutput); + } + finally { + if (sceneItem1) { + if (sceneItem1.source) { + sceneItem1.source.release(); + } + sceneItem1.remove(); + } + if (sceneItem2) { + if (sceneItem2.source) { + sceneItem2.source.release(); + } + sceneItem2.remove(); + } + if (scene) { + scene.release(); + } + osn.Global.setOutputSource(0, returnSource); + const recordingEncoder = recording.videoEncoder; + osn.AdvancedRecordingFactory.destroy(recording); + if (recordingEncoder)recordingEncoder.release(); + + const recording2Encoder = recording2.videoEncoder; + osn.AdvancedRecordingFactory.destroy(recording2); + if (recording2Encoder)recording2Encoder.release(); + } }); it('Start Dual Output with advanced recording and audio scene items', async function() { - if (obs.isDarwin()) { - this.skip(); - } const returnSource = osn.Global.getOutputSource(0); const recording = osn.AdvancedRecordingFactory.create(); - 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-5'); - recording.overwrite = false; - recording.noSpace = false; - recording.mixer = 1; - recording.video = obs.defaultVideoContext; - const track1 = osn.AudioTrackFactory.create(160, 'track1'); - osn.AudioTrackFactory.setAtIndex(track1, 1); - recording.signalHandler = (signal) => { obs.signals.push(signal) }; - const recording2 = osn.AdvancedRecordingFactory.create(); - 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-6'); - recording2.overwrite = false; - recording2.noSpace = false; - recording2.mixer = 2; - recording2.video = secondContext; - const track2 = osn.AudioTrackFactory.create(160, 'track2'); - osn.AudioTrackFactory.setAtIndex(track2, 2); - recording2.signalHandler = (signal) => { obs.signals.push(signal) }; - - // Getting scene - let secondSceneName = 'scene_' + randomUUID(); - const scene = osn.SceneFactory.create(secondSceneName); - osn.Global.setOutputSource(0, scene); - - // Getting source - let settings: ISettings = {}; - settings = inputSettings.ffmpegSource; - settings['volume'] = 100; - settings['local_file'] = path.join( media_path, "sleek.mp3" ); - settings['looping'] = true; - let firstSourceName = EOBSInputTypes.FFMPEGSource.toString() + '_' + randomUUID(); - const firstsource = osn.InputFactory.create(EOBSInputTypes.FFMPEGSource, firstSourceName, settings); - - // Adding input source to scene to create scene item - const sceneItem1 = scene.add(firstsource); - sceneItem1.video = obs.defaultVideoContext; - sceneItem1.visible = true; - let position1: IVec2 = { x: 1100, y: 200 }; - sceneItem1.position = position1; - - settings['local_file'] = path.join( media_path, "echoes.mp3" ); - let secondSourceName = EOBSInputTypes.FFMPEGSource.toString() + '_' + randomUUID(); - const secondsource = osn.InputFactory.create(EOBSInputTypes.FFMPEGSource, secondSourceName, settings); - - const sceneItem2 = scene.add(secondsource); - sceneItem2.video = secondContext; - sceneItem2.visible = true; - let position2: IVec2 = { x: 500, y: 1200 }; - sceneItem2.position = position2; - - await sleep(1500); - - recording.start(); - await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Start, ETestErrorMsg.RecordingOutput); - - recording2.start(); - await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Start, ETestErrorMsg.RecordingOutput); - - await sleep(1500); - - recording.stop(); - await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stopping, ETestErrorMsg.RecordingOutput); - await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stop, ETestErrorMsg.RecordingOutput); - await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Wrote, ETestErrorMsg.RecordingOutput); - - recording2.stop(); - await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stopping, ETestErrorMsg.RecordingOutput); - 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(); - - sceneItem2.source.release(); - sceneItem2.remove(); - - scene.release(); + let scene: osn.IScene | undefined; + let sceneItem1: osn.ISceneItem | undefined; + let sceneItem2: osn.ISceneItem | undefined; + try { + 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-5'); + recording.overwrite = false; + recording.noSpace = false; + recording.mixer = 1; + recording.video = obs.defaultVideoContext; + const track1 = osn.AudioTrackFactory.create(160, 'track1'); + osn.AudioTrackFactory.setAtIndex(track1, 1); + recording.signalHandler = (signal) => { obs.signals.push(signal) }; + + 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-6'); + recording2.overwrite = false; + recording2.noSpace = false; + recording2.mixer = 2; + recording2.video = secondContext; + const track2 = osn.AudioTrackFactory.create(160, 'track2'); + osn.AudioTrackFactory.setAtIndex(track2, 2); + recording2.signalHandler = (signal) => { obs.signals.push(signal) }; + + // Getting scene + let secondSceneName = 'scene_' + randomUUID(); + scene = osn.SceneFactory.create(secondSceneName); + osn.Global.setOutputSource(0, scene); + + // Getting source + let settings: ISettings = {}; + settings = inputSettings.ffmpegSource; + settings['volume'] = 100; + settings['local_file'] = path.join( media_path, "sleek.mp3" ); + settings['looping'] = true; + let firstSourceName = EOBSInputTypes.FFMPEGSource.toString() + '_' + randomUUID(); + const firstsource = osn.InputFactory.create(EOBSInputTypes.FFMPEGSource, firstSourceName, settings); + + // Adding input source to scene to create scene item + sceneItem1 = scene.add(firstsource); + sceneItem1.video = obs.defaultVideoContext; + sceneItem1.visible = true; + let position1: IVec2 = { x: 1100, y: 200 }; + sceneItem1.position = position1; + + settings['local_file'] = path.join( media_path, "echoes.mp3" ); + let secondSourceName = EOBSInputTypes.FFMPEGSource.toString() + '_' + randomUUID(); + const secondsource = osn.InputFactory.create(EOBSInputTypes.FFMPEGSource, secondSourceName, settings); + + sceneItem2 = scene.add(secondsource); + sceneItem2.video = secondContext; + sceneItem2.visible = true; + let position2: IVec2 = { x: 500, y: 1200 }; + sceneItem2.position = position2; + + await sleep(1500); + + recording.start(); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Start, ETestErrorMsg.RecordingOutput); + + recording2.start(); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Start, ETestErrorMsg.RecordingOutput); + + await sleep(1500); + + recording.stop(); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stopping, ETestErrorMsg.RecordingOutput); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stop, ETestErrorMsg.RecordingOutput); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Wrote, ETestErrorMsg.RecordingOutput); + + recording2.stop(); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stopping, ETestErrorMsg.RecordingOutput); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stop, ETestErrorMsg.RecordingOutput); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Wrote, ETestErrorMsg.RecordingOutput); + } + catch (error) { + logInfo(testName, 'Error occurred during test execution: ' + error); + throw error; + } + finally { + if (sceneItem1) { + sceneItem1.source.release(); + sceneItem1.remove(); + } + + if (sceneItem2) { + sceneItem2.source.release(); + sceneItem2.remove(); + } + scene?.release(); + + osn.Global.setOutputSource(0, returnSource); + const recordingEncoder = recording.videoEncoder; + osn.AdvancedRecordingFactory.destroy(recording); + + const recording2Encoder = recording2.videoEncoder; + osn.AdvancedRecordingFactory.destroy(recording2); + recordingEncoder?.release(); + recording2Encoder?.release(); + } }); it('Start Dual Output with simple recording and audio scene items', async function() { - if (obs.isDarwin()) { - this.skip(); - } const returnSource = osn.Global.getOutputSource(0); 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-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; - recording.noSpace = false; - recording.quality = ERecordingQuality.HighQuality; - recording.video = obs.defaultVideoContext; - const track1 = osn.AudioTrackFactory.create(160, 'track1'); - osn.AudioTrackFactory.setAtIndex(track1, 1); - recording.signalHandler = (signal) => { obs.signals.push(signal) }; - 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-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; - recording2.noSpace = false; - recording2.quality = ERecordingQuality.HighQuality; - recording2.video = secondContext; - const track2 = osn.AudioTrackFactory.create(160, 'track2'); - osn.AudioTrackFactory.setAtIndex(track2, 2); - recording2.signalHandler = (signal) => { obs.signals.push(signal) }; - - // Getting scene - let secondSceneName = 'scene_' + randomUUID(); - const scene = osn.SceneFactory.create(secondSceneName); - osn.Global.setOutputSource(0, scene); - - // Getting source - let settings: ISettings = {}; - settings = inputSettings.ffmpegSource; - settings['volume'] = 100; - settings['local_file'] = path.join( media_path, "sleek.mp3" ); - settings['looping'] = true; - let firstSourceName = EOBSInputTypes.FFMPEGSource.toString() + '_' + randomUUID(); - const firstsource = osn.InputFactory.create(EOBSInputTypes.FFMPEGSource, firstSourceName, settings); - - // Adding input source to scene to create scene item - const sceneItem1 = scene.add(firstsource); - sceneItem1.video = obs.defaultVideoContext; - sceneItem1.visible = true; - let position1: IVec2 = { x: 1100, y: 200 }; - sceneItem1.position = position1; - - settings['local_file'] = path.join( media_path, "echoes.mp3" ); - let secondSourceName = EOBSInputTypes.FFMPEGSource.toString() + '_' + randomUUID(); - const secondsource = osn.InputFactory.create(EOBSInputTypes.FFMPEGSource, secondSourceName, settings); - - const sceneItem2 = scene.add(secondsource); - sceneItem2.video = secondContext; - sceneItem2.visible = true; - let position2: IVec2 = { x: 500, y: 1200 }; - sceneItem2.position = position2; - - await sleep(1500); - - recording.start(); - await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Start, ETestErrorMsg.RecordingOutput); - - recording2.start(); - await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Start, ETestErrorMsg.RecordingOutput); - - await sleep(1500); - - recording.stop(); - await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stopping, ETestErrorMsg.RecordingOutput); - await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stop, ETestErrorMsg.RecordingOutput); - await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Wrote, ETestErrorMsg.RecordingOutput); - - recording2.stop(); - await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stopping, ETestErrorMsg.RecordingOutput); - 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); - - sceneItem1.source.release(); - sceneItem1.remove(); - - sceneItem2.source.release(); - sceneItem2.remove(); - - scene.release(); - - recordingEncoder.release(); - recording2Encoder.release(); - recordingAudioEncoder.release(); - recording2AudioEncoder.release(); + let scene: osn.IScene | undefined; + let sceneItem1: osn.ISceneItem | undefined; + let sceneItem2: osn.ISceneItem | undefined; + try { + recording.path = path.join(path.normalize(__dirname), '..', 'osnData'); + recording.format = ERecordingFormat.MP4; + 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; + recording.noSpace = false; + recording.quality = ERecordingQuality.HighQuality; + recording.video = obs.defaultVideoContext; + const track1 = osn.AudioTrackFactory.create(160, 'track1'); + osn.AudioTrackFactory.setAtIndex(track1, 1); + recording.signalHandler = (signal) => { obs.signals.push(signal) }; + + recording2.path = path.join(path.normalize(__dirname), '..', 'osnData'); + recording2.format = ERecordingFormat.MP4; + 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; + recording2.noSpace = false; + recording2.quality = ERecordingQuality.HighQuality; + recording2.video = secondContext; + const track2 = osn.AudioTrackFactory.create(160, 'track2'); + osn.AudioTrackFactory.setAtIndex(track2, 2); + recording2.signalHandler = (signal) => { obs.signals.push(signal) }; + + // Getting scene + let secondSceneName = 'scene_' + randomUUID(); + scene = osn.SceneFactory.create(secondSceneName); + osn.Global.setOutputSource(0, scene); + + // Getting source + let settings: ISettings = {}; + settings = inputSettings.ffmpegSource; + settings['volume'] = 100; + settings['local_file'] = path.join( media_path, "sleek.mp3" ); + settings['looping'] = true; + let firstSourceName = EOBSInputTypes.FFMPEGSource.toString() + '_' + randomUUID(); + const firstsource = osn.InputFactory.create(EOBSInputTypes.FFMPEGSource, firstSourceName, settings); + + // Adding input source to scene to create scene item + sceneItem1 = scene.add(firstsource); + sceneItem1.video = obs.defaultVideoContext; + sceneItem1.visible = true; + let position1: IVec2 = { x: 1100, y: 200 }; + sceneItem1.position = position1; + + settings['local_file'] = path.join( media_path, "echoes.mp3" ); + let secondSourceName = EOBSInputTypes.FFMPEGSource.toString() + '_' + randomUUID(); + const secondsource = osn.InputFactory.create(EOBSInputTypes.FFMPEGSource, secondSourceName, settings); + + sceneItem2 = scene.add(secondsource); + sceneItem2.video = secondContext; + sceneItem2.visible = true; + let position2: IVec2 = { x: 500, y: 1200 }; + sceneItem2.position = position2; + + await sleep(1500); + + recording.start(); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Start, ETestErrorMsg.RecordingOutput); + + recording2.start(); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Start, ETestErrorMsg.RecordingOutput); + + await sleep(1500); + + recording.stop(); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stopping, ETestErrorMsg.RecordingOutput); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stop, ETestErrorMsg.RecordingOutput); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Wrote, ETestErrorMsg.RecordingOutput); + + recording2.stop(); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stopping, ETestErrorMsg.RecordingOutput); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Stop, ETestErrorMsg.RecordingOutput); + await handleStreamSignals(EOBSOutputType.Recording, EOBSOutputSignal.Wrote, ETestErrorMsg.RecordingOutput); + } catch (error) { + logInfo(testName, 'Error occurred during test execution: ' + error); + throw error; + } + finally { + if (sceneItem1) { + sceneItem1.source.release(); + sceneItem1.remove(); + } + + if (sceneItem2) { + sceneItem2.source.release(); + sceneItem2.remove(); + } + scene?.release(); + + osn.Global.setOutputSource(0, returnSource); + const recordingEncoder = recording.videoEncoder; + const recordingAudioEncoder = recording.audioEncoder; + osn.SimpleRecordingFactory.destroy(recording); + + const recording2Encoder = recording2.videoEncoder; + const recording2AudioEncoder = recording2.audioEncoder; + osn.SimpleRecordingFactory.destroy(recording2); + recordingEncoder?.release(); + recording2Encoder?.release(); + recordingAudioEncoder?.release(); + recording2AudioEncoder?.release(); + } }); it('Start Dual Output with legacy streaming to two services', async function() { - if (obs.isDarwin()) { - this.skip(); - } // Preparing environment obs.setSetting(EOBSSettingsCategories.Output, 'Mode', 'Simple'); obs.setSetting(EOBSSettingsCategories.Output, 'StreamEncoder', obs.os === 'win32' ? 'x264' : 'obs_x264'); @@ -591,9 +624,6 @@ describe(testName, () => { }); it('Start Dual Output with legacy streaming to two services and audio sources', async function() { - if (obs.isDarwin()) { - this.skip(); - } // Preparing environment obs.setSetting(EOBSSettingsCategories.Output, 'Mode', 'Simple'); obs.setSetting(EOBSSettingsCategories.Output, 'StreamEncoder', obs.os === 'win32' ? 'x264' : 'obs_x264'); diff --git a/tests/osn-tests/src/test_osn_enhanced_broadcasting_advanced_streaming.ts b/tests/osn-tests/src/test_osn_enhanced_broadcasting_advanced_streaming.ts index dd963b931..9c10f73a2 100644 --- a/tests/osn-tests/src/test_osn_enhanced_broadcasting_advanced_streaming.ts +++ b/tests/osn-tests/src/test_osn_enhanced_broadcasting_advanced_streaming.ts @@ -12,11 +12,11 @@ import path = require('path'); const testName = 'osn-enhanced-broadcasting-advanced-streaming'; -describe(testName, () => { +describe(testName, function() { let obs: OBSHandler; let hasTestFailed: boolean = false; const mediaPath = path.join(path.normalize(__dirname), '..', 'media'); - let secondContext: osn.IVideo = null; + let secondContext: osn.IVideo; // Initialize OBS process before(async() => { @@ -28,21 +28,26 @@ describe(testName, () => { // Reserving user from pool await obs.reserveUser(); - secondContext = osn.VideoFactory.create(); - const secondVideoInfo: osn.IVideoInfo = { - fpsNum: 60, - fpsDen: 2, - baseWidth: 720, - baseHeight: 1280, - outputWidth: 720, - outputHeight: 1280, - outputFormat: osn.EVideoFormat.NV12, - colorspace: osn.EColorSpace.CS709, - range: osn.ERangeType.Full, - scaleType: osn.EScaleType.Lanczos, - fpsType: osn.EFPSType.Fractional - }; - secondContext.video = secondVideoInfo; + if (!obs.isCI()) { + // Creating second video context for dual canvas streaming test. + secondContext = osn.VideoFactory.create(); + const secondVideoInfo: osn.IVideoInfo = { + fpsNum: 60, + fpsDen: 2, + baseWidth: 720, + baseHeight: 1280, + outputWidth: 720, + outputHeight: 1280, + outputFormat: osn.EVideoFormat.NV12, + colorspace: osn.EColorSpace.CS709, + range: osn.ERangeType.Full, + scaleType: osn.EScaleType.Lanczos, + fpsType: osn.EFPSType.Fractional + }; + secondContext.video = secondVideoInfo; + } else { + logInfo(testName, 'Skip AddVideoContext. Running in CI environment, skipping creation of second video context that requires GPU'); + } }); // Shutdown OBS process @@ -50,7 +55,9 @@ describe(testName, () => { // Releasing user got from pool await obs.releaseUser(); - secondContext.destroy(); + if (secondContext) { + secondContext.destroy(); + } obs.shutdown(); if (hasTestFailed === true) { @@ -73,10 +80,6 @@ describe(testName, () => { it('Enhanced Broadcasting Advanced Streaming rejects without crashing in CI', function() { // This test is CI only because CI is expected to hit a Twitch Enhanced Broadcasting rejection. - if (obs.isDarwin()) { - this.skip(); - } - if (!obs.isCI()) { this.skip(); } @@ -121,11 +124,8 @@ describe(testName, () => { }); it('Enhanced Broadcasting Advanced Streaming Single Canvas', async function() { - if (obs.isDarwin()) { - this.skip(); - } - if (obs.isCI()) { + logInfo(testName, 'Running in CI environment, skipping test that requires GPU'); // Skipping this test because CI server doesn't have GPU, but you can run it locally this.skip(); } @@ -219,11 +219,8 @@ describe(testName, () => { }); it('Enhanced Broadcasting Advanced Streaming Dual Canvas', async function() { - if (obs.isDarwin()) { - this.skip(); - } - if (obs.isCI()) { + logInfo(testName, 'Running in CI environment, skipping test that requires GPU'); // Skipping this test because CI server doesn't have GPU, but you can run it locally this.skip(); } diff --git a/tests/osn-tests/src/test_osn_enhanced_broadcasting_simple_streaming.ts b/tests/osn-tests/src/test_osn_enhanced_broadcasting_simple_streaming.ts index 0b9527603..2ea095696 100644 --- a/tests/osn-tests/src/test_osn_enhanced_broadcasting_simple_streaming.ts +++ b/tests/osn-tests/src/test_osn_enhanced_broadcasting_simple_streaming.ts @@ -12,11 +12,11 @@ import path = require('path'); const testName = 'osn-enhanced-broadcasting-simple-streaming'; -describe(testName, () => { +describe(testName, function() { let obs: OBSHandler; let hasTestFailed: boolean = false; const mediaPath = path.join(path.normalize(__dirname), '..', 'media'); - let secondContext: osn.IVideo = null; + let secondContext: osn.IVideo; // Initialize OBS process before(async() => { @@ -29,21 +29,25 @@ describe(testName, () => { // Reserving user from pool await obs.reserveUser(); - secondContext = osn.VideoFactory.create(); - const secondVideoInfo: osn.IVideoInfo = { - fpsNum: 60, - fpsDen: 2, - baseWidth: 720, - baseHeight: 1280, - outputWidth: 720, - outputHeight: 1280, - outputFormat: osn.EVideoFormat.NV12, - colorspace: osn.EColorSpace.CS709, - range: osn.ERangeType.Full, - scaleType: osn.EScaleType.Lanczos, - fpsType: osn.EFPSType.Fractional - }; - secondContext.video = secondVideoInfo; + if (!obs.isCI()) { + secondContext = osn.VideoFactory.create(); + const secondVideoInfo: osn.IVideoInfo = { + fpsNum: 60, + fpsDen: 2, + baseWidth: 720, + baseHeight: 1280, + outputWidth: 720, + outputHeight: 1280, + outputFormat: osn.EVideoFormat.NV12, + colorspace: osn.EColorSpace.CS709, + range: osn.ERangeType.Full, + scaleType: osn.EScaleType.Lanczos, + fpsType: osn.EFPSType.Fractional + }; + secondContext.video = secondVideoInfo; + } else { + logInfo(testName, 'Skip AddVideoContext. Running in CI environment, skipping creation of second video context that requires GPU'); + } }); // Shutdown OBS process @@ -70,10 +74,6 @@ describe(testName, () => { }); it('Enhanced Broadcasting Simple Streaming honors stream delay', async function() { - if (obs.isDarwin()) { - this.skip(); - } - if (obs.isCI()) { // Skipping this test because CI server doesn't have GPU, but you can run it locally this.skip(); @@ -160,10 +160,6 @@ describe(testName, () => { }); it('Enhanced Broadcasting Simple Streaming Single Canvas', async function() { - if (obs.isDarwin()) { - this.skip(); - } - if (obs.isCI()) { // Skipping this test because CI server doesn't have GPU, but you can run it locally this.skip(); @@ -256,10 +252,6 @@ describe(testName, () => { }); it('Enhanced Broadcasting Simple Streaming Dual Canvas', async function() { - if (obs.isDarwin()) { - this.skip(); - } - if (obs.isCI()) { // Skipping this test because CI server doesn't have GPU, but you can run it locally this.skip(); diff --git a/tests/osn-tests/src/test_osn_input.ts b/tests/osn-tests/src/test_osn_input.ts index 0ee9d8e89..622b3aa00 100644 --- a/tests/osn-tests/src/test_osn_input.ts +++ b/tests/osn-tests/src/test_osn_input.ts @@ -390,9 +390,6 @@ describe(testName, () => { }); it('Add video filter to video sources', function() { - if (obs.isDarwin()) { - this.skip(); - } let videoFilters: string[] = []; let addedFilters: string[] = []; diff --git a/tests/osn-tests/src/test_osn_simple_recording.ts b/tests/osn-tests/src/test_osn_simple_recording.ts index e0909c95a..214a52cb5 100644 --- a/tests/osn-tests/src/test_osn_simple_recording.ts +++ b/tests/osn-tests/src/test_osn_simple_recording.ts @@ -13,7 +13,7 @@ import path = require('path'); const testName = 'osn-simple-recording'; -describe(testName, () => { +describe(testName, function() { let obs: OBSHandler; let hasTestFailed: boolean = false; // Initialize OBS process @@ -52,62 +52,67 @@ describe(testName, () => { it('Create simple recording', async () => { const recording = osn.SimpleRecordingFactory.create(); - expect(recording).to.not.equal( - undefined, "Error while creating the simple recording output"); - - expect(recording.path).to.equal( - '', "Invalid path default value"); - expect(recording.format).to.equal( - ERecordingFormat.MP4, "Invalid format default value"); - expect(recording.fileFormat).to.equal( - '%CCYY-%MM-%DD %hh-%mm-%ss', "Invalid fileFormat default value"); - expect(recording.overwrite).to.equal( - false, "Invalid overwrite default value"); - expect(recording.noSpace).to.equal( - false, "Invalid noSpace default value"); - expect(recording.muxerSettings).to.equal( - '', "Invalid muxerSettings default value"); - expect(recording.quality).to.equal( - osn.ERecordingQuality.Stream, "Invalid quality default value"); - expect(recording.lowCPU).to.equal( - false, "Invalid lowCPU default value"); - - recording.path = path.join(path.normalize(__dirname), '..', 'osnData'); - recording.format = ERecordingFormat.MOV; - recording.quality = ERecordingQuality.HighQuality; - recording.video = obs.defaultVideoContext; - recording.videoEncoder = - osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-recording-1'); - recording.lowCPU = true; - recording.audioEncoder = osn.AudioEncoderFactory.create("ffmpeg_aac", "audio-encoder-simple-recording-1"); - recording.overwrite = true; - recording.noSpace = false; - - expect(recording.path).to.equal( - path.join(path.normalize(__dirname), '..', 'osnData'), "Invalid path value"); - expect(recording.format).to.equal( - ERecordingFormat.MOV, "Invalid format value"); - expect(recording.quality).to.equal( - osn.ERecordingQuality.HighQuality, "Invalid quality value"); - expect(recording.lowCPU).to.equal( - true, "Invalid lowCPU value"); - expect(recording.overwrite).to.equal( - true, "Invalid overwrite value"); - 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(); + try { + expect(recording).to.not.equal( + undefined, "Error while creating the simple recording output"); + + expect(recording.path).to.equal( + '', "Invalid path default value"); + expect(recording.format).to.equal( + ERecordingFormat.MP4, "Invalid format default value"); + expect(recording.fileFormat).to.equal( + '%CCYY-%MM-%DD %hh-%mm-%ss', "Invalid fileFormat default value"); + expect(recording.overwrite).to.equal( + false, "Invalid overwrite default value"); + expect(recording.noSpace).to.equal( + false, "Invalid noSpace default value"); + expect(recording.muxerSettings).to.equal( + '', "Invalid muxerSettings default value"); + expect(recording.quality).to.equal( + osn.ERecordingQuality.Stream, "Invalid quality default value"); + expect(recording.lowCPU).to.equal( + false, "Invalid lowCPU default value"); + + recording.path = path.join(path.normalize(__dirname), '..', 'osnData'); + recording.format = ERecordingFormat.MOV; + recording.quality = ERecordingQuality.HighQuality; + recording.video = obs.defaultVideoContext; + recording.videoEncoder = + osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-recording-1'); + recording.lowCPU = true; + recording.audioEncoder = osn.AudioEncoderFactory.create("ffmpeg_aac", "audio-encoder-simple-recording-1"); + recording.overwrite = true; + recording.noSpace = false; + + expect(recording.path).to.equal( + path.join(path.normalize(__dirname), '..', 'osnData'), "Invalid path value"); + expect(recording.format).to.equal( + ERecordingFormat.MOV, "Invalid format value"); + expect(recording.quality).to.equal( + osn.ERecordingQuality.HighQuality, "Invalid quality value"); + expect(recording.lowCPU).to.equal( + true, "Invalid lowCPU value"); + expect(recording.overwrite).to.equal( + true, "Invalid overwrite value"); + expect(recording.noSpace).to.equal( + false, "Invalid noSpace value"); + } finally { + const videoEncoder = recording.videoEncoder; + const audioEncoder = recording.audioEncoder; + osn.SimpleRecordingFactory.destroy(recording); + if (videoEncoder) { + videoEncoder.release(); + } + if (audioEncoder) { + audioEncoder.release(); + } + } }); it('Start simple recording - Stream', async function () { - if (obs.isDarwin()) { - this.skip(); - } const recording = osn.SimpleRecordingFactory.create(); + const stream = osn.SimpleStreamingFactory.create(); + try { recording.path = path.join(path.normalize(__dirname), '..', 'osnData'); recording.format = ERecordingFormat.MP4; recording.quality = ERecordingQuality.Stream; @@ -117,7 +122,6 @@ describe(testName, () => { recording.video = obs.defaultVideoContext; recording.signalHandler = (signal) => {obs.signals.push(signal)}; - const stream = osn.SimpleStreamingFactory.create(); stream.video = obs.defaultVideoContext; stream.videoEncoder = osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-stream-1'); @@ -231,20 +235,19 @@ describe(testName, () => { EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); 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(); + } finally { + const streamEncoder = stream.videoEncoder; + const audioEncoder = stream.audioEncoder; + osn.SimpleRecordingFactory.destroy(recording); + osn.SimpleStreamingFactory.destroy(stream); + if (streamEncoder) streamEncoder.release(); + if (audioEncoder) audioEncoder.release(); + } }); it('Start simple recording - HighQuality', async function () { - if (obs.isDarwin()) { - this.skip(); - } const recording = osn.SimpleRecordingFactory.create(); + try { recording.path = path.join(path.normalize(__dirname), '..', 'osnData'); recording.format = ERecordingFormat.MP4; recording.quality = ERecordingQuality.HighQuality; @@ -308,19 +311,16 @@ describe(testName, () => { EOBSOutputType.Recording, GetErrorMessage(ETestErrorMsg.RecordingOutput)); 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(); + } finally { + const videoEncoder = recording.videoEncoder; + const audioEncoder = recording.audioEncoder; + osn.SimpleRecordingFactory.destroy(recording); + if (videoEncoder) videoEncoder.release(); + if (audioEncoder) audioEncoder.release(); + } }); it('Start simple recording - mpegts', async function () { - if (obs.isDarwin()) { - this.skip(); - } - const formats: ERecordingFormat[] = [ ERecordingFormat.MP4, ERecordingFormat.MOV, @@ -331,208 +331,206 @@ describe(testName, () => { ]; 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 - ) - ); + try { + 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) + ); + } finally { + // cleanup for this format + const videoEncoder = recording.videoEncoder; + const audioEncoder = recording.audioEncoder; + osn.SimpleRecordingFactory.destroy(recording); + if (videoEncoder) videoEncoder.release(); + if (audioEncoder) audioEncoder.release(); } - - 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 () { - if (obs.isDarwin()) { - this.skip(); - } const recording = osn.SimpleRecordingFactory.create(); - recording.path = path.join(path.normalize(__dirname), '..', 'osnData'); - recording.format = ERecordingFormat.MP4; - recording.quality = ERecordingQuality.HigherQuality; - recording.video = obs.defaultVideoContext; - recording.videoEncoder = - osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-recording-3'); - recording.lowCPU = false; - 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)}; - - 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(500); - - 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)); + try { + recording.path = path.join(path.normalize(__dirname), '..', 'osnData'); + recording.format = ERecordingFormat.MP4; + recording.quality = ERecordingQuality.HigherQuality; + recording.video = obs.defaultVideoContext; + recording.videoEncoder = + osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-recording-3'); + recording.lowCPU = false; + 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)}; + + 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(500); + + 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)); + } finally { + const videoEncoder = recording.videoEncoder; + const audioEncoder = recording.audioEncoder; + osn.SimpleRecordingFactory.destroy(recording); + if (videoEncoder) videoEncoder.release(); + if (audioEncoder) audioEncoder.release(); } - - expect(signalInfo.type).to.equal( - EOBSOutputType.Recording, GetErrorMessage(ETestErrorMsg.RecordingOutput)); - 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 () { - if (obs.isDarwin()) { - this.skip(); - } const recording = osn.SimpleRecordingFactory.create(); + try { recording.path = path.join(path.normalize(__dirname), '..', 'osnData'); recording.format = ERecordingFormat.MP4; recording.quality = ERecordingQuality.Lossless; @@ -580,8 +578,9 @@ describe(testName, () => { EOBSOutputType.Recording, GetErrorMessage(ETestErrorMsg.RecordingOutput)); expect(signalInfo.signal).to.equal( EOBSOutputSignal.Stop, GetErrorMessage(ETestErrorMsg.RecordingOutput)); - - osn.SimpleRecordingFactory.destroy(recording); + } finally { + osn.SimpleRecordingFactory.destroy(recording); + } }); it('Create a browser source and test messages', async function () { @@ -617,69 +616,73 @@ describe(testName, () => { sceneItem1.visible = true; const recording = osn.SimpleRecordingFactory.create(); - recording.path = path.join(path.normalize(__dirname), '..', 'osnData'); - 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('ffmpeg_aac', 'audio-encoder-browser-rec') - recording.overwrite = true; - recording.noSpace = false; - recording.signalHandler = (sig) => obs.signals.push(sig); - - obs.setSourceMessageListener(); - recording.start(); - let sig = await obs.getNextSignalInfo(EOBSOutputType.Recording,EOBSOutputSignal.Start,); - - if (sig.signal === EOBSOutputSignal.Stop) { - throw Error(GetErrorMessage(ETestErrorMsg.RecordOutputDidNotStart,sig.code.toString(),sig.error,),); + try { + recording.path = path.join(path.normalize(__dirname), '..', 'osnData'); + 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('ffmpeg_aac', 'audio-encoder-browser-rec') + recording.overwrite = true; + recording.noSpace = false; + recording.signalHandler = (sig) => obs.signals.push(sig); + + obs.setSourceMessageListener(); + recording.start(); + let sig = await obs.getNextSignalInfo(EOBSOutputType.Recording,EOBSOutputSignal.Start,); + + if (sig.signal === EOBSOutputSignal.Stop) { + throw Error(GetErrorMessage(ETestErrorMsg.RecordOutputDidNotStart,sig.code.toString(),sig.error,),); + } + + settings['message'] = "First message"; + browserInput.sendMessage(settings); + + await sleep(1500); + settings['message'] = "Second message after timeout"; + browserInput.sendMessage(settings); + + await sleep(1500); + settings['message'] = "Third message after timeout"; + browserInput.sendMessage(settings); + + await sleep(1500); + sceneItem1.visible = false; + await sleep(1500); + sceneItem1.visible = true; + + await sleep(1500); + recording.stop(); + sig = await obs.getNextSignalInfo(EOBSOutputType.Recording, EOBSOutputSignal.Stopping); + expect(sig.signal).to.equal(EOBSOutputSignal.Stopping, GetErrorMessage(ETestErrorMsg.RecordingOutput)); + + sig = await obs.getNextSignalInfo(EOBSOutputType.Recording,EOBSOutputSignal.Stop,); + + if (sig.code !== 0) { + throw Error(GetErrorMessage(ETestErrorMsg.RecordOutputStoppedWithError,sig.code.toString(),sig.error,),); + } + expect(sig.signal).to.equal(EOBSOutputSignal.Stop,GetErrorMessage(ETestErrorMsg.RecordingOutput),); + + sig = await obs.getNextSignalInfo(EOBSOutputType.Recording,EOBSOutputSignal.Wrote,); + if (sig.code !== 0) { + throw Error(GetErrorMessage(ETestErrorMsg.RecordOutputStoppedWithError,sig.code.toString(),sig.error,),); + } + expect(sig.signal).to.equal(EOBSOutputSignal.Wrote,GetErrorMessage(ETestErrorMsg.RecordingOutput),); + } finally { + + const videoEncoder = recording.videoEncoder; + const audioEncoder = recording.audioEncoder; + osn.SimpleRecordingFactory.destroy(recording); + if (videoEncoder) videoEncoder.release(); + if (audioEncoder) audioEncoder.release(); + + browserInput.release(); + sceneItem1.source.release(); + sceneItem1.remove(); + + scene.release(); + obs.removeSourceMessageListener(); } - - settings['message'] = "First message"; - browserInput.sendMessage(settings); - - await sleep(1500); - settings['message'] = "Second message after timeout"; - browserInput.sendMessage(settings); - - await sleep(1500); - settings['message'] = "Third message after timeout"; - browserInput.sendMessage(settings); - - await sleep(1500); - sceneItem1.visible = false; - await sleep(1500); - sceneItem1.visible = true; - - await sleep(1500); - recording.stop(); - sig = await obs.getNextSignalInfo(EOBSOutputType.Recording, EOBSOutputSignal.Stopping); - expect(sig.signal).to.equal(EOBSOutputSignal.Stopping, GetErrorMessage(ETestErrorMsg.RecordingOutput)); - - sig = await obs.getNextSignalInfo(EOBSOutputType.Recording,EOBSOutputSignal.Stop,); - - if (sig.code !== 0) { - throw Error(GetErrorMessage(ETestErrorMsg.RecordOutputStoppedWithError,sig.code.toString(),sig.error,),); - } - expect(sig.signal).to.equal(EOBSOutputSignal.Stop,GetErrorMessage(ETestErrorMsg.RecordingOutput),); - - sig = await obs.getNextSignalInfo(EOBSOutputType.Recording,EOBSOutputSignal.Wrote,); - if (sig.code !== 0) { - throw Error(GetErrorMessage(ETestErrorMsg.RecordOutputStoppedWithError,sig.code.toString(),sig.error,),); - } - 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(); - - scene.release(); }); diff --git a/tests/osn-tests/src/test_osn_simple_replayBuffer.ts b/tests/osn-tests/src/test_osn_simple_replayBuffer.ts index e1043754f..b13539da1 100644 --- a/tests/osn-tests/src/test_osn_simple_replayBuffer.ts +++ b/tests/osn-tests/src/test_osn_simple_replayBuffer.ts @@ -104,9 +104,6 @@ describe(testName, () => { }); it('Start simple replay buffer - Use Recording', async function() { - if (obs.isDarwin()) { - this.skip(); - } const replayBuffer = osn.SimpleReplayBufferFactory.create(); replayBuffer.path = path.join(path.normalize(__dirname), '..', 'osnData'); replayBuffer.format = osn.ERecordingFormat.MP4; @@ -192,9 +189,6 @@ describe(testName, () => { }); it('Start simple replay buffer - Use Stream through Recording', async function() { - if (obs.isDarwin()) { - this.skip(); - } const replayBuffer = osn.SimpleReplayBufferFactory.create(); replayBuffer.path = path.join(path.normalize(__dirname), '..', 'osnData'); replayBuffer.format = osn.ERecordingFormat.MP4; diff --git a/tests/osn-tests/src/test_osn_simple_streaming.ts b/tests/osn-tests/src/test_osn_simple_streaming.ts index e3135f565..cba2a85e7 100644 --- a/tests/osn-tests/src/test_osn_simple_streaming.ts +++ b/tests/osn-tests/src/test_osn_simple_streaming.ts @@ -84,9 +84,6 @@ describe(testName, () => { }); it('Stream with missing video encoder', async function() { - if (obs.isDarwin()) { - this.skip(); - } const stream = osn.SimpleStreamingFactory.create(); stream.service = osn.ServiceFactory.legacySettings; stream.video = obs.defaultVideoContext; @@ -102,9 +99,6 @@ describe(testName, () => { }); it('Stream with missing audio encoder', async function() { - if (obs.isDarwin()) { - this.skip(); - } const stream = osn.SimpleStreamingFactory.create(); stream.videoEncoder = osn.VideoEncoderFactory.create('obs_x264', 'video-encoder'); stream.service = osn.ServiceFactory.legacySettings; @@ -120,9 +114,6 @@ describe(testName, () => { }); it('Stream with missing service', async function() { - if (obs.isDarwin()) { - this.skip(); - } const stream = osn.SimpleStreamingFactory.create(); stream.videoEncoder = osn.VideoEncoderFactory.create('obs_x264', 'video-encoder'); stream.video = obs.defaultVideoContext; @@ -138,9 +129,6 @@ describe(testName, () => { }); it('Stream with missing canvas', async function() { - if (obs.isDarwin()) { - this.skip(); - } const stream = osn.SimpleStreamingFactory.create(); stream.videoEncoder = osn.VideoEncoderFactory.create('obs_x264', 'video-encoder'); stream.service = osn.ServiceFactory.legacySettings; @@ -155,100 +143,97 @@ describe(testName, () => { }); it('Start streaming', async function() { - if (obs.isDarwin()) { - this.skip(); - } const stream = osn.SimpleStreamingFactory.create(); - stream.videoEncoder = - osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-simple-streaming-1'); - stream.service = osn.ServiceFactory.legacySettings; - stream.delay = - osn.DelayFactory.create(); - stream.reconnect = - osn.ReconnectFactory.create(); - stream.network = - osn.NetworkFactory.create(); - stream.video = obs.defaultVideoContext; - stream.audioEncoder = osn.AudioEncoderFactory.create("ffmpeg_aac", "audio-encoder-simple-streaming-4"); - stream.signalHandler = (signal) => {obs.signals.push(signal)}; - - stream.start(); + try { + stream.videoEncoder = + osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-simple-streaming-1'); + stream.service = osn.ServiceFactory.legacySettings; + stream.delay = + osn.DelayFactory.create(); + stream.reconnect = + osn.ReconnectFactory.create(); + stream.network = + osn.NetworkFactory.create(); + stream.video = obs.defaultVideoContext; + stream.audioEncoder = osn.AudioEncoderFactory.create("ffmpeg_aac", "audio-encoder-simple-streaming-4"); + stream.signalHandler = (signal) => {obs.signals.push(signal)}; - let signalInfo = await obs.getNextSignalInfo( - EOBSOutputType.Streaming, EOBSOutputSignal.Starting); - expect(signalInfo.type).to.equal( - EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); - expect(signalInfo.signal).to.equal( - EOBSOutputSignal.Starting, GetErrorMessage(ETestErrorMsg.StreamOutput)); + stream.start(); - signalInfo = await obs.getNextSignalInfo( - EOBSOutputType.Streaming, EOBSOutputSignal.Activate); + let signalInfo = await obs.getNextSignalInfo( + EOBSOutputType.Streaming, EOBSOutputSignal.Starting); + expect(signalInfo.type).to.equal( + EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); + expect(signalInfo.signal).to.equal( + EOBSOutputSignal.Starting, GetErrorMessage(ETestErrorMsg.StreamOutput)); - if (signalInfo.signal == EOBSOutputSignal.Stop) { - throw Error(GetErrorMessage( - ETestErrorMsg.StreamOutputDidNotStart, signalInfo.code.toString(), signalInfo.error)); - } + signalInfo = await obs.getNextSignalInfo( + EOBSOutputType.Streaming, EOBSOutputSignal.Activate); - expect(signalInfo.type).to.equal(EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); - expect(signalInfo.signal).to.equal(EOBSOutputSignal.Activate, GetErrorMessage(ETestErrorMsg.StreamOutput)); + if (signalInfo.signal == EOBSOutputSignal.Stop) { + throw Error(GetErrorMessage( + ETestErrorMsg.StreamOutputDidNotStart, signalInfo.code.toString(), signalInfo.error)); + } - signalInfo = await obs.getNextSignalInfo(EOBSOutputType.Streaming, EOBSOutputSignal.Start); - expect(signalInfo.type).to.equal(EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); - expect(signalInfo.signal).to.equal(EOBSOutputSignal.Start, GetErrorMessage(ETestErrorMsg.StreamOutput)); + expect(signalInfo.type).to.equal(EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); + expect(signalInfo.signal).to.equal(EOBSOutputSignal.Activate, GetErrorMessage(ETestErrorMsg.StreamOutput)); - await sleep(500); + signalInfo = await obs.getNextSignalInfo(EOBSOutputType.Streaming, EOBSOutputSignal.Start); + expect(signalInfo.type).to.equal(EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); + expect(signalInfo.signal).to.equal(EOBSOutputSignal.Start, GetErrorMessage(ETestErrorMsg.StreamOutput)); - expect(stream.droppedFrames).to.not.equal( - undefined, "Undefined droppedFrames"); - expect(stream.totalFrames).to.not.equal( - undefined, "Undefined totalFrames"); - expect(stream.kbitsPerSec).to.not.equal( - undefined, "Undefined kbitsPerSec"); - expect(stream.dataOutput).to.not.equal( - undefined, "Undefined dataOutput"); + await sleep(500); - stream.stop(); + expect(stream.droppedFrames).to.not.equal( + undefined, "Undefined droppedFrames"); + expect(stream.totalFrames).to.not.equal( + undefined, "Undefined totalFrames"); + expect(stream.kbitsPerSec).to.not.equal( + undefined, "Undefined kbitsPerSec"); + expect(stream.dataOutput).to.not.equal( + undefined, "Undefined dataOutput"); - signalInfo = await obs.getNextSignalInfo( - EOBSOutputType.Streaming, EOBSOutputSignal.Stopping); + stream.stop(); - expect(signalInfo.type).to.equal( - EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); - expect(signalInfo.signal).to.equal( - EOBSOutputSignal.Stopping, GetErrorMessage(ETestErrorMsg.StreamOutput)); + signalInfo = await obs.getNextSignalInfo( + EOBSOutputType.Streaming, EOBSOutputSignal.Stopping); - signalInfo = await obs.getNextSignalInfo(EOBSOutputType.Streaming, EOBSOutputSignal.Stop); + expect(signalInfo.type).to.equal( + EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); + expect(signalInfo.signal).to.equal( + EOBSOutputSignal.Stopping, GetErrorMessage(ETestErrorMsg.StreamOutput)); - if (signalInfo.code != 0) { - throw Error(GetErrorMessage( - ETestErrorMsg.StreamOutputStoppedWithError, - signalInfo.code.toString(), signalInfo.error)); - } + signalInfo = await obs.getNextSignalInfo(EOBSOutputType.Streaming, EOBSOutputSignal.Stop); - expect(signalInfo.type).to.equal( - EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); - expect(signalInfo.signal).to.equal( - EOBSOutputSignal.Stop, GetErrorMessage(ETestErrorMsg.StreamOutput)); + if (signalInfo.code != 0) { + throw Error(GetErrorMessage( + ETestErrorMsg.StreamOutputStoppedWithError, + signalInfo.code.toString(), signalInfo.error)); + } - signalInfo = await obs.getNextSignalInfo( - EOBSOutputType.Streaming, EOBSOutputSignal.Deactivate); - expect(signalInfo.type).to.equal( - EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); - expect(signalInfo.signal).to.equal( - EOBSOutputSignal.Deactivate, GetErrorMessage(ETestErrorMsg.StreamOutput)); + expect(signalInfo.type).to.equal( + EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); + expect(signalInfo.signal).to.equal( + EOBSOutputSignal.Stop, GetErrorMessage(ETestErrorMsg.StreamOutput)); - const streamEncoder = stream.videoEncoder; - const audioEncoder = stream.audioEncoder; - osn.SimpleStreamingFactory.destroy(stream); - streamEncoder.release(); - audioEncoder.release(); + signalInfo = await obs.getNextSignalInfo( + EOBSOutputType.Streaming, EOBSOutputSignal.Deactivate); + expect(signalInfo.type).to.equal( + EOBSOutputType.Streaming, GetErrorMessage(ETestErrorMsg.StreamOutput)); + expect(signalInfo.signal).to.equal( + EOBSOutputSignal.Deactivate, GetErrorMessage(ETestErrorMsg.StreamOutput)); + } + finally + { + const streamEncoder = stream.videoEncoder; + const audioEncoder = stream.audioEncoder; + osn.SimpleStreamingFactory.destroy(stream); + if (streamEncoder) streamEncoder.release(); + if (audioEncoder) audioEncoder.release(); + } }); it('Simple Streaming honors stream delay', async function() { - if (obs.isDarwin()) { - this.skip(); - } - const configuredDelayMs = 10 * 1000; const allowedTimingDriftMs = 1 * 1000; const stream = osn.SimpleStreamingFactory.create(); @@ -336,9 +321,6 @@ describe(testName, () => { }); it('Stream with invalid stream key', async function() { - if (obs.isDarwin()) { - this.skip(); - } const stream = osn.SimpleStreamingFactory.create(); stream.videoEncoder = osn.VideoEncoderFactory.create('obs_x264', 'video-encoder-simple-streaming-2'); diff --git a/tests/osn-tests/util/obs_handler.ts b/tests/osn-tests/util/obs_handler.ts index d44168b8b..f98bed12d 100644 --- a/tests/osn-tests/util/obs_handler.ts +++ b/tests/osn-tests/util/obs_handler.ts @@ -122,12 +122,12 @@ export class OBSHandler { const exitCode = osn.NodeObs.IPC.host(this.pipeName); if (exitCode !== osn.EVideoCodes.Success) { if (exitCode === osn.EIPCError.OTHER_ERROR) { - throw Error('OBS IPC host failed: missing executable or some other error.'); + throw Error(`OBS IPC host failed: missing executable or some other error. Code ${exitCode}`); } throw Error(`OBS IPC host failed with code ${exitCode}. See osn.EIPCError for more details.`); } osn.NodeObs.SetWorkingDirectory(this.workingDirectory); - initResult = osn.NodeObs.OBS_API_initAPI(this.language, this.obsPath, this.version, this.crashServer); + initResult = osn.NodeObs.OBS_API_initAPI(this.language, this.obsPath, this.version, this.crashServer, this.osnTestName); } catch (e) { throw Error('Exception when initializing OBS process: ' + e); } @@ -282,6 +282,10 @@ export class OBSHandler { 'streaming starting signal timeout', 'streaming activate signal timeout', 'streaming start signal timeout', + 'recording start signal timeout', + 'recording wrote signal timeout', + 'replay-buffer start signal timeout', + 'replay-buffer writing signal timeout', ]; if (retryableTimeouts.some(timeoutMessage => normalizedMessage.includes(timeoutMessage))) { @@ -449,10 +453,12 @@ export class OBSHandler { return await this.getNextSignalInfoOf(output, [signal]); } - async getNextSignalInfoOf(output: string, signals: string[]): Promise { - const signalDescription = signals.join('/'); + async getNextSignalInfoOf(output: string, signalsList: string[]): Promise { + const signalDescription = signalsList.join('/'); const timeoutMessage = output.replace(/^\w/, c => c.toUpperCase()) + ' ' + signalDescription + ' signal timeout'; - const deadline = Date.now() + 30000; + const expectedDeadline = Date.now() + 30000; + const deadline = Date.now() + 60000; // 60 second timeout for receiving expected signal, since some steps (like recording stop) can take a while on slower CI machines + const startTime = Date.now(); while (Date.now() < deadline) { const remainingMs = deadline - Date.now(); @@ -463,7 +469,10 @@ export class OBSHandler { }), ]); - if (signalInfo.type === output && signals.indexOf(signalInfo.signal) >= 0) { + if (signalInfo.type === output && signalsList.indexOf(signalInfo.signal) >= 0) { + if (Date.now() > expectedDeadline) { + logWarning(this.osnTestName, `Received expected ${output}/${signalDescription} signal after ${Date.now() - startTime}ms, which is longer than the expected ${expectedDeadline - startTime}ms. Signal info: ${this.formatSignalInfo(signalInfo)}`); + } return signalInfo; } @@ -506,7 +515,7 @@ export class OBSHandler { logInfo(this.osnTestName, 'createDefaultVideoContext called'); this.defaultVideoContext = osn.VideoFactory.create(); const defaultVideoInfo: osn.IVideoInfo = { - fpsNum: 60, + fpsNum: 30, fpsDen: 1, baseWidth: 1280, baseHeight: 720, @@ -551,10 +560,19 @@ export class OBSHandler { }); } - isDarwin() + removeSourceMessageListener() { + osn.NodeObs.RemoveSourceCallback(); + osn.NodeObs.RemoveSourceMessageCallback(); + } + + /* + * Is running on darwin CI build agent, which has shown to be particularly flaky with streaming/recording outputs, so + * we may want to skip some tests or add extra retries when running in this environment + */ + isOnDarwinIntelCI() { // Wrapped this in a function- just incase we want to add more conditions later or disable only within the build agent. - return this.os === 'darwin'; + return this.os === 'darwin' && this.ci && process.arch === 'x64'; } // is the build server environment