diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e803aa57d..358f50376 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: false # If 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 82bcacdee..abdb850db 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 @@ const fs = require('fs'); 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,290 +52,296 @@ 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; + 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(); + recording.start(); - let signalInfo = await obs.getNextSignalInfo( - EOBSOutputType.Recording, EOBSOutputSignal.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)); - } + 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)); + expect(signalInfo.type).to.equal( + EOBSOutputType.Recording, GetErrorMessage(ETestErrorMsg.RecordingOutput)); + expect(signalInfo.signal).to.equal( + EOBSOutputSignal.Start, GetErrorMessage(ETestErrorMsg.RecordingOutput)); - stream.start(); + 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.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); + signalInfo = await obs.getNextSignalInfo( + EOBSOutputType.Streaming, EOBSOutputSignal.Activate); - if (signalInfo.signal == EOBSOutputSignal.Stop) { - throw Error(GetErrorMessage( - ETestErrorMsg.StreamOutputDidNotStart, signalInfo.code.toString())); - } + 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)); + 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)); + 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); + await sleep(500); - recording.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.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); + 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.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)); + 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', + ); - 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(); - stream.stop(); + signalInfo = await obs.getNextSignalInfo( + EOBSOutputType.Streaming, EOBSOutputSignal.Stopping); - 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)); - 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); - signalInfo = await obs.getNextSignalInfo(EOBSOutputType.Streaming, EOBSOutputSignal.Stop); + if (signalInfo.code != 0) { + throw Error(GetErrorMessage( + ETestErrorMsg.StreamOutputStoppedWithError, + signalInfo.code.toString(), signalInfo.error)); + } - 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)); - 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(); + 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(); + } + } }); 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(); + 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)}; - let signalInfo = await obs.getNextSignalInfo( - EOBSOutputType.Recording, EOBSOutputSignal.Start); + recording.start(); - if (signalInfo.signal == EOBSOutputSignal.Stop) { - throw Error(GetErrorMessage( - ETestErrorMsg.RecordOutputDidNotStart, signalInfo.code.toString(), signalInfo.error)); - } + let signalInfo = await obs.getNextSignalInfo( + EOBSOutputType.Recording, EOBSOutputSignal.Start); - expect(signalInfo.type).to.equal( - EOBSOutputType.Recording, GetErrorMessage(ETestErrorMsg.RecordingOutput)); - expect(signalInfo.signal).to.equal( - EOBSOutputSignal.Start, GetErrorMessage(ETestErrorMsg.RecordingOutput)); + if (signalInfo.signal == EOBSOutputSignal.Stop) { + throw Error(GetErrorMessage( + ETestErrorMsg.RecordOutputDidNotStart, signalInfo.code.toString(), signalInfo.error)); + } - await sleep(500); + expect(signalInfo.type).to.equal( + EOBSOutputType.Recording, GetErrorMessage(ETestErrorMsg.RecordingOutput)); + expect(signalInfo.signal).to.equal( + EOBSOutputSignal.Start, GetErrorMessage(ETestErrorMsg.RecordingOutput)); - recording.stop(); + await sleep(500); - 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)); + recording.stop(); - signalInfo = await obs.getNextSignalInfo( - EOBSOutputType.Recording, EOBSOutputSignal.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)); - if (signalInfo.code != 0) { - throw Error(GetErrorMessage( - ETestErrorMsg.RecordOutputStoppedWithError, signalInfo.code.toString(), signalInfo.error)); - } + signalInfo = await obs.getNextSignalInfo( + EOBSOutputType.Recording, EOBSOutputSignal.Stop); - expect(signalInfo.type).to.equal( - EOBSOutputType.Recording, GetErrorMessage(ETestErrorMsg.RecordingOutput)); - expect(signalInfo.signal).to.equal( - EOBSOutputSignal.Stop, GetErrorMessage(ETestErrorMsg.RecordingOutput)); + if (signalInfo.code != 0) { + throw Error(GetErrorMessage( + ETestErrorMsg.RecordOutputStoppedWithError, signalInfo.code.toString(), signalInfo.error)); + } - signalInfo = await obs.getNextSignalInfo( - EOBSOutputType.Recording, EOBSOutputSignal.Wrote); + expect(signalInfo.type).to.equal( + EOBSOutputType.Recording, GetErrorMessage(ETestErrorMsg.RecordingOutput)); + expect(signalInfo.signal).to.equal( + EOBSOutputSignal.Stop, GetErrorMessage(ETestErrorMsg.RecordingOutput)); - if (signalInfo.code != 0) { - throw Error(GetErrorMessage( - ETestErrorMsg.RecordOutputStoppedWithError, signalInfo.code.toString(), signalInfo.error)); - } + signalInfo = await obs.getNextSignalInfo( + EOBSOutputType.Recording, EOBSOutputSignal.Wrote); - expect(signalInfo.type).to.equal( - EOBSOutputType.Recording, GetErrorMessage(ETestErrorMsg.RecordingOutput)); - expect(signalInfo.signal).to.equal( - EOBSOutputSignal.Wrote, GetErrorMessage(ETestErrorMsg.RecordingOutput)); + if (signalInfo.code != 0) { + throw Error(GetErrorMessage( + ETestErrorMsg.RecordOutputStoppedWithError, signalInfo.code.toString(), signalInfo.error)); + } - const videoEncoder = recording.videoEncoder; - osn.AdvancedRecordingFactory.destroy(recording); - videoEncoder.release(); + 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(); + } + } }); it('Audio track uses configured bitrate after binding to an OBS encoder', function () { - if (obs.isDarwin()) { + if (obs.isOnDarwinCI()) { this.skip(); } @@ -351,7 +357,7 @@ describe(testName, () => { }); it('Start advanced recording - Enable file split every second', async function () { - if (obs.isDarwin()) { + if (obs.isOnDarwinCI()) { this.skip(); } const recording = osn.AdvancedRecordingFactory.create(); @@ -408,7 +414,7 @@ describe(testName, () => { }); it('Start advanced recording - Enable file split every few bytes', async function () { - if (obs.isDarwin()) { + if (obs.isOnDarwinCI()) { this.skip(); } const recording = osn.AdvancedRecordingFactory.create(); diff --git a/tests/osn-tests/src/test_osn_advanced_replayBuffer.ts b/tests/osn-tests/src/test_osn_advanced_replayBuffer.ts index 8b684500f..53a8b290a 100644 --- a/tests/osn-tests/src/test_osn_advanced_replayBuffer.ts +++ b/tests/osn-tests/src/test_osn_advanced_replayBuffer.ts @@ -131,9 +131,6 @@ describe(testName, () => { }); it('Start advanced replay buffer - Use Recording', async function() { - if (obs.isDarwin()) { - this.skip(); - } replayBuffer = osn.AdvancedReplayBufferFactory.create(); replayBuffer.path = path.join(path.normalize(__dirname), '..', 'osnData'); replayBuffer.format = osn.ERecordingFormat.MP4; @@ -261,9 +258,6 @@ describe(testName, () => { }); it('Start advanced replay buffer - Use Stream through Recording', async function() { - if (obs.isDarwin()) { - this.skip(); - } replayBuffer = osn.AdvancedReplayBufferFactory.create(); replayBuffer.path = path.join(path.normalize(__dirname), '..', 'osnData'); replayBuffer.format = osn.ERecordingFormat.MP4; diff --git a/tests/osn-tests/src/test_osn_advanced_streaming.ts b/tests/osn-tests/src/test_osn_advanced_streaming.ts index 9c918d7ab..d73c6b01a 100644 --- a/tests/osn-tests/src/test_osn_advanced_streaming.ts +++ b/tests/osn-tests/src/test_osn_advanced_streaming.ts @@ -136,9 +136,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; @@ -155,9 +152,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; @@ -174,9 +168,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'); @@ -193,9 +184,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'); @@ -283,9 +271,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_audio.ts b/tests/osn-tests/src/test_osn_audio.ts index b3a75c586..11c9cece2 100644 --- a/tests/osn-tests/src/test_osn_audio.ts +++ b/tests/osn-tests/src/test_osn_audio.ts @@ -70,7 +70,7 @@ describe(testName, () => { foundDefaultDevice = true; } } - if (!obs.isDarwin()) { // On virtual mac the default output device is not included in the list of output devices + if (!obs.isOnDarwinCI()) { // On virtual mac the default output device is not included in the list of output devices expect(foundDefaultDevice).to.equal(true, GetErrorMessage(ETestErrorMsg.DefaultDeviceNotFound)); } }); diff --git a/tests/osn-tests/src/test_osn_dual_output.ts b/tests/osn-tests/src/test_osn_dual_output.ts index 51aef4c79..9897600c9 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.isOnDarwinCI()) { 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.isOnDarwinCI()) { 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.isOnDarwinCI()) { 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..920d78c72 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; } @@ -551,10 +560,15 @@ export class OBSHandler { }); } - isDarwin() + removeSourceMessageListener() { + osn.NodeObs.RemoveSourceCallback(); + osn.NodeObs.RemoveSourceMessageCallback(); + } + + isOnDarwinCI() { // 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; } // is the build server environment