@stream-io/video-react-native-sdk 1.20.16 → 1.21.0
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/CHANGELOG.md +19 -0
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/streamvideo/reactnative/StreamVideoReactNativeModule.kt +127 -0
- package/dist/commonjs/components/Participant/ParticipantView/VideoRenderer.js +1 -1
- package/dist/commonjs/components/Participant/ParticipantView/VideoRenderer.js.map +1 -1
- package/dist/commonjs/providers/BusyTonePlayer.js +54 -0
- package/dist/commonjs/providers/BusyTonePlayer.js.map +1 -0
- package/dist/commonjs/providers/StreamVideo.js +3 -2
- package/dist/commonjs/providers/StreamVideo.js.map +1 -1
- package/dist/commonjs/utils/StreamVideoRN/index.js +16 -0
- package/dist/commonjs/utils/StreamVideoRN/index.js.map +1 -1
- package/dist/commonjs/utils/push/android.js +2 -0
- package/dist/commonjs/utils/push/android.js.map +1 -1
- package/dist/commonjs/utils/push/internal/ios.js +5 -0
- package/dist/commonjs/utils/push/internal/ios.js.map +1 -1
- package/dist/commonjs/utils/push/setupIosVoipPushEvents.js +2 -2
- package/dist/commonjs/utils/push/setupIosVoipPushEvents.js.map +1 -1
- package/dist/commonjs/version.js +1 -1
- package/dist/commonjs/version.js.map +1 -1
- package/dist/module/components/Participant/ParticipantView/VideoRenderer.js +1 -1
- package/dist/module/components/Participant/ParticipantView/VideoRenderer.js.map +1 -1
- package/dist/module/providers/BusyTonePlayer.js +48 -0
- package/dist/module/providers/BusyTonePlayer.js.map +1 -0
- package/dist/module/providers/StreamVideo.js +3 -2
- package/dist/module/providers/StreamVideo.js.map +1 -1
- package/dist/module/utils/StreamVideoRN/index.js +16 -0
- package/dist/module/utils/StreamVideoRN/index.js.map +1 -1
- package/dist/module/utils/push/android.js +2 -0
- package/dist/module/utils/push/android.js.map +1 -1
- package/dist/module/utils/push/internal/ios.js +5 -0
- package/dist/module/utils/push/internal/ios.js.map +1 -1
- package/dist/module/utils/push/setupIosVoipPushEvents.js +2 -2
- package/dist/module/utils/push/setupIosVoipPushEvents.js.map +1 -1
- package/dist/module/version.js +1 -1
- package/dist/module/version.js.map +1 -1
- package/dist/typescript/components/Participant/ParticipantView/VideoRenderer.d.ts.map +1 -1
- package/dist/typescript/providers/BusyTonePlayer.d.ts +6 -0
- package/dist/typescript/providers/BusyTonePlayer.d.ts.map +1 -0
- package/dist/typescript/providers/StreamVideo.d.ts.map +1 -1
- package/dist/typescript/utils/StreamVideoRN/index.d.ts +9 -0
- package/dist/typescript/utils/StreamVideoRN/index.d.ts.map +1 -1
- package/dist/typescript/utils/StreamVideoRN/types.d.ts.map +1 -1
- package/dist/typescript/utils/push/android.d.ts.map +1 -1
- package/dist/typescript/utils/push/internal/ios.d.ts.map +1 -1
- package/dist/typescript/version.d.ts +1 -1
- package/dist/typescript/version.d.ts.map +1 -1
- package/expo-config-plugin/dist/withAppDelegate.js +20 -0
- package/ios/StreamVideoReactNative.h +4 -0
- package/ios/StreamVideoReactNative.m +276 -0
- package/ios/StreamVideoReactNative.xcodeproj/project.pbxproj +29 -1
- package/package.json +5 -5
- package/src/components/Participant/ParticipantView/VideoRenderer.tsx +1 -6
- package/src/providers/BusyTonePlayer.tsx +71 -0
- package/src/providers/StreamVideo.tsx +4 -4
- package/src/utils/StreamVideoRN/index.ts +16 -0
- package/src/utils/StreamVideoRN/types.ts +1 -0
- package/src/utils/push/android.ts +3 -0
- package/src/utils/push/internal/ios.ts +8 -0
- package/src/utils/push/setupIosVoipPushEvents.ts +2 -2
- package/src/version.ts +1 -1
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
#import <React/RCTUIManager.h>
|
|
4
4
|
#import <React/RCTUIManagerUtils.h>
|
|
5
5
|
#import <UIKit/UIKit.h>
|
|
6
|
+
#import <CallKit/CallKit.h>
|
|
6
7
|
#import "StreamVideoReactNative.h"
|
|
7
8
|
#import "WebRTCModule.h"
|
|
8
9
|
#import "WebRTCModuleOptions.h"
|
|
10
|
+
#import <AVFoundation/AVFoundation.h>
|
|
11
|
+
#import <AudioToolbox/AudioToolbox.h>
|
|
9
12
|
|
|
10
13
|
// Do not change these consts, it is what is used react-native-webrtc
|
|
11
14
|
NSNotificationName const kBroadcastStartedNotification = @"iOS_BroadcastStarted";
|
|
@@ -15,6 +18,8 @@ static NSMutableDictionary *_incomingCallUUIDsByCallID = nil;
|
|
|
15
18
|
static NSMutableDictionary *_incomingCallCidsByUUID = nil;
|
|
16
19
|
static dispatch_queue_t _dictionaryQueue = nil;
|
|
17
20
|
|
|
21
|
+
static BOOL _shouldRejectCallWhenBusy = NO;
|
|
22
|
+
|
|
18
23
|
void broadcastNotificationCallback(CFNotificationCenterRef center,
|
|
19
24
|
void *observer,
|
|
20
25
|
CFStringRef name,
|
|
@@ -25,10 +30,14 @@ void broadcastNotificationCallback(CFNotificationCenterRef center,
|
|
|
25
30
|
[this screenShareEventReceived: eventName];
|
|
26
31
|
}
|
|
27
32
|
|
|
33
|
+
@interface StreamVideoReactNative () <AVAudioPlayerDelegate>
|
|
34
|
+
@end
|
|
35
|
+
|
|
28
36
|
@implementation StreamVideoReactNative
|
|
29
37
|
{
|
|
30
38
|
bool hasListeners;
|
|
31
39
|
CFNotificationCenterRef _notificationCenter;
|
|
40
|
+
AVAudioPlayer *_busyTonePlayer; // Instance variable
|
|
32
41
|
}
|
|
33
42
|
|
|
34
43
|
// necessary for addUIBlock usage https://github.com/facebook/react-native/issues/50800#issuecomment-2823327307
|
|
@@ -77,6 +86,13 @@ RCT_EXPORT_METHOD(currentThermalState:(RCTPromiseResolveBlock)resolve rejecter:(
|
|
|
77
86
|
}
|
|
78
87
|
|
|
79
88
|
-(void)dealloc {
|
|
89
|
+
if (_busyTonePlayer) {
|
|
90
|
+
if (_busyTonePlayer.isPlaying) {
|
|
91
|
+
[_busyTonePlayer stop];
|
|
92
|
+
}
|
|
93
|
+
_busyTonePlayer = nil;
|
|
94
|
+
[self removeAudioInterruptionHandling];
|
|
95
|
+
}
|
|
80
96
|
[self clearObserver];
|
|
81
97
|
}
|
|
82
98
|
|
|
@@ -356,4 +372,264 @@ RCT_EXPORT_METHOD(getBatteryState:(RCTPromiseResolveBlock)resolve
|
|
|
356
372
|
];
|
|
357
373
|
}
|
|
358
374
|
|
|
375
|
+
+(BOOL)shouldRejectCallWhenBusy {
|
|
376
|
+
return _shouldRejectCallWhenBusy;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
RCT_EXPORT_METHOD(setShouldRejectCallWhenBusy:(BOOL)shouldReject) {
|
|
380
|
+
_shouldRejectCallWhenBusy = shouldReject;
|
|
381
|
+
#ifdef DEBUG
|
|
382
|
+
NSLog(@"setShouldRejectCallWhenBusy: %@", shouldReject ? @"YES" : @"NO");
|
|
383
|
+
#endif
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
+ (BOOL)hasAnyActiveCall
|
|
387
|
+
{
|
|
388
|
+
CXCallObserver *callObserver = [[CXCallObserver alloc] init];
|
|
389
|
+
|
|
390
|
+
for(CXCall *call in callObserver.calls){
|
|
391
|
+
if(call.hasConnected){
|
|
392
|
+
NSLog(@"[RNCallKeep] Found active call with UUID: %@", call.UUID);
|
|
393
|
+
return YES;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return NO;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
RCT_EXPORT_METHOD(playBusyTone:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
|
401
|
+
{
|
|
402
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
403
|
+
[self stopBusyTone]; // Stop any existing playback first
|
|
404
|
+
|
|
405
|
+
// Configure audio session
|
|
406
|
+
NSError *sessionError = nil;
|
|
407
|
+
AVAudioSession *session = [AVAudioSession sharedInstance];
|
|
408
|
+
[session setCategory:AVAudioSessionCategoryPlayback
|
|
409
|
+
mode:AVAudioSessionModeDefault
|
|
410
|
+
options:AVAudioSessionCategoryOptionMixWithOthers
|
|
411
|
+
error:&sessionError];
|
|
412
|
+
|
|
413
|
+
if (sessionError) {
|
|
414
|
+
NSString *errorMsg = [NSString stringWithFormat:@"Failed to set audio session category: %@", sessionError.localizedDescription];
|
|
415
|
+
NSLog(@"%@", errorMsg);
|
|
416
|
+
reject(@"AUDIO_SESSION_ERROR", errorMsg, sessionError);
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
[session setActive:YES error:&sessionError];
|
|
421
|
+
if (sessionError) {
|
|
422
|
+
NSString *errorMsg = [NSString stringWithFormat:@"Failed to activate audio session: %@", sessionError.localizedDescription];
|
|
423
|
+
NSLog(@"%@", errorMsg);
|
|
424
|
+
reject(@"AUDIO_SESSION_ERROR", errorMsg, sessionError);
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Generate busy tone data
|
|
429
|
+
NSData *busyToneData = [self generateBusyToneData];
|
|
430
|
+
if (!busyToneData || busyToneData.length == 0) {
|
|
431
|
+
NSString *errorMsg = @"Failed to generate busy tone data";
|
|
432
|
+
NSLog(@"%@", errorMsg);
|
|
433
|
+
reject(@"AUDIO_GENERATION_ERROR", errorMsg, nil);
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Create audio player
|
|
438
|
+
NSError *playerError = nil;
|
|
439
|
+
self->_busyTonePlayer = [[AVAudioPlayer alloc] initWithData:busyToneData error:&playerError];
|
|
440
|
+
|
|
441
|
+
if (playerError || !self->_busyTonePlayer) {
|
|
442
|
+
NSString *errorMsg = [NSString stringWithFormat:@"Failed to create audio player: %@", playerError.localizedDescription];
|
|
443
|
+
NSLog(@"%@", errorMsg);
|
|
444
|
+
reject(@"AUDIO_PLAYER_ERROR", errorMsg, playerError);
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Configure player
|
|
449
|
+
self->_busyTonePlayer.delegate = self;
|
|
450
|
+
self->_busyTonePlayer.numberOfLoops = -1; // Loop indefinitely
|
|
451
|
+
self->_busyTonePlayer.volume = 0.5; // Set reasonable volume
|
|
452
|
+
|
|
453
|
+
// Setup audio interruption handling
|
|
454
|
+
[self setupAudioInterruptionHandling];
|
|
455
|
+
|
|
456
|
+
// Prepare and play
|
|
457
|
+
BOOL prepared = [self->_busyTonePlayer prepareToPlay];
|
|
458
|
+
if (!prepared) {
|
|
459
|
+
NSString *errorMsg = @"Failed to prepare audio player";
|
|
460
|
+
NSLog(@"%@", errorMsg);
|
|
461
|
+
reject(@"AUDIO_PLAYER_ERROR", errorMsg, nil);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
BOOL playing = [self->_busyTonePlayer play];
|
|
466
|
+
if (playing) {
|
|
467
|
+
resolve(@YES);
|
|
468
|
+
} else {
|
|
469
|
+
NSString *errorMsg = @"Failed to start audio playback";
|
|
470
|
+
NSLog(@"%@", errorMsg);
|
|
471
|
+
reject(@"AUDIO_PLAYBACK_ERROR", errorMsg, nil);
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
- (NSData *)generateBusyToneData {
|
|
477
|
+
// Generate 1 second of busy tone pattern: 0.5s 480Hz tone, 0.5s silence
|
|
478
|
+
const int sampleRate = 44100; // Standard sample rate for better quality
|
|
479
|
+
const float duration = 1.0; // 1 second total
|
|
480
|
+
const float beepDuration = 0.5; // 0.5 seconds beep
|
|
481
|
+
const float frequency = 480.0; // 480 Hz busy tone frequency
|
|
482
|
+
|
|
483
|
+
int totalSamples = (int)(duration * sampleRate);
|
|
484
|
+
|
|
485
|
+
// Create PCM data buffer with proper size calculation
|
|
486
|
+
NSMutableData *audioData = [NSMutableData dataWithLength:44 + totalSamples * 2]; // WAV header + 16-bit samples
|
|
487
|
+
uint8_t *bytes = (uint8_t *)[audioData mutableBytes];
|
|
488
|
+
|
|
489
|
+
// Helper function to write little-endian values
|
|
490
|
+
void (^writeLittleEndian32)(uint8_t *, uint32_t) = ^(uint8_t *dest, uint32_t value) {
|
|
491
|
+
dest[0] = value & 0xFF;
|
|
492
|
+
dest[1] = (value >> 8) & 0xFF;
|
|
493
|
+
dest[2] = (value >> 16) & 0xFF;
|
|
494
|
+
dest[3] = (value >> 24) & 0xFF;
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
void (^writeLittleEndian16)(uint8_t *, uint16_t) = ^(uint8_t *dest, uint16_t value) {
|
|
498
|
+
dest[0] = value & 0xFF;
|
|
499
|
+
dest[1] = (value >> 8) & 0xFF;
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
// Write WAV header with proper endianness
|
|
503
|
+
memcpy(bytes, "RIFF", 4);
|
|
504
|
+
writeLittleEndian32(bytes + 4, 36 + totalSamples * 2);
|
|
505
|
+
memcpy(bytes + 8, "WAVE", 4);
|
|
506
|
+
memcpy(bytes + 12, "fmt ", 4);
|
|
507
|
+
writeLittleEndian32(bytes + 16, 16); // PCM format chunk size
|
|
508
|
+
writeLittleEndian16(bytes + 20, 1); // PCM format
|
|
509
|
+
writeLittleEndian16(bytes + 22, 1); // Mono
|
|
510
|
+
writeLittleEndian32(bytes + 24, sampleRate);
|
|
511
|
+
writeLittleEndian32(bytes + 28, sampleRate * 2); // Bytes per second
|
|
512
|
+
writeLittleEndian16(bytes + 32, 2); // Bytes per sample
|
|
513
|
+
writeLittleEndian16(bytes + 34, 16); // Bits per sample
|
|
514
|
+
memcpy(bytes + 36, "data", 4);
|
|
515
|
+
writeLittleEndian32(bytes + 40, totalSamples * 2);
|
|
516
|
+
|
|
517
|
+
// Generate audio samples
|
|
518
|
+
int16_t *samples = (int16_t *)(bytes + 44);
|
|
519
|
+
for (int i = 0; i < totalSamples; i++) {
|
|
520
|
+
float t = (float)i / sampleRate;
|
|
521
|
+
float cycleTime = fmod(t, duration); // Time within current cycle
|
|
522
|
+
|
|
523
|
+
if (cycleTime < beepDuration) {
|
|
524
|
+
// Generate 480Hz sine wave with proper amplitude
|
|
525
|
+
float sineWave = sinf(2.0f * M_PI * frequency * t);
|
|
526
|
+
float amplitude = 0.4f * sineWave; // Increased amplitude for better audibility
|
|
527
|
+
samples[i] = CFSwapInt16HostToLittle((int16_t)(amplitude * 32767.0f));
|
|
528
|
+
} else {
|
|
529
|
+
// Silence period
|
|
530
|
+
samples[i] = 0;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
return audioData;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
RCT_EXPORT_METHOD(stopBusyTone:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
|
538
|
+
{
|
|
539
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
540
|
+
[self stopBusyTone];
|
|
541
|
+
resolve(@YES);
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
- (void)stopBusyTone {
|
|
546
|
+
if (_busyTonePlayer) {
|
|
547
|
+
if (_busyTonePlayer.isPlaying) {
|
|
548
|
+
[_busyTonePlayer stop];
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
_busyTonePlayer = nil;
|
|
552
|
+
|
|
553
|
+
// Remove audio interruption observers
|
|
554
|
+
[self removeAudioInterruptionHandling];
|
|
555
|
+
|
|
556
|
+
// Only deactivate audio session if there are no active calls
|
|
557
|
+
// This prevents interfering with ongoing WebRTC audio sessions
|
|
558
|
+
BOOL hasActiveCall = [StreamVideoReactNative hasAnyActiveCall];
|
|
559
|
+
if (!hasActiveCall) {
|
|
560
|
+
NSError *error = nil;
|
|
561
|
+
[[AVAudioSession sharedInstance] setActive:NO
|
|
562
|
+
withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation
|
|
563
|
+
error:&error];
|
|
564
|
+
if (error) {
|
|
565
|
+
NSLog(@"Error deactivating audio session: %@", error.localizedDescription);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
#pragma mark - AVAudioPlayerDelegate
|
|
572
|
+
|
|
573
|
+
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
|
|
574
|
+
// Audio finished - this shouldn't happen with infinite loops
|
|
575
|
+
// Check if this is our player and handle cleanup if needed
|
|
576
|
+
if (player == _busyTonePlayer) {
|
|
577
|
+
_busyTonePlayer = nil;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error {
|
|
582
|
+
if (player == _busyTonePlayer) {
|
|
583
|
+
_busyTonePlayer = nil;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Note: audioPlayerBeginInterruption and audioPlayerEndInterruption are deprecated
|
|
588
|
+
// Audio interruptions are now handled via AVAudioSession notifications
|
|
589
|
+
// These are registered in setupAudioInterruptionHandling method
|
|
590
|
+
|
|
591
|
+
#pragma mark - Audio Interruption Handling
|
|
592
|
+
|
|
593
|
+
- (void)setupAudioInterruptionHandling {
|
|
594
|
+
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
595
|
+
selector:@selector(audioSessionInterrupted:)
|
|
596
|
+
name:AVAudioSessionInterruptionNotification
|
|
597
|
+
object:[AVAudioSession sharedInstance]];
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
- (void)removeAudioInterruptionHandling {
|
|
601
|
+
[[NSNotificationCenter defaultCenter] removeObserver:self
|
|
602
|
+
name:AVAudioSessionInterruptionNotification
|
|
603
|
+
object:[AVAudioSession sharedInstance]];
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
- (void)audioSessionInterrupted:(NSNotification *)notification {
|
|
607
|
+
AVAudioSessionInterruptionType interruptionType = [notification.userInfo[AVAudioSessionInterruptionTypeKey] unsignedIntegerValue];
|
|
608
|
+
|
|
609
|
+
switch (interruptionType) {
|
|
610
|
+
case AVAudioSessionInterruptionTypeBegan:
|
|
611
|
+
if (_busyTonePlayer && _busyTonePlayer.isPlaying) {
|
|
612
|
+
[_busyTonePlayer pause];
|
|
613
|
+
}
|
|
614
|
+
break;
|
|
615
|
+
|
|
616
|
+
case AVAudioSessionInterruptionTypeEnded: {
|
|
617
|
+
AVAudioSessionInterruptionOptions options = [notification.userInfo[AVAudioSessionInterruptionOptionKey] unsignedIntegerValue];
|
|
618
|
+
|
|
619
|
+
if (options & AVAudioSessionInterruptionOptionShouldResume) {
|
|
620
|
+
// Reactivate audio session
|
|
621
|
+
NSError *error = nil;
|
|
622
|
+
[[AVAudioSession sharedInstance] setActive:YES error:&error];
|
|
623
|
+
|
|
624
|
+
if (!error && _busyTonePlayer) {
|
|
625
|
+
[_busyTonePlayer play];
|
|
626
|
+
} else if (error) {
|
|
627
|
+
NSLog(@"Failed to reactivate audio session after interruption: %@", error.localizedDescription);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
break;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
359
635
|
@end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
archiveVersion = 1;
|
|
4
4
|
classes = {
|
|
5
5
|
};
|
|
6
|
-
objectVersion =
|
|
6
|
+
objectVersion = 70;
|
|
7
7
|
objects = {
|
|
8
8
|
|
|
9
9
|
/* Begin PBXCopyFilesBuildPhase section */
|
|
@@ -42,6 +42,21 @@
|
|
|
42
42
|
F4FF95D5245B92E700C19C63 /* StreamVideoReactNative-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "StreamVideoReactNative-Bridging-Header.h"; sourceTree = "<group>"; };
|
|
43
43
|
/* End PBXFileReference section */
|
|
44
44
|
|
|
45
|
+
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
|
46
|
+
05DBB1DF2E6B3F0600309346 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
|
|
47
|
+
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
|
48
|
+
membershipExceptions = (
|
|
49
|
+
busy.mp3,
|
|
50
|
+
);
|
|
51
|
+
target = 58B511DA1A9E6C8500147676 /* StreamVideoReactNative */;
|
|
52
|
+
};
|
|
53
|
+
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
|
54
|
+
|
|
55
|
+
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
|
56
|
+
05DBB1DA2E6B3ECE00309346 /* New Folder */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = "New Folder"; sourceTree = "<group>"; };
|
|
57
|
+
05DBB1DB2E6B3EEC00309346 /* Resources */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (05DBB1DF2E6B3F0600309346 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = Resources; sourceTree = "<group>"; };
|
|
58
|
+
/* End PBXFileSystemSynchronizedRootGroup section */
|
|
59
|
+
|
|
45
60
|
/* Begin PBXFrameworksBuildPhase section */
|
|
46
61
|
58B511D81A9E6C8500147676 /* Frameworks */ = {
|
|
47
62
|
isa = PBXFrameworksBuildPhase;
|
|
@@ -64,6 +79,8 @@
|
|
|
64
79
|
58B511D21A9E6C8500147676 = {
|
|
65
80
|
isa = PBXGroup;
|
|
66
81
|
children = (
|
|
82
|
+
05DBB1DB2E6B3EEC00309346 /* Resources */,
|
|
83
|
+
05DBB1DA2E6B3ECE00309346 /* New Folder */,
|
|
67
84
|
DDCF7DCC2C90359D00420842 /* PictureInPicture */,
|
|
68
85
|
DDCF7DCA2C90359D00420842 /* RTCViewPip.swift */,
|
|
69
86
|
DDCF7DC92C90359D00420842 /* RTCViewPipManager.mm */,
|
|
@@ -114,6 +131,7 @@
|
|
|
114
131
|
58B511D71A9E6C8500147676 /* Sources */,
|
|
115
132
|
58B511D81A9E6C8500147676 /* Frameworks */,
|
|
116
133
|
58B511D91A9E6C8500147676 /* CopyFiles */,
|
|
134
|
+
05DBB1DD2E6B3F0100309346 /* Resources */,
|
|
117
135
|
);
|
|
118
136
|
buildRules = (
|
|
119
137
|
);
|
|
@@ -156,6 +174,16 @@
|
|
|
156
174
|
};
|
|
157
175
|
/* End PBXProject section */
|
|
158
176
|
|
|
177
|
+
/* Begin PBXResourcesBuildPhase section */
|
|
178
|
+
05DBB1DD2E6B3F0100309346 /* Resources */ = {
|
|
179
|
+
isa = PBXResourcesBuildPhase;
|
|
180
|
+
buildActionMask = 2147483647;
|
|
181
|
+
files = (
|
|
182
|
+
);
|
|
183
|
+
runOnlyForDeploymentPostprocessing = 0;
|
|
184
|
+
};
|
|
185
|
+
/* End PBXResourcesBuildPhase section */
|
|
186
|
+
|
|
159
187
|
/* Begin PBXSourcesBuildPhase section */
|
|
160
188
|
58B511D71A9E6C8500147676 /* Sources */ = {
|
|
161
189
|
isa = PBXSourcesBuildPhase;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stream-io/video-react-native-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.21.0",
|
|
4
4
|
"description": "Stream Video SDK for React Native",
|
|
5
5
|
"author": "https://getstream.io",
|
|
6
6
|
"homepage": "https://getstream.io/video/docs/react-native/",
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"!**/.*"
|
|
46
46
|
],
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@stream-io/video-client": "1.
|
|
49
|
-
"@stream-io/video-react-bindings": "1.8.
|
|
48
|
+
"@stream-io/video-client": "1.32.0",
|
|
49
|
+
"@stream-io/video-react-bindings": "1.8.4",
|
|
50
50
|
"intl-pluralrules": "2.0.1",
|
|
51
51
|
"lodash.merge": "^4.6.2",
|
|
52
52
|
"react-native-url-polyfill": "1.3.0",
|
|
@@ -125,9 +125,9 @@
|
|
|
125
125
|
"@react-native-firebase/app": "^22.1.0",
|
|
126
126
|
"@react-native-firebase/messaging": "^22.1.0",
|
|
127
127
|
"@react-native/babel-preset": "^0.79.2",
|
|
128
|
-
"@stream-io/noise-cancellation-react-native": "^0.
|
|
128
|
+
"@stream-io/noise-cancellation-react-native": "^0.3.0",
|
|
129
129
|
"@stream-io/react-native-webrtc": "125.4.3",
|
|
130
|
-
"@stream-io/video-filters-react-native": "^0.
|
|
130
|
+
"@stream-io/video-filters-react-native": "^0.7.0",
|
|
131
131
|
"@testing-library/jest-native": "^5.4.3",
|
|
132
132
|
"@testing-library/react-native": "13.2.0",
|
|
133
133
|
"@tsconfig/node14": "14.1.3",
|
|
@@ -104,12 +104,7 @@ export const VideoRenderer = ({
|
|
|
104
104
|
isParticipantVideoEnabled(participant.sessionId);
|
|
105
105
|
|
|
106
106
|
useEffect(() => {
|
|
107
|
-
if (
|
|
108
|
-
Platform.OS === 'ios' &&
|
|
109
|
-
registerIosScreenshot &&
|
|
110
|
-
viewRef.current &&
|
|
111
|
-
canShowVideo
|
|
112
|
-
) {
|
|
107
|
+
if (Platform.OS === 'ios' && registerIosScreenshot && canShowVideo) {
|
|
113
108
|
registerIosScreenshot(participant, trackType, viewRef);
|
|
114
109
|
return () => {
|
|
115
110
|
deregisterIosScreenshot(participant, trackType);
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { useStreamVideoClient } from '@stream-io/video-react-bindings';
|
|
3
|
+
import { StreamVideoRN } from '../utils';
|
|
4
|
+
import { getLogger } from '@stream-io/video-client';
|
|
5
|
+
|
|
6
|
+
const BUSY_TONE_DURATION_IN_MS = 1500;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* This is a renderless component to play a busy tone for call rejection when the callee is busy for the user's outgoing call.
|
|
10
|
+
*/
|
|
11
|
+
const BusyTonePlayer = () => {
|
|
12
|
+
const client = useStreamVideoClient();
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (!client) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const unsubscribe = client.on('call.rejected', async (event) => {
|
|
20
|
+
const isCallCreatedByMe =
|
|
21
|
+
event.call.created_by.id === client.state.connectedUser?.id;
|
|
22
|
+
const isCalleeBusy = isCallCreatedByMe && event.reason === 'busy';
|
|
23
|
+
|
|
24
|
+
let busyToneTimeout: ReturnType<typeof setTimeout> | undefined;
|
|
25
|
+
|
|
26
|
+
const logger = getLogger(['RejectCallWhenBusy']);
|
|
27
|
+
|
|
28
|
+
if (isCalleeBusy) {
|
|
29
|
+
if (busyToneTimeout) {
|
|
30
|
+
clearTimeout(busyToneTimeout);
|
|
31
|
+
busyToneTimeout = undefined;
|
|
32
|
+
}
|
|
33
|
+
logger(
|
|
34
|
+
'info',
|
|
35
|
+
`Playing busy tone for call rejection for call cid: ${event.call.cid}`,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
StreamVideoRN.playBusyTone()
|
|
39
|
+
.then(() => {
|
|
40
|
+
busyToneTimeout = setTimeout(() => {
|
|
41
|
+
StreamVideoRN.stopBusyTone()
|
|
42
|
+
.then(() => {
|
|
43
|
+
logger(
|
|
44
|
+
'info',
|
|
45
|
+
`Stopped busy tone for call rejection for call cid: ${event.call.cid}`,
|
|
46
|
+
);
|
|
47
|
+
})
|
|
48
|
+
.catch((error) =>
|
|
49
|
+
logger('error', 'stopBusyTone failed:', error),
|
|
50
|
+
);
|
|
51
|
+
busyToneTimeout = undefined;
|
|
52
|
+
}, BUSY_TONE_DURATION_IN_MS);
|
|
53
|
+
})
|
|
54
|
+
.catch((error) => {
|
|
55
|
+
logger('error', 'playBusyTone failed:', error);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return () => {
|
|
59
|
+
StreamVideoRN.stopBusyTone().catch((err) =>
|
|
60
|
+
logger('error', 'stopBusyTone on cleanup failed:', err),
|
|
61
|
+
);
|
|
62
|
+
clearTimeout(busyToneTimeout);
|
|
63
|
+
unsubscribe();
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
}, [client]);
|
|
67
|
+
|
|
68
|
+
return null;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export default BusyTonePlayer;
|
|
@@ -10,6 +10,7 @@ import { translations } from '../translations';
|
|
|
10
10
|
import { type DeepPartial, StreamTheme } from '../contexts/ThemeContext';
|
|
11
11
|
import { type Theme } from '../theme/theme';
|
|
12
12
|
import { ScreenshotIosContextProvider } from '../contexts/internal/ScreenshotIosContext';
|
|
13
|
+
import BusyTonePlayer from './BusyTonePlayer';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
*
|
|
@@ -59,11 +60,10 @@ export const StreamVideo = (
|
|
|
59
60
|
translationsOverrides={translationsOverrides}
|
|
60
61
|
i18nInstance={i18nInstance}
|
|
61
62
|
>
|
|
63
|
+
<PushRegister />
|
|
64
|
+
<BusyTonePlayer />
|
|
62
65
|
<StreamTheme style={style}>
|
|
63
|
-
<ScreenshotIosContextProvider>
|
|
64
|
-
<PushRegister />
|
|
65
|
-
{children}
|
|
66
|
-
</ScreenshotIosContextProvider>
|
|
66
|
+
<ScreenshotIosContextProvider>{children}</ScreenshotIosContextProvider>
|
|
67
67
|
</StreamTheme>
|
|
68
68
|
</StreamVideoProvider>
|
|
69
69
|
);
|
|
@@ -5,6 +5,7 @@ import newNotificationCallbacks, {
|
|
|
5
5
|
} from '../internal/newNotificationCallbacks';
|
|
6
6
|
import { setupIosCallKeepEvents } from '../push/setupIosCallKeepEvents';
|
|
7
7
|
import { setupIosVoipPushEvents } from '../push/setupIosVoipPushEvents';
|
|
8
|
+
import { NativeModules } from 'react-native';
|
|
8
9
|
|
|
9
10
|
// Utility type for deep partial
|
|
10
11
|
type DeepPartial<T> = {
|
|
@@ -62,6 +63,7 @@ const DEFAULT_STREAM_VIDEO_CONFIG: StreamVideoConfig = {
|
|
|
62
63
|
|
|
63
64
|
export class StreamVideoRN {
|
|
64
65
|
private static config = DEFAULT_STREAM_VIDEO_CONFIG;
|
|
66
|
+
private static busyToneTimeout: NodeJS.Timeout | null = null;
|
|
65
67
|
|
|
66
68
|
/**
|
|
67
69
|
* Update the global config for StreamVideoRN except for push config.
|
|
@@ -164,4 +166,18 @@ export class StreamVideoRN {
|
|
|
164
166
|
newNotificationCallbacks.current?.filter((cb) => cb !== callback);
|
|
165
167
|
};
|
|
166
168
|
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Play native busy tone for call rejection
|
|
172
|
+
*/
|
|
173
|
+
static async playBusyTone() {
|
|
174
|
+
return NativeModules.StreamVideoReactNative?.playBusyTone();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Stop native busy tone
|
|
179
|
+
*/
|
|
180
|
+
static async stopBusyTone() {
|
|
181
|
+
return NativeModules.StreamVideoReactNative?.stopBusyTone();
|
|
182
|
+
}
|
|
167
183
|
}
|
|
@@ -169,6 +169,9 @@ export const firebaseDataHandler = async (
|
|
|
169
169
|
const created_by_id = data.created_by_id as string;
|
|
170
170
|
const receiver_id = data.receiver_id as string;
|
|
171
171
|
|
|
172
|
+
const video_client = await pushConfig.createStreamVideoClient();
|
|
173
|
+
await video_client?.onRingingCall(call_cid);
|
|
174
|
+
|
|
172
175
|
const shouldCallBeClosed = (callToCheck: Call) => {
|
|
173
176
|
const { mustEndCall } = shouldCallBeEnded(
|
|
174
177
|
callToCheck,
|
|
@@ -46,6 +46,7 @@ export const onVoipNotificationReceived = async (
|
|
|
46
46
|
}
|
|
47
47
|
const logger = getLogger(['setupIosVoipPushEvents']);
|
|
48
48
|
const client = await pushConfig.createStreamVideoClient();
|
|
49
|
+
|
|
49
50
|
if (!client) {
|
|
50
51
|
logger(
|
|
51
52
|
'debug',
|
|
@@ -53,6 +54,13 @@ export const onVoipNotificationReceived = async (
|
|
|
53
54
|
);
|
|
54
55
|
return;
|
|
55
56
|
}
|
|
57
|
+
const shouldRejectCallWhenBusy = client['rejectCallWhenBusy'] ?? false;
|
|
58
|
+
if (shouldRejectCallWhenBusy) {
|
|
59
|
+
// inform the iOS native module that we should reject call when busy
|
|
60
|
+
NativeModules.StreamVideoReactNative.setShouldRejectCallWhenBusy(
|
|
61
|
+
shouldRejectCallWhenBusy,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
56
64
|
const callFromPush = await client.onRingingCall(call_cid);
|
|
57
65
|
let uuid = '';
|
|
58
66
|
try {
|
|
@@ -13,11 +13,11 @@ export function setupIosVoipPushEvents(
|
|
|
13
13
|
return;
|
|
14
14
|
}
|
|
15
15
|
const logger = getLogger(['setupIosVoipPushEvents']);
|
|
16
|
-
if (!pushConfig.
|
|
16
|
+
if (!pushConfig.ios.pushProviderName) {
|
|
17
17
|
// TODO: remove this check and find a better way once we have telecom integration for android
|
|
18
18
|
logger(
|
|
19
19
|
'debug',
|
|
20
|
-
'
|
|
20
|
+
'ios pushProviderName is not defined, so skipping the setupIosVoipPushEvents',
|
|
21
21
|
);
|
|
22
22
|
return;
|
|
23
23
|
}
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '1.
|
|
1
|
+
export const version = '1.21.0';
|