@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.
@@ -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
  */