mirror of
https://github.com/pion/mediadevices.git
synced 2026-04-22 15:57:27 +08:00
03900dcb1b
* wip * wip * Organize * Remove unnecessary change in camera_darwin.go filtering * wip * Make observer stop safe during startup * wip IsAvailable impl * Fix non-darwin builds * Lock bg loop to main thread and add comment * Remove fmt prints * Simplify isAvailable; Add timeout for Read darwin * Match comment with code * Change to singleton pattern; Add clearer safer state machine states; Change language from Stop to Destroy; Add new error for when observer is unavailable; * Add stubs for linux * Move cancel() up so its not dead code sometimes * Add stubs for Windows too * Remove StopObserver usage * Add camera tests * Add device observer tests * Fix multiple destroy calls bug; Call setup in start * Improve isAvailable * Improve string handling in device observer c * Add error handling in example * Add comment about setup vs start * Rename and organize device observer darwin * Explicitly case initial state for setup * Fix potential destroy goroutine leak; Use only modern build tag; Return err not nil for stubs; Improve comments * Close startDone channel on device observer stop not wait
261 lines
8.4 KiB
Objective-C
261 lines
8.4 KiB
Objective-C
// MIT License
|
|
//
|
|
// Copyright (c) 2019-2020 Pion
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in all
|
|
// copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
// SOFTWARE.
|
|
|
|
// Naming Convention (let "name" as an actual variable name):
|
|
// - mName: "name" is a member of an Objective C object
|
|
// - pName: "name" is a C pointer
|
|
// - refName: "name" is an Objective C object reference
|
|
|
|
#import <Foundation/Foundation.h>
|
|
#import <AVFoundation/AVFoundation.h>
|
|
#import <string.h>
|
|
#import "DeviceObserver.h"
|
|
|
|
|
|
extern void goDeviceEventCallback(void *pUserData, int eventType, DeviceInfo *pDevice);
|
|
|
|
void deviceEventBridge(void *pUserData, DeviceEventType eventType, DeviceInfo *pDevice) {
|
|
goDeviceEventCallback(pUserData, (int)eventType, pDevice);
|
|
}
|
|
|
|
@interface DeviceObserverDelegate : NSObject {
|
|
DeviceEventCallback mCallback;
|
|
void *mUserData;
|
|
AVCaptureDeviceDiscoverySession *mDiscoverySession;
|
|
BOOL mObserving;
|
|
}
|
|
@end
|
|
|
|
@implementation DeviceObserverDelegate
|
|
|
|
- (instancetype)initWithCallback:(DeviceEventCallback)callback userData:(void *)pUserData {
|
|
self = [super init];
|
|
if (self) {
|
|
mCallback = callback;
|
|
mUserData = pUserData;
|
|
mObserving = NO;
|
|
|
|
NSArray *refDeviceTypes = @[
|
|
AVCaptureDeviceTypeBuiltInWideAngleCamera,
|
|
AVCaptureDeviceTypeExternal
|
|
];
|
|
|
|
mDiscoverySession = [[AVCaptureDeviceDiscoverySession
|
|
discoverySessionWithDeviceTypes:refDeviceTypes
|
|
mediaType:AVMediaTypeVideo
|
|
position:AVCaptureDevicePositionUnspecified] retain];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)startObserving {
|
|
if (mObserving) return;
|
|
|
|
[mDiscoverySession addObserver:self
|
|
forKeyPath:@"devices"
|
|
options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew)
|
|
context:nil];
|
|
|
|
mObserving = YES;
|
|
}
|
|
|
|
- (void)stopObserving {
|
|
if (!mObserving) return;
|
|
|
|
[mDiscoverySession removeObserver:self forKeyPath:@"devices"];
|
|
mObserving = NO;
|
|
}
|
|
|
|
- (void)observeValueForKeyPath:(NSString *)keyPath
|
|
ofObject:(id)object
|
|
change:(NSDictionary<NSKeyValueChangeKey,id> *)change
|
|
context:(void *)pContext {
|
|
|
|
if (![keyPath isEqualToString:@"devices"]) return;
|
|
|
|
NSArray<AVCaptureDevice *> *refOldDevices = change[NSKeyValueChangeOldKey];
|
|
NSArray<AVCaptureDevice *> *refNewDevices = change[NSKeyValueChangeNewKey];
|
|
|
|
if ([refOldDevices isKindOfClass:[NSNull class]]) refOldDevices = @[];
|
|
if ([refNewDevices isKindOfClass:[NSNull class]]) refNewDevices = @[];
|
|
|
|
// Build sets of device UIDs for comparison
|
|
NSMutableSet *refOldUIDs = [NSMutableSet set];
|
|
NSMutableDictionary *refOldDeviceMap = [NSMutableDictionary dictionary];
|
|
for (AVCaptureDevice *refDevice in refOldDevices) {
|
|
[refOldUIDs addObject:refDevice.uniqueID];
|
|
refOldDeviceMap[refDevice.uniqueID] = refDevice;
|
|
}
|
|
|
|
NSMutableSet *refNewUIDs = [NSMutableSet set];
|
|
NSMutableDictionary *refNewDeviceMap = [NSMutableDictionary dictionary];
|
|
for (AVCaptureDevice *refDevice in refNewDevices) {
|
|
[refNewUIDs addObject:refDevice.uniqueID];
|
|
refNewDeviceMap[refDevice.uniqueID] = refDevice;
|
|
}
|
|
|
|
// Find added devices
|
|
NSMutableSet *refAddedUIDs = [refNewUIDs mutableCopy];
|
|
[refAddedUIDs minusSet:refOldUIDs];
|
|
|
|
// Find removed devices
|
|
NSMutableSet *refRemovedUIDs = [refOldUIDs mutableCopy];
|
|
[refRemovedUIDs minusSet:refNewUIDs];
|
|
|
|
// Notify about added devices
|
|
for (NSString *uid in refAddedUIDs) {
|
|
AVCaptureDevice *refDevice = refNewDeviceMap[uid];
|
|
DeviceInfo info;
|
|
memset(&info, 0, sizeof(info));
|
|
strlcpy(info.uid, refDevice.uniqueID.UTF8String, sizeof(info.uid));
|
|
strlcpy(info.name, refDevice.localizedName.UTF8String, sizeof(info.name));
|
|
|
|
if (mCallback) {
|
|
mCallback(mUserData, DeviceEventConnected, &info);
|
|
}
|
|
}
|
|
|
|
// Notify about removed devices
|
|
for (NSString *uid in refRemovedUIDs) {
|
|
AVCaptureDevice *refDevice = refOldDeviceMap[uid];
|
|
DeviceInfo info;
|
|
memset(&info, 0, sizeof(info));
|
|
strlcpy(info.uid, refDevice.uniqueID.UTF8String, sizeof(info.uid));
|
|
strlcpy(info.name, refDevice.localizedName.UTF8String, sizeof(info.name));
|
|
|
|
if (mCallback) {
|
|
mCallback(mUserData, DeviceEventDisconnected, &info);
|
|
}
|
|
}
|
|
|
|
[refAddedUIDs release];
|
|
[refRemovedUIDs release];
|
|
}
|
|
|
|
- (void)dealloc {
|
|
[self stopObserving];
|
|
[mDiscoverySession release];
|
|
[super dealloc];
|
|
}
|
|
|
|
@end
|
|
|
|
// Global observer instance
|
|
static DeviceObserverDelegate *refObserver = nil;
|
|
|
|
STATUS DeviceObserverInit(DeviceEventCallback callback, void *pUserData) {
|
|
@autoreleasepool {
|
|
if (refObserver != nil) {
|
|
return "observer already initialized";
|
|
}
|
|
|
|
refObserver = [[DeviceObserverDelegate alloc] initWithCallback:callback userData:pUserData];
|
|
if (refObserver == nil) {
|
|
return "failed to create observer";
|
|
}
|
|
|
|
return STATUS_OK;
|
|
}
|
|
}
|
|
|
|
STATUS DeviceObserverStart(void) {
|
|
@autoreleasepool {
|
|
if (refObserver == nil) {
|
|
return "observer not initialized";
|
|
}
|
|
|
|
[refObserver startObserving];
|
|
return STATUS_OK;
|
|
}
|
|
}
|
|
|
|
STATUS DeviceObserverStop(void) {
|
|
@autoreleasepool {
|
|
if (refObserver == nil) {
|
|
return "observer not initialized";
|
|
}
|
|
|
|
[refObserver stopObserving];
|
|
return STATUS_OK;
|
|
}
|
|
}
|
|
|
|
STATUS DeviceObserverDestroy(void) {
|
|
@autoreleasepool {
|
|
if (refObserver == nil) {
|
|
return "observer not initialized";
|
|
}
|
|
|
|
[refObserver stopObserving];
|
|
[refObserver release];
|
|
refObserver = nil;
|
|
|
|
return STATUS_OK;
|
|
}
|
|
}
|
|
|
|
STATUS DeviceObserverGetDevices(DeviceInfo *pDevices, int *pCount) {
|
|
@autoreleasepool {
|
|
if (pDevices == NULL || pCount == NULL) {
|
|
return "invalid arguments";
|
|
}
|
|
|
|
// Use discovery session for device enumeration
|
|
NSArray *refDeviceTypes = @[
|
|
AVCaptureDeviceTypeBuiltInWideAngleCamera,
|
|
AVCaptureDeviceTypeExternal
|
|
];
|
|
|
|
AVCaptureDeviceDiscoverySession *refSession = [AVCaptureDeviceDiscoverySession
|
|
discoverySessionWithDeviceTypes:refDeviceTypes
|
|
mediaType:AVMediaTypeVideo
|
|
position:AVCaptureDevicePositionUnspecified];
|
|
|
|
int i = 0;
|
|
for (AVCaptureDevice *refDevice in refSession.devices) {
|
|
if (i >= MAX_DEVICES) break;
|
|
|
|
memset(&pDevices[i], 0, sizeof(DeviceInfo));
|
|
strlcpy(pDevices[i].uid, refDevice.uniqueID.UTF8String, sizeof(pDevices[i].uid));
|
|
strlcpy(pDevices[i].name, refDevice.localizedName.UTF8String, sizeof(pDevices[i].name));
|
|
i++;
|
|
}
|
|
|
|
*pCount = i;
|
|
return STATUS_OK;
|
|
}
|
|
}
|
|
|
|
STATUS DeviceObserverRunFor(double seconds) {
|
|
@autoreleasepool {
|
|
// Add a timer to keep the run loop alive
|
|
NSTimer *refTimer = [NSTimer scheduledTimerWithTimeInterval:seconds
|
|
target:[NSDate class]
|
|
selector:@selector(date)
|
|
userInfo:nil
|
|
repeats:NO];
|
|
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:seconds]];
|
|
[refTimer invalidate];
|
|
return STATUS_OK;
|
|
}
|
|
}
|