Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ 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
# Run even after test failures so the PR still gets the flaky summary.
- name: Publish flaky test check
if: ${{ always() }}
Expand Down
2 changes: 2 additions & 0 deletions obs-studio-server/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,8 @@ SET(osn-server_SOURCES
if (APPLE)
SET(osn-server-osx_SOURCES
"${PROJECT_SOURCE_DIR}/source/osn-multitrack-video-system-info-osx.mm"
"${PROJECT_SOURCE_DIR}/source/nodeobs_settings-osx.h"
"${PROJECT_SOURCE_DIR}/source/nodeobs_settings-osx.mm"

###### osx-util ######
"${PROJECT_SOURCE_DIR}/source/util-osx.hpp"
Expand Down
13 changes: 13 additions & 0 deletions obs-studio-server/source/nodeobs_settings-osx.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#pragma once

#ifdef __APPLE__

#include <string>
#include <utility>
#include <vector>

// Returns (localizedName, uniqueID) pairs for connected video capture devices.
// Availability and results depend on the runtime implementation and OS support.
std::vector<std::pair<std::string, std::string>> getVideoDevicesMacOS();

#endif
43 changes: 43 additions & 0 deletions obs-studio-server/source/nodeobs_settings-osx.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#include "nodeobs_settings-osx.h"

#import <AVFoundation/AVFoundation.h>

std::vector<std::pair<std::string, std::string>> getVideoDevicesMacOS()
{
std::vector<std::pair<std::string, std::string>> result;

if (@available(macOS 12.0, *)) {
NSMutableArray<AVCaptureDeviceType> *deviceTypes = [NSMutableArray arrayWithObject:AVCaptureDeviceTypeBuiltInWideAngleCamera];

if (@available(macOS 13.0, *)) {
[deviceTypes addObject:AVCaptureDeviceTypeExternal];
} else {
// AVCaptureDeviceTypeExternalUnknown is deprecated in macOS 13 in favour of
// AVCaptureDeviceTypeExternal, but is the correct constant for macOS 12.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[deviceTypes addObject:AVCaptureDeviceTypeExternalUnknown];
#pragma clang diagnostic pop
}

AVCaptureDeviceDiscoverySession *session = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:deviceTypes
mediaType:AVMediaTypeVideo
position:AVCaptureDevicePositionUnspecified];

for (AVCaptureDevice *device in session.devices) {
if (!device.localizedName || !device.uniqueID)
continue;
result.push_back({[device.localizedName UTF8String], [device.uniqueID UTF8String]});
}
Comment thread
sandboxcoder marked this conversation as resolved.
} else {
NSArray<AVCaptureDevice *> *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];

for (AVCaptureDevice *device in devices) {
if (!device.localizedName || !device.uniqueID)
continue;
result.push_back({[device.localizedName UTF8String], [device.uniqueID UTF8String]});
}
}

return result;
}
43 changes: 15 additions & 28 deletions obs-studio-server/source/nodeobs_settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#ifdef __APPLE__
#include <sys/types.h>
#include <sys/stat.h>
#include "nodeobs_settings-osx.h"
#endif

#include <unordered_set>
Expand Down Expand Up @@ -3866,36 +3867,16 @@ void OBS_settings::saveGenericSettings(std::vector<SubCategory> genericSettings,

void getDevices(const char *source_id, const char *property_name, std::vector<ipc::value> &rval)
{
auto settings = obs_get_source_defaults(source_id);
if (!settings)
return;

const char *dummy_device_name = "does_not_exist";
obs_data_set_string(settings, property_name, dummy_device_name);
if (strcmp(source_id, "dshow_input") == 0) {
obs_data_set_string(settings, "video_device_id", dummy_device_name);
obs_data_set_string(settings, "audio_device_id", dummy_device_name);
}

// Create a dummy source so that the "device" property can be init
auto dummy_source = obs_source_create(source_id, dummy_device_name, settings, nullptr);
if (!dummy_source) {
obs_data_release(settings);
return;
}

auto props = obs_source_properties(dummy_source);
auto props = obs_get_source_properties(source_id);
if (!props) {
obs_source_release(dummy_source);
obs_data_release(settings);
blog(LOG_WARNING, "Could not get source properties for source id: %s", source_id);
return;
}

auto prop = obs_properties_get(props, property_name);
if (!prop) {
blog(LOG_WARNING, "Could not get the property [%s] for source id: %s", property_name, source_id);
obs_properties_destroy(props);
obs_source_release(dummy_source);
obs_data_release(settings);
return;
}

Expand All @@ -3919,8 +3900,6 @@ void getDevices(const char *source_id, const char *property_name, std::vector<ip
}

obs_properties_destroy(props);
obs_data_release(settings);
obs_source_release(dummy_source);
}

#ifdef WIN32
Expand Down Expand Up @@ -4097,9 +4076,17 @@ void OBS_settings::OBS_settings_getVideoDevices(void *data, const int64_t id, co
rval.push_back(ipc::value((uint32_t)0));
enumVideoDevices(rval);
#elif __APPLE__
const char *source_id = "macos_avcapture";
const char *property_name = "device";
getDevices(source_id, property_name, rval);
// Here we do not invoke getDevices() because the mac-capture
// plugin will not enumerate video devices unless it is fully initialized.
// So instead, we will enumerate video devices manually. Hopefully,
// mac-capture doesn't do anything special to enumerate video devices.
// If so, then this implementation will need to be updated.
auto devices = getVideoDevicesMacOS();
rval.push_back(ipc::value((uint64_t)devices.size()));
for (const auto &[name, uid] : devices) {
rval.push_back(ipc::value(name.c_str()));
rval.push_back(ipc::value(uid.c_str()));
}
#endif

AUTO_DEBUG;
Expand Down
6 changes: 6 additions & 0 deletions tests/osn-tests/src/test_osn_advanced_replayBuffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ describe(testName, () => {
});

it('Start advanced replay buffer - Use Recording', async function() {
if (obs.isDarwinCI()) {
this.skip();
}
replayBuffer = osn.AdvancedReplayBufferFactory.create();
replayBuffer.path = path.join(path.normalize(__dirname), '..', 'osnData');
replayBuffer.format = osn.ERecordingFormat.MP4;
Expand Down Expand Up @@ -258,6 +261,9 @@ describe(testName, () => {
});

it('Start advanced replay buffer - Use Stream through Recording', async function() {
if (obs.isDarwinCI()) {
this.skip();
}
replayBuffer = osn.AdvancedReplayBufferFactory.create();
replayBuffer.path = path.join(path.normalize(__dirname), '..', 'osnData');
replayBuffer.format = osn.ERecordingFormat.MP4;
Expand Down
4 changes: 3 additions & 1 deletion tests/osn-tests/src/test_osn_audio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,12 @@ describe(testName, () => {
for (const device of devices) {
expect(device).to.have.property('id');
expect(device).to.have.property('description');
logInfo(testName, `Output Audio Device Found: ${device.description} with id: ${device.id}`);
if (device.id === 'default') {
foundDefaultDevice = true;
}
}
if (!obs.isDarwin()) { // On virtual mac the default output device is not included in the list of output devices
if (!obs.isDarwinCI()) { // On virtual mac(s) the default output device is not included in the list of output devices
expect(foundDefaultDevice).to.equal(true, GetErrorMessage(ETestErrorMsg.DefaultDeviceNotFound));
}
});
Expand All @@ -83,6 +84,7 @@ describe(testName, () => {
for (const device of devices) {
expect(device).to.have.property('id');
expect(device).to.have.property('description');
logInfo(testName, `Input Audio Device Found: ${device.description} with id: ${device.id}`);
if (device.id === 'default') {
foundDefaultDevice = true;
}
Expand Down
14 changes: 14 additions & 0 deletions tests/osn-tests/src/test_osn_video.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,4 +191,18 @@ describe(testName, () => {

context.destroy();
});

it('Get output video devices', function() {
const devices = osn.NodeObs.OBS_settings_getVideoDevices();
expect(devices).to.not.equal(undefined, GetErrorMessage(ETestErrorMsg.VideoDevices));
expect(Array.isArray(devices)).to.equal(true, GetErrorMessage(ETestErrorMsg.VideoDevicesIsArray));
for (const device of devices) {
expect(device).to.have.property('id');
expect(device).to.have.property('description');
logInfo(testName, `Output Video Device Found: ${device.description} with id: ${device.id}`);
}
if (!obs.isCI()) { // On CI the list of video devices is empty since there is no GPU. Just check that we got an array back and not worry about the contents
expect(devices.length).to.be.greaterThan(0, GetErrorMessage(ETestErrorMsg.VideoDevicesEmpty));
}
});
});
3 changes: 3 additions & 0 deletions tests/osn-tests/util/error_messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,9 @@ export const enum ETestErrorMsg {
AudioDevices = 'Failed to get audio devices',
AudioDevicesIsArray = 'Returned audio devices value is not an array',
DefaultDeviceNotFound = 'Did not find the default audio device in the list of audio devices',
VideoDevices = 'Failed to get video devices',
VideoDevicesIsArray = 'Returned video devices value is not an array',
VideoDevicesEmpty = 'Returned video devices array is empty',
}

export function GetErrorMessage(message: string, value1?: string, value2?: string, value3?: string): string {
Expand Down
8 changes: 7 additions & 1 deletion tests/osn-tests/util/obs_handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -553,10 +553,16 @@ export class OBSHandler {

isDarwin()
{
// Wrapped this in a function- just incase we want to add more conditions later or disable only within the build agent.
// Wrapped this in a function- just in case we want to add more conditions later or disable only within the build agent.
return this.os === 'darwin';
}

isDarwinCI()
{
// Wrapped this in a function- just in case we want to add more conditions later or disable only within the build agent.
return this.os === 'darwin' && this.ci;
}

// is the build server environment
isCI()
{
Expand Down
Loading