@abdurrahman-dev/react-native-ivs-broadcast 0.2.5 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -4
- package/android/src/main/java/com/reactnativeivsbroadcast/IVSBroadcastModule.kt +393 -0
- package/ios/IVSBroadcastModule.m +554 -1
- package/lib/index.d.ts +75 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +199 -0
- package/lib/index.js.map +1 -1
- package/lib/types.d.ts +86 -1
- package/lib/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +233 -6
- package/src/types.ts +118 -1
package/ios/IVSBroadcastModule.m
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
#import "IVSBroadcastModule.h"
|
|
2
2
|
#import <AmazonIVSBroadcast/AmazonIVSBroadcast.h>
|
|
3
|
+
#import <AVKit/AVKit.h>
|
|
3
4
|
|
|
4
5
|
static IVSBroadcastModule *sharedInstance = nil;
|
|
5
6
|
|
|
6
|
-
@interface IVSBroadcastModule () <IVSBroadcastSessionDelegate>
|
|
7
|
+
@interface IVSBroadcastModule () <IVSBroadcastSessionDelegate, AVPictureInPictureControllerDelegate>
|
|
7
8
|
@property (nonatomic, strong) NSMutableDictionary<NSString *, IVSBroadcastSession *> *sessions;
|
|
8
9
|
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSURL *> *sessionUrls;
|
|
9
10
|
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSString *> *currentCameraPosition;
|
|
10
11
|
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSNumber *> *isMutedState;
|
|
12
|
+
@property (nonatomic, strong) NSMutableDictionary<NSString *, AVPictureInPictureController *> *pipControllers;
|
|
11
13
|
@property (nonatomic, assign) BOOL hasListeners;
|
|
12
14
|
@end
|
|
13
15
|
|
|
@@ -26,6 +28,7 @@ RCT_EXPORT_MODULE(IVSBroadcastModule);
|
|
|
26
28
|
_sessionUrls = [NSMutableDictionary dictionary];
|
|
27
29
|
_currentCameraPosition = [NSMutableDictionary dictionary];
|
|
28
30
|
_isMutedState = [NSMutableDictionary dictionary];
|
|
31
|
+
_pipControllers = [NSMutableDictionary dictionary];
|
|
29
32
|
_hasListeners = NO;
|
|
30
33
|
sharedInstance = self;
|
|
31
34
|
}
|
|
@@ -381,6 +384,150 @@ RCT_EXPORT_METHOD(setCameraPosition:(NSString *)sessionId
|
|
|
381
384
|
});
|
|
382
385
|
}
|
|
383
386
|
|
|
387
|
+
RCT_EXPORT_METHOD(selectCamera:(NSString *)sessionId
|
|
388
|
+
deviceId:(NSString *)deviceId
|
|
389
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
390
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
391
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
392
|
+
@try {
|
|
393
|
+
IVSBroadcastSession *session = self.sessions[sessionId];
|
|
394
|
+
if (!session) {
|
|
395
|
+
reject(@"SELECT_CAMERA_ERROR", @"Session not found", nil);
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Tüm kullanılabilir cihazları listele
|
|
400
|
+
NSArray<IVSDeviceDescriptor *> *availableDevices = [IVSBroadcastSession listAvailableDevices];
|
|
401
|
+
IVSDeviceDescriptor *targetCamera = nil;
|
|
402
|
+
|
|
403
|
+
// deviceId ile kamerayı bul
|
|
404
|
+
for (IVSDeviceDescriptor *device in availableDevices) {
|
|
405
|
+
if (device.type == IVSDeviceTypeCamera &&
|
|
406
|
+
[device.urn isEqualToString:deviceId]) {
|
|
407
|
+
targetCamera = device;
|
|
408
|
+
break;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (!targetCamera) {
|
|
413
|
+
reject(@"SELECT_CAMERA_ERROR",
|
|
414
|
+
[NSString stringWithFormat:@"Camera with deviceId '%@' not found", deviceId], nil);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Mevcut kamerayı bul
|
|
419
|
+
NSArray<id<IVSDevice>> *attachedDevices = [session listAttachedDevices];
|
|
420
|
+
id<IVSDevice> currentCamera = nil;
|
|
421
|
+
|
|
422
|
+
for (id<IVSDevice> device in attachedDevices) {
|
|
423
|
+
if (device.descriptor.type == IVSDeviceTypeCamera) {
|
|
424
|
+
currentCamera = device;
|
|
425
|
+
break;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Kamera pozisyonunu güncelle
|
|
430
|
+
NSString *position = (targetCamera.position == IVSDevicePositionFront) ? @"front" : @"back";
|
|
431
|
+
|
|
432
|
+
if (currentCamera) {
|
|
433
|
+
// Mevcut kamerayı yeni kamerayla değiştir
|
|
434
|
+
[session exchangeOldDevice:currentCamera withNewDevice:targetCamera onComplete:^(id<IVSDevice> _Nullable newDevice, NSError * _Nullable error) {
|
|
435
|
+
if (newDevice) {
|
|
436
|
+
self.currentCameraPosition[sessionId] = position;
|
|
437
|
+
|
|
438
|
+
// Kamera değişti, PreviewView'ları bilgilendir
|
|
439
|
+
[[NSNotificationCenter defaultCenter] postNotificationName:@"IVSCameraDeviceChanged"
|
|
440
|
+
object:nil
|
|
441
|
+
userInfo:@{@"sessionId": sessionId}];
|
|
442
|
+
resolve(nil);
|
|
443
|
+
} else {
|
|
444
|
+
reject(@"SELECT_CAMERA_ERROR", error.localizedDescription ?: @"Failed to select camera", error);
|
|
445
|
+
}
|
|
446
|
+
}];
|
|
447
|
+
} else {
|
|
448
|
+
// Kamera yoksa yeni kamerayı ekle
|
|
449
|
+
[session attachDeviceDescriptor:targetCamera toSlotWithName:nil onComplete:^(id<IVSDevice> _Nullable device, NSError * _Nullable error) {
|
|
450
|
+
if (device) {
|
|
451
|
+
self.currentCameraPosition[sessionId] = position;
|
|
452
|
+
resolve(nil);
|
|
453
|
+
} else {
|
|
454
|
+
reject(@"SELECT_CAMERA_ERROR", error.localizedDescription ?: @"Failed to attach camera", error);
|
|
455
|
+
}
|
|
456
|
+
}];
|
|
457
|
+
}
|
|
458
|
+
} @catch (NSException *exception) {
|
|
459
|
+
reject(@"SELECT_CAMERA_ERROR", exception.reason, nil);
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
RCT_EXPORT_METHOD(selectMicrophone:(NSString *)sessionId
|
|
465
|
+
deviceId:(NSString *)deviceId
|
|
466
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
467
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
468
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
469
|
+
@try {
|
|
470
|
+
IVSBroadcastSession *session = self.sessions[sessionId];
|
|
471
|
+
if (!session) {
|
|
472
|
+
reject(@"SELECT_MICROPHONE_ERROR", @"Session not found", nil);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Tüm kullanılabilir cihazları listele
|
|
477
|
+
NSArray<IVSDeviceDescriptor *> *availableDevices = [IVSBroadcastSession listAvailableDevices];
|
|
478
|
+
IVSDeviceDescriptor *targetMicrophone = nil;
|
|
479
|
+
|
|
480
|
+
// deviceId ile mikrofonu bul
|
|
481
|
+
for (IVSDeviceDescriptor *device in availableDevices) {
|
|
482
|
+
if (device.type == IVSDeviceTypeMicrophone &&
|
|
483
|
+
[device.urn isEqualToString:deviceId]) {
|
|
484
|
+
targetMicrophone = device;
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
if (!targetMicrophone) {
|
|
490
|
+
reject(@"SELECT_MICROPHONE_ERROR",
|
|
491
|
+
[NSString stringWithFormat:@"Microphone with deviceId '%@' not found", deviceId], nil);
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Mevcut mikrofonu bul
|
|
496
|
+
NSArray<id<IVSDevice>> *attachedDevices = [session listAttachedDevices];
|
|
497
|
+
id<IVSDevice> currentMicrophone = nil;
|
|
498
|
+
|
|
499
|
+
for (id<IVSDevice> device in attachedDevices) {
|
|
500
|
+
if (device.descriptor.type == IVSDeviceTypeMicrophone) {
|
|
501
|
+
currentMicrophone = device;
|
|
502
|
+
break;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (currentMicrophone) {
|
|
507
|
+
// Mevcut mikrofonu yeni mikrofonla değiştir
|
|
508
|
+
[session exchangeOldDevice:currentMicrophone withNewDevice:targetMicrophone onComplete:^(id<IVSDevice> _Nullable newDevice, NSError * _Nullable error) {
|
|
509
|
+
if (newDevice) {
|
|
510
|
+
resolve(nil);
|
|
511
|
+
} else {
|
|
512
|
+
reject(@"SELECT_MICROPHONE_ERROR", error.localizedDescription ?: @"Failed to select microphone", error);
|
|
513
|
+
}
|
|
514
|
+
}];
|
|
515
|
+
} else {
|
|
516
|
+
// Mikrofon yoksa yeni mikrofonu ekle
|
|
517
|
+
[session attachDeviceDescriptor:targetMicrophone toSlotWithName:nil onComplete:^(id<IVSDevice> _Nullable device, NSError * _Nullable error) {
|
|
518
|
+
if (device) {
|
|
519
|
+
resolve(nil);
|
|
520
|
+
} else {
|
|
521
|
+
reject(@"SELECT_MICROPHONE_ERROR", error.localizedDescription ?: @"Failed to attach microphone", error);
|
|
522
|
+
}
|
|
523
|
+
}];
|
|
524
|
+
}
|
|
525
|
+
} @catch (NSException *exception) {
|
|
526
|
+
reject(@"SELECT_MICROPHONE_ERROR", exception.reason, nil);
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
|
|
384
531
|
- (void)switchToCamera:(NSString *)position
|
|
385
532
|
session:(IVSBroadcastSession *)session
|
|
386
533
|
sessionId:(NSString *)sessionId
|
|
@@ -655,4 +802,410 @@ RCT_EXPORT_METHOD(updateAudioConfig:(NSString *)sessionId
|
|
|
655
802
|
return nil;
|
|
656
803
|
}
|
|
657
804
|
|
|
805
|
+
// MARK: - Gelişmiş Özellikler
|
|
806
|
+
|
|
807
|
+
RCT_EXPORT_METHOD(listAvailableDevices:(RCTPromiseResolveBlock)resolve
|
|
808
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
809
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
810
|
+
@try {
|
|
811
|
+
NSArray<IVSDeviceDescriptor *> *devices = [IVSBroadcastSession listAvailableDevices];
|
|
812
|
+
NSMutableArray *deviceArray = [NSMutableArray array];
|
|
813
|
+
|
|
814
|
+
for (IVSDeviceDescriptor *device in devices) {
|
|
815
|
+
NSDictionary *deviceDict = @{
|
|
816
|
+
@"type": [self deviceTypeToString:device.type],
|
|
817
|
+
@"position": [self devicePositionToString:device.position],
|
|
818
|
+
@"deviceId": device.urn ?: @"",
|
|
819
|
+
@"friendlyName": device.friendlyName ?: @"",
|
|
820
|
+
@"isDefault": @(device.isDefault)
|
|
821
|
+
};
|
|
822
|
+
[deviceArray addObject:deviceDict];
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
resolve(deviceArray);
|
|
826
|
+
} @catch (NSException *exception) {
|
|
827
|
+
reject(@"LIST_DEVICES_ERROR", exception.reason, nil);
|
|
828
|
+
}
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
RCT_EXPORT_METHOD(listAttachedDevices:(NSString *)sessionId
|
|
833
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
834
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
835
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
836
|
+
@try {
|
|
837
|
+
IVSBroadcastSession *session = self.sessions[sessionId];
|
|
838
|
+
if (!session) {
|
|
839
|
+
reject(@"LIST_ATTACHED_DEVICES_ERROR", @"Session not found", nil);
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
NSArray<id<IVSDevice>> *devices = [session listAttachedDevices];
|
|
844
|
+
NSMutableArray *deviceArray = [NSMutableArray array];
|
|
845
|
+
|
|
846
|
+
for (id<IVSDevice> device in devices) {
|
|
847
|
+
NSDictionary *deviceDict = @{
|
|
848
|
+
@"type": [self deviceTypeToString:device.descriptor.type],
|
|
849
|
+
@"position": [self devicePositionToString:device.descriptor.position],
|
|
850
|
+
@"deviceId": device.descriptor.urn ?: @"",
|
|
851
|
+
@"friendlyName": device.descriptor.friendlyName ?: @"",
|
|
852
|
+
@"isDefault": @(device.descriptor.isDefault)
|
|
853
|
+
};
|
|
854
|
+
[deviceArray addObject:deviceDict];
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
resolve(deviceArray);
|
|
858
|
+
} @catch (NSException *exception) {
|
|
859
|
+
reject(@"LIST_ATTACHED_DEVICES_ERROR", exception.reason, nil);
|
|
860
|
+
}
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
RCT_EXPORT_METHOD(setCameraZoom:(NSString *)sessionId
|
|
865
|
+
zoomFactor:(double)zoomFactor
|
|
866
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
867
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
868
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
869
|
+
@try {
|
|
870
|
+
IVSBroadcastSession *session = self.sessions[sessionId];
|
|
871
|
+
if (!session) {
|
|
872
|
+
reject(@"SET_ZOOM_ERROR", @"Session not found", nil);
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
NSArray<id<IVSDevice>> *attachedDevices = [session listAttachedDevices];
|
|
877
|
+
for (id<IVSDevice> device in attachedDevices) {
|
|
878
|
+
if (device.descriptor.type == IVSDeviceTypeCamera) {
|
|
879
|
+
if ([device conformsToProtocol:@protocol(IVSCamera)]) {
|
|
880
|
+
id<IVSCamera> camera = (id<IVSCamera>)device;
|
|
881
|
+
[camera setVideoZoomFactor:zoomFactor];
|
|
882
|
+
resolve(nil);
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
reject(@"SET_ZOOM_ERROR", @"Camera not found", nil);
|
|
889
|
+
} @catch (NSException *exception) {
|
|
890
|
+
reject(@"SET_ZOOM_ERROR", exception.reason, nil);
|
|
891
|
+
}
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
RCT_EXPORT_METHOD(setTorchEnabled:(NSString *)sessionId
|
|
896
|
+
enabled:(BOOL)enabled
|
|
897
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
898
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
899
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
900
|
+
@try {
|
|
901
|
+
IVSBroadcastSession *session = self.sessions[sessionId];
|
|
902
|
+
if (!session) {
|
|
903
|
+
reject(@"SET_TORCH_ERROR", @"Session not found", nil);
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
NSArray<id<IVSDevice>> *attachedDevices = [session listAttachedDevices];
|
|
908
|
+
for (id<IVSDevice> device in attachedDevices) {
|
|
909
|
+
if (device.descriptor.type == IVSDeviceTypeCamera) {
|
|
910
|
+
if ([device conformsToProtocol:@protocol(IVSCamera)]) {
|
|
911
|
+
id<IVSCamera> camera = (id<IVSCamera>)device;
|
|
912
|
+
if (camera.isTorchSupported) {
|
|
913
|
+
camera.torchEnabled = enabled;
|
|
914
|
+
resolve(nil);
|
|
915
|
+
return;
|
|
916
|
+
} else {
|
|
917
|
+
reject(@"SET_TORCH_ERROR", @"Torch not supported on this camera", nil);
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
reject(@"SET_TORCH_ERROR", @"Camera not found", nil);
|
|
925
|
+
} @catch (NSException *exception) {
|
|
926
|
+
reject(@"SET_TORCH_ERROR", exception.reason, nil);
|
|
927
|
+
}
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
RCT_EXPORT_METHOD(getCameraCapabilities:(NSString *)sessionId
|
|
932
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
933
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
934
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
935
|
+
@try {
|
|
936
|
+
IVSBroadcastSession *session = self.sessions[sessionId];
|
|
937
|
+
if (!session) {
|
|
938
|
+
reject(@"GET_CAPABILITIES_ERROR", @"Session not found", nil);
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
NSArray<id<IVSDevice>> *attachedDevices = [session listAttachedDevices];
|
|
943
|
+
for (id<IVSDevice> device in attachedDevices) {
|
|
944
|
+
if (device.descriptor.type == IVSDeviceTypeCamera) {
|
|
945
|
+
if ([device conformsToProtocol:@protocol(IVSCamera)]) {
|
|
946
|
+
id<IVSCamera> camera = (id<IVSCamera>)device;
|
|
947
|
+
NSDictionary *capabilities = @{
|
|
948
|
+
@"minZoomFactor": @(camera.minAvailableVideoZoomFactor),
|
|
949
|
+
@"maxZoomFactor": @(camera.maxAvailableVideoZoomFactor),
|
|
950
|
+
@"isTorchSupported": @(camera.isTorchSupported)
|
|
951
|
+
};
|
|
952
|
+
resolve(capabilities);
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
reject(@"GET_CAPABILITIES_ERROR", @"Camera not found", nil);
|
|
959
|
+
} @catch (NSException *exception) {
|
|
960
|
+
reject(@"GET_CAPABILITIES_ERROR", exception.reason, nil);
|
|
961
|
+
}
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
RCT_EXPORT_METHOD(sendTimedMetadata:(NSString *)sessionId
|
|
966
|
+
metadata:(NSString *)metadata
|
|
967
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
968
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
969
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
970
|
+
@try {
|
|
971
|
+
IVSBroadcastSession *session = self.sessions[sessionId];
|
|
972
|
+
if (!session) {
|
|
973
|
+
reject(@"SEND_METADATA_ERROR", @"Session not found", nil);
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
NSError *error = nil;
|
|
978
|
+
BOOL success = [session sendTimedMetadata:metadata error:&error];
|
|
979
|
+
|
|
980
|
+
if (success) {
|
|
981
|
+
resolve(nil);
|
|
982
|
+
} else {
|
|
983
|
+
reject(@"SEND_METADATA_ERROR", error.localizedDescription ?: @"Failed to send metadata", error);
|
|
984
|
+
}
|
|
985
|
+
} @catch (NSException *exception) {
|
|
986
|
+
reject(@"SEND_METADATA_ERROR", exception.reason, nil);
|
|
987
|
+
}
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
RCT_EXPORT_METHOD(setAudioGain:(NSString *)sessionId
|
|
992
|
+
gain:(double)gain
|
|
993
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
994
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
995
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
996
|
+
@try {
|
|
997
|
+
IVSBroadcastSession *session = self.sessions[sessionId];
|
|
998
|
+
if (!session) {
|
|
999
|
+
reject(@"SET_GAIN_ERROR", @"Session not found", nil);
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
NSArray<id<IVSDevice>> *attachedDevices = [session listAttachedDevices];
|
|
1004
|
+
for (id<IVSDevice> device in attachedDevices) {
|
|
1005
|
+
if (device.descriptor.type == IVSDeviceTypeMicrophone) {
|
|
1006
|
+
if ([device conformsToProtocol:@protocol(IVSAudioDevice)]) {
|
|
1007
|
+
id<IVSAudioDevice> audioDevice = (id<IVSAudioDevice>)device;
|
|
1008
|
+
[audioDevice setGain:gain];
|
|
1009
|
+
resolve(nil);
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
reject(@"SET_GAIN_ERROR", @"Microphone not found", nil);
|
|
1016
|
+
} @catch (NSException *exception) {
|
|
1017
|
+
reject(@"SET_GAIN_ERROR", exception.reason, nil);
|
|
1018
|
+
}
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
RCT_EXPORT_METHOD(getAudioGain:(NSString *)sessionId
|
|
1023
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
1024
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
1025
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
1026
|
+
@try {
|
|
1027
|
+
IVSBroadcastSession *session = self.sessions[sessionId];
|
|
1028
|
+
if (!session) {
|
|
1029
|
+
reject(@"GET_GAIN_ERROR", @"Session not found", nil);
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
NSArray<id<IVSDevice>> *attachedDevices = [session listAttachedDevices];
|
|
1034
|
+
for (id<IVSDevice> device in attachedDevices) {
|
|
1035
|
+
if (device.descriptor.type == IVSDeviceTypeMicrophone) {
|
|
1036
|
+
if ([device conformsToProtocol:@protocol(IVSAudioDevice)]) {
|
|
1037
|
+
id<IVSAudioDevice> audioDevice = (id<IVSAudioDevice>)device;
|
|
1038
|
+
float gain = [audioDevice gain];
|
|
1039
|
+
resolve(@(gain));
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
reject(@"GET_GAIN_ERROR", @"Microphone not found", nil);
|
|
1046
|
+
} @catch (NSException *exception) {
|
|
1047
|
+
reject(@"GET_GAIN_ERROR", exception.reason, nil);
|
|
1048
|
+
}
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// Helper methods
|
|
1053
|
+
- (NSString *)deviceTypeToString:(IVSDeviceType)type {
|
|
1054
|
+
switch (type) {
|
|
1055
|
+
case IVSDeviceTypeCamera:
|
|
1056
|
+
return @"camera";
|
|
1057
|
+
case IVSDeviceTypeMicrophone:
|
|
1058
|
+
return @"microphone";
|
|
1059
|
+
case IVSDeviceTypeUserAudio:
|
|
1060
|
+
return @"userAudio";
|
|
1061
|
+
case IVSDeviceTypeUserVideo:
|
|
1062
|
+
return @"userVideo";
|
|
1063
|
+
default:
|
|
1064
|
+
return @"unknown";
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
- (NSString *)devicePositionToString:(IVSDevicePosition)position {
|
|
1069
|
+
switch (position) {
|
|
1070
|
+
case IVSDevicePositionFront:
|
|
1071
|
+
return @"front";
|
|
1072
|
+
case IVSDevicePositionBack:
|
|
1073
|
+
return @"back";
|
|
1074
|
+
default:
|
|
1075
|
+
return @"unknown";
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
// MARK: - Picture-in-Picture
|
|
1080
|
+
|
|
1081
|
+
RCT_EXPORT_METHOD(isPictureInPictureSupported:(RCTPromiseResolveBlock)resolve
|
|
1082
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
1083
|
+
if (@available(iOS 15.0, *)) {
|
|
1084
|
+
BOOL supported = [AVPictureInPictureController isPictureInPictureSupported];
|
|
1085
|
+
resolve(@(supported));
|
|
1086
|
+
} else {
|
|
1087
|
+
resolve(@NO);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
RCT_EXPORT_METHOD(startPictureInPicture:(NSString *)sessionId
|
|
1092
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
1093
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
1094
|
+
if (@available(iOS 15.0, *)) {
|
|
1095
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
1096
|
+
@try {
|
|
1097
|
+
IVSBroadcastSession *session = self.sessions[sessionId];
|
|
1098
|
+
if (!session) {
|
|
1099
|
+
reject(@"START_PIP_ERROR", @"Session not found", nil);
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
// Session'dan preview view al
|
|
1104
|
+
NSError *error = nil;
|
|
1105
|
+
IVSImagePreviewView *previewView = [session previewViewWithAspectMode:IVSAspectModeFill error:&error];
|
|
1106
|
+
|
|
1107
|
+
if (!previewView || error) {
|
|
1108
|
+
reject(@"START_PIP_ERROR", @"Failed to get preview view", error);
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
// PiP controller oluştur
|
|
1113
|
+
AVPictureInPictureController *pipController = [[AVPictureInPictureController alloc] initWithIVSImagePreviewView:previewView];
|
|
1114
|
+
|
|
1115
|
+
if (!pipController) {
|
|
1116
|
+
reject(@"START_PIP_ERROR", @"Failed to create PiP controller", nil);
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
pipController.delegate = self;
|
|
1121
|
+
self.pipControllers[sessionId] = pipController;
|
|
1122
|
+
|
|
1123
|
+
if ([pipController canStartPictureInPicture]) {
|
|
1124
|
+
[pipController startPictureInPicture];
|
|
1125
|
+
resolve(nil);
|
|
1126
|
+
} else {
|
|
1127
|
+
reject(@"START_PIP_ERROR", @"Cannot start Picture-in-Picture", nil);
|
|
1128
|
+
}
|
|
1129
|
+
} @catch (NSException *exception) {
|
|
1130
|
+
reject(@"START_PIP_ERROR", exception.reason, nil);
|
|
1131
|
+
}
|
|
1132
|
+
});
|
|
1133
|
+
} else {
|
|
1134
|
+
reject(@"START_PIP_ERROR", @"Picture-in-Picture requires iOS 15.0 or later", nil);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
RCT_EXPORT_METHOD(stopPictureInPicture:(NSString *)sessionId
|
|
1139
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
1140
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
1141
|
+
if (@available(iOS 15.0, *)) {
|
|
1142
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
1143
|
+
@try {
|
|
1144
|
+
AVPictureInPictureController *pipController = self.pipControllers[sessionId];
|
|
1145
|
+
if (!pipController) {
|
|
1146
|
+
reject(@"STOP_PIP_ERROR", @"PiP controller not found", nil);
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
if (pipController.isPictureInPictureActive) {
|
|
1151
|
+
[pipController stopPictureInPicture];
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
[self.pipControllers removeObjectForKey:sessionId];
|
|
1155
|
+
resolve(nil);
|
|
1156
|
+
} @catch (NSException *exception) {
|
|
1157
|
+
reject(@"STOP_PIP_ERROR", exception.reason, nil);
|
|
1158
|
+
}
|
|
1159
|
+
});
|
|
1160
|
+
} else {
|
|
1161
|
+
reject(@"STOP_PIP_ERROR", @"Picture-in-Picture requires iOS 15.0 or later", nil);
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
RCT_EXPORT_METHOD(getPictureInPictureState:(NSString *)sessionId
|
|
1166
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
1167
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
1168
|
+
if (@available(iOS 15.0, *)) {
|
|
1169
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
1170
|
+
@try {
|
|
1171
|
+
AVPictureInPictureController *pipController = self.pipControllers[sessionId];
|
|
1172
|
+
if (!pipController) {
|
|
1173
|
+
resolve(@"idle");
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
if (pipController.isPictureInPictureActive) {
|
|
1178
|
+
resolve(@"active");
|
|
1179
|
+
} else if (pipController.isPictureInPicturePossible) {
|
|
1180
|
+
resolve(@"idle");
|
|
1181
|
+
} else {
|
|
1182
|
+
resolve(@"stopped");
|
|
1183
|
+
}
|
|
1184
|
+
} @catch (NSException *exception) {
|
|
1185
|
+
reject(@"GET_PIP_STATE_ERROR", exception.reason, nil);
|
|
1186
|
+
}
|
|
1187
|
+
});
|
|
1188
|
+
} else {
|
|
1189
|
+
resolve(@"unsupported");
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
#pragma mark - AVPictureInPictureControllerDelegate
|
|
1194
|
+
|
|
1195
|
+
- (void)pictureInPictureControllerWillStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {
|
|
1196
|
+
// PiP başlıyor
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
- (void)pictureInPictureControllerDidStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {
|
|
1200
|
+
// PiP başladı
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
- (void)pictureInPictureControllerWillStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {
|
|
1204
|
+
// PiP duruyor
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
- (void)pictureInPictureControllerDidStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {
|
|
1208
|
+
// PiP durdu
|
|
1209
|
+
}
|
|
1210
|
+
|
|
658
1211
|
@end
|
package/lib/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { IVSBroadcastConfig, IVSBroadcastSession, BroadcastState, NetworkHealth, AudioStats, VideoStats, VideoConfig, AudioConfig } from "./types";
|
|
1
|
+
import type { IVSBroadcastConfig, IVSBroadcastSession, BroadcastState, NetworkHealth, AudioStats, VideoStats, VideoConfig, AudioConfig, TransmissionStatistics, AudioDeviceStats, NetworkTestResult, DeviceDescriptor, CameraCapabilities } from "./types";
|
|
2
2
|
declare class IVSBroadcast {
|
|
3
3
|
private listeners;
|
|
4
4
|
/**
|
|
@@ -41,6 +41,18 @@ declare class IVSBroadcast {
|
|
|
41
41
|
* Kamera pozisyonunu ayarlar
|
|
42
42
|
*/
|
|
43
43
|
setCameraPosition(sessionId: string, position: "front" | "back"): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Kamera listesinden belirli bir kamerayı seçer
|
|
46
|
+
* @param sessionId - Broadcast session ID
|
|
47
|
+
* @param deviceId - Seçilecek kameranın deviceId'si (listAvailableDevices'dan alınabilir)
|
|
48
|
+
*/
|
|
49
|
+
selectCamera(sessionId: string, deviceId: string): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Mikrofon listesinden belirli bir mikrofonu seçer
|
|
52
|
+
* @param sessionId - Broadcast session ID
|
|
53
|
+
* @param deviceId - Seçilecek mikrofonun deviceId'si (listAvailableDevices'dan alınabilir)
|
|
54
|
+
*/
|
|
55
|
+
selectMicrophone(sessionId: string, deviceId: string): Promise<void>;
|
|
44
56
|
/**
|
|
45
57
|
* Mikrofonu açıp kapatır
|
|
46
58
|
*/
|
|
@@ -57,6 +69,65 @@ declare class IVSBroadcast {
|
|
|
57
69
|
* Audio konfigürasyonunu günceller
|
|
58
70
|
*/
|
|
59
71
|
updateAudioConfig(sessionId: string, config: AudioConfig): Promise<void>;
|
|
72
|
+
/**
|
|
73
|
+
* Kullanılabilir cihazları listeler
|
|
74
|
+
*/
|
|
75
|
+
listAvailableDevices(): Promise<DeviceDescriptor[]>;
|
|
76
|
+
/**
|
|
77
|
+
* Bağlı cihazları listeler
|
|
78
|
+
*/
|
|
79
|
+
listAttachedDevices(sessionId: string): Promise<DeviceDescriptor[]>;
|
|
80
|
+
/**
|
|
81
|
+
* Kamera zoom seviyesini ayarlar
|
|
82
|
+
*/
|
|
83
|
+
setCameraZoom(sessionId: string, zoomFactor: number): Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* Kamera flaşını açıp kapatır
|
|
86
|
+
*/
|
|
87
|
+
setTorchEnabled(sessionId: string, enabled: boolean): Promise<void>;
|
|
88
|
+
/**
|
|
89
|
+
* Kamera yeteneklerini alır
|
|
90
|
+
*/
|
|
91
|
+
getCameraCapabilities(sessionId: string): Promise<CameraCapabilities>;
|
|
92
|
+
/**
|
|
93
|
+
* Zamanlı metadata gönderir
|
|
94
|
+
*/
|
|
95
|
+
sendTimedMetadata(sessionId: string, metadata: string): Promise<void>;
|
|
96
|
+
/**
|
|
97
|
+
* Ağ kalite testi başlatır
|
|
98
|
+
*/
|
|
99
|
+
startNetworkTest(rtmpUrl: string, streamKey?: string, duration?: number): Promise<string>;
|
|
100
|
+
/**
|
|
101
|
+
* Ağ kalite testini iptal eder
|
|
102
|
+
*/
|
|
103
|
+
cancelNetworkTest(testId: string): Promise<void>;
|
|
104
|
+
/**
|
|
105
|
+
* Ses gain seviyesini ayarlar (0.0 - 2.0 arası)
|
|
106
|
+
*/
|
|
107
|
+
setAudioGain(sessionId: string, gain: number): Promise<void>;
|
|
108
|
+
/**
|
|
109
|
+
* Mevcut ses gain seviyesini alır
|
|
110
|
+
*/
|
|
111
|
+
getAudioGain(sessionId: string): Promise<number>;
|
|
112
|
+
/**
|
|
113
|
+
* Picture-in-Picture modunu başlatır (iOS 15+, Android 8.0+)
|
|
114
|
+
* @param sessionId - Broadcast session ID
|
|
115
|
+
*/
|
|
116
|
+
startPictureInPicture(sessionId: string): Promise<void>;
|
|
117
|
+
/**
|
|
118
|
+
* Picture-in-Picture modunu durdurur
|
|
119
|
+
* @param sessionId - Broadcast session ID
|
|
120
|
+
*/
|
|
121
|
+
stopPictureInPicture(sessionId: string): Promise<void>;
|
|
122
|
+
/**
|
|
123
|
+
* Picture-in-Picture durumunu alır
|
|
124
|
+
* @param sessionId - Broadcast session ID
|
|
125
|
+
*/
|
|
126
|
+
getPictureInPictureState(sessionId: string): Promise<string>;
|
|
127
|
+
/**
|
|
128
|
+
* Picture-in-Picture desteğinin olup olmadığını kontrol eder
|
|
129
|
+
*/
|
|
130
|
+
isPictureInPictureSupported(): Promise<boolean>;
|
|
60
131
|
/**
|
|
61
132
|
* Event listener ekler
|
|
62
133
|
* @returns Cleanup fonksiyonu - listener'ı kaldırmak için çağırılır
|
|
@@ -69,6 +140,9 @@ declare class IVSBroadcast {
|
|
|
69
140
|
addListener(eventType: "onNetworkHealth", callback: (health: NetworkHealth) => void): () => void;
|
|
70
141
|
addListener(eventType: "onAudioStats", callback: (stats: AudioStats) => void): () => void;
|
|
71
142
|
addListener(eventType: "onVideoStats", callback: (stats: VideoStats) => void): () => void;
|
|
143
|
+
addListener(eventType: "onTransmissionStatistics", callback: (stats: TransmissionStatistics) => void): () => void;
|
|
144
|
+
addListener(eventType: "onAudioDeviceStats", callback: (stats: AudioDeviceStats) => void): () => void;
|
|
145
|
+
addListener(eventType: "onNetworkTestResult", callback: (result: NetworkTestResult) => void): () => void;
|
|
72
146
|
/**
|
|
73
147
|
* Event listener'ı kaldırır
|
|
74
148
|
*/
|