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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ A screen record module for React Native.
- Support iOS >= 11.0 (Simulator is not work)

- Support Android

- minSdkVersion = 26
- compileSdkVersion = 33
- targetSdkVersion = 31
- use [HBRecorder](https://github.com/HBiSoft/HBRecorder)

- Support Expo by using [expo-record-screen-config-plugin](https://github.com/closetothe/expo-record-screen-config-plugin)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not merge the plugin here?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any update?


## Installation

```sh
Expand Down Expand Up @@ -57,10 +60,14 @@ npx pod-install
### Recording full screen

```js
import RecordScreen, { RecordingStartResponse } from 'react-native-record-screen';
import RecordScreen, {
RecordingStartResponse,
} from 'react-native-record-screen';

// recording start
const res = RecordScreen.startRecording().catch((error) => console.error(error));
const res = RecordScreen.startRecording().catch((error) =>
console.error(error)
);
if (res === RecordingStartResponse.PermissionError) {
// user denies access
}
Expand Down Expand Up @@ -99,7 +106,7 @@ if (res) {
RecordScreen.startRecording({
bitrate: 1024000, // default 236390400
fps: 24, // default 60
})
});
```

### Clean Sandbox
Expand Down
6 changes: 3 additions & 3 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ PODS:
- React-jsinspector (0.72.0)
- React-logger (0.72.0):
- glog
- react-native-record-screen (0.6.0):
- react-native-record-screen (0.0.5):
- RCT-Folly (= 2021.07.22.00)
- React-Core
- react-native-video (5.2.1):
Expand Down Expand Up @@ -700,7 +700,7 @@ SPEC CHECKSUMS:
React-jsiexecutor: 990287d74aedc4fdd08ebd80736b1a5c71b54da2
React-jsinspector: 8d754fc957255a29d93e52fc67a895045cdc8703
React-logger: 454ffb01980778a43b0153ee98721d0275b56616
react-native-record-screen: abc1c23577598fa2d576ade433706d6a8f7e4654
react-native-record-screen: 2fdf2c4d11a54363bced92ff124c206062577611
react-native-video: c26780b224543c62d5e1b2a7244a5cd1b50e8253
React-NativeModulesApple: 038cd625999ff352fc13d11fd335ea7509794599
React-perflogger: 684a11499a0589cc42135d6d5cc04d0e4e0e261a
Expand All @@ -725,4 +725,4 @@ SPEC CHECKSUMS:

PODFILE CHECKSUM: ecc2815bd41817eeabc4f10f1f2a34740ceaac89

COCOAPODS: 1.11.3
COCOAPODS: 1.12.1
6 changes: 4 additions & 2 deletions example/ios/RecordScreenExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@
baseConfigurationReference = 5B7EB9410499542E8C5724F5 /* Pods-RecordScreenExample-RecordScreenExampleTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
DEVELOPMENT_TEAM = 2YU2NGKRBH;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
Expand Down Expand Up @@ -465,6 +466,7 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
COPY_PHASE_STRIP = NO;
DEVELOPMENT_TEAM = 2YU2NGKRBH;
INFOPLIST_FILE = RecordScreenExampleTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
LD_RUNPATH_SEARCH_PATHS = (
Expand All @@ -490,7 +492,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 6HP9M88DJ2;
DEVELOPMENT_TEAM = 2YU2NGKRBH;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = RecordScreenExample/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
Expand Down Expand Up @@ -518,7 +520,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 6HP9M88DJ2;
DEVELOPMENT_TEAM = 2YU2NGKRBH;
INFOPLIST_FILE = RecordScreenExample/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
Expand Down
93 changes: 82 additions & 11 deletions ios/RecordScreen.mm
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,81 @@ - (int) adjustMultipleOf2:(int)value;
self.fps = [RCTConvert int: config[@"fps"]];
}


RCT_REMAP_METHOD(requestPermissions, requestResolve:(RCTPromiseResolveBlock)resolve requestReject:(RCTPromiseRejectBlock)reject)
{
UIApplication *app = [UIApplication sharedApplication];
_backgroundRenderingID = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:_backgroundRenderingID];
_backgroundRenderingID = UIBackgroundTaskInvalid;
}];

self.screenRecorder = [RPScreenRecorder sharedRecorder];
if (self.screenRecorder.isRecording) {
return;
}

self.encounteredFirstBuffer = NO;

NSArray *pathDocuments = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *outputURL = pathDocuments[0];

NSString *videoOutPath = [[outputURL stringByAppendingPathComponent:[NSString stringWithFormat:@"%u", arc4random_uniform(1000000)]] stringByAppendingPathExtension:@"mp4"];

NSError *error;
self.writer = [AVAssetWriter assetWriterWithURL:[NSURL fileURLWithPath:videoOutPath] fileType:AVFileTypeMPEG4 error:&error];
if (!self.writer) {
NSLog(@"writer: %@", error);
abort();
}

AudioChannelLayout acl = { 0 };
acl.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
self.audioInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeAudio outputSettings:@{ AVFormatIDKey: @(kAudioFormatMPEG4AAC), AVSampleRateKey: @(44100), AVChannelLayoutKey: [NSData dataWithBytes: &acl length: sizeof( acl ) ], AVEncoderBitRateKey: @(320000), AVEncoderAudioQualityKey: @(127)}];
self.micInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeAudio outputSettings:@{ AVFormatIDKey: @(kAudioFormatMPEG4AAC), AVSampleRateKey: @(44100), AVChannelLayoutKey: [NSData dataWithBytes: &acl length: sizeof( acl ) ], AVEncoderBitRateKey: @(320000), AVEncoderAudioQualityKey: @(127)}];

self.audioInput.preferredVolume = 0.0;
self.micInput.preferredVolume = 0.0;

NSDictionary *compressionProperties = @{AVVideoProfileLevelKey : AVVideoProfileLevelH264HighAutoLevel,
AVVideoH264EntropyModeKey : AVVideoH264EntropyModeCABAC,
AVVideoAverageBitRateKey : @(self.bitrate),
AVVideoMaxKeyFrameIntervalKey : @(self.fps),
AVVideoAllowFrameReorderingKey : @NO};

NSLog(@"width: %d", [self adjustMultipleOf2:self.screenWidth]);
NSLog(@"height: %d", [self adjustMultipleOf2:self.screenHeight]);
if (@available(iOS 11.0, *)) {
NSDictionary *videoSettings = @{AVVideoCompressionPropertiesKey : compressionProperties,
AVVideoCodecKey : AVVideoCodecTypeH264,
AVVideoWidthKey : @([self adjustMultipleOf2:self.screenWidth]),
AVVideoHeightKey : @([self adjustMultipleOf2:self.screenHeight])};

self.videoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:videoSettings];
} else {
// Fallback on earlier versions
}

[self.writer addInput:self.audioInput];
[self.writer addInput:self.micInput];
[self.writer addInput:self.videoInput];
[self.videoInput setMediaTimeScale:60];
[self.writer setMovieTimeScale:60];
[self.videoInput setExpectsMediaDataInRealTime:YES];

if (self.enableMic) {
self.screenRecorder.microphoneEnabled = YES;
}

[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
if (granted) {
resolve(@"granted");
} else {
resolve(@"denied");
}
}];
}

RCT_REMAP_METHOD(startRecording, resolve:(RCTPromiseResolveBlock)resolve rejecte:(RCTPromiseRejectBlock)reject)
{
UIApplication *app = [UIApplication sharedApplication];
Expand All @@ -77,10 +152,10 @@ - (int) adjustMultipleOf2:(int)value;

self.encounteredFirstBuffer = NO;

NSArray *pathDocuments = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSArray *pathDocuments = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *outputURL = pathDocuments[0];

NSString *videoOutPath = [[outputURL stringByAppendingPathComponent:[NSString stringWithFormat:@"%u", arc4random() % 1000]] stringByAppendingPathExtension:@"mp4"];
NSString *videoOutPath = [[outputURL stringByAppendingPathComponent:[NSString stringWithFormat:@"%u", arc4random_uniform(1000000)]] stringByAppendingPathExtension:@"mp4"];

NSError *error;
self.writer = [AVAssetWriter assetWriterWithURL:[NSURL fileURLWithPath:videoOutPath] fileType:AVFileTypeMPEG4 error:&error];
Expand All @@ -90,9 +165,9 @@ - (int) adjustMultipleOf2:(int)value;
}

AudioChannelLayout acl = { 0 };
acl.mChannelLayoutTag = kAudioChannelLayoutTag_Mono;
self.audioInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeAudio outputSettings:@{ AVFormatIDKey: @(kAudioFormatMPEG4AAC), AVSampleRateKey: @(44100), AVChannelLayoutKey: [NSData dataWithBytes: &acl length: sizeof( acl ) ], AVEncoderBitRateKey: @(64000)}];
self.micInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeAudio outputSettings:@{ AVFormatIDKey: @(kAudioFormatMPEG4AAC), AVSampleRateKey: @(44100), AVChannelLayoutKey: [NSData dataWithBytes: &acl length: sizeof( acl ) ], AVEncoderBitRateKey: @(64000)}];
acl.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
self.audioInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeAudio outputSettings:@{ AVFormatIDKey: @(kAudioFormatMPEG4AAC), AVSampleRateKey: @(44100), AVChannelLayoutKey: [NSData dataWithBytes: &acl length: sizeof( acl ) ], AVEncoderBitRateKey: @(320000), AVEncoderAudioQualityKey: @(127)}];
self.micInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeAudio outputSettings:@{ AVFormatIDKey: @(kAudioFormatMPEG4AAC), AVSampleRateKey: @(44100), AVChannelLayoutKey: [NSData dataWithBytes: &acl length: sizeof( acl ) ], AVEncoderBitRateKey: @(320000), AVEncoderAudioQualityKey: @(127)}];

self.audioInput.preferredVolume = 0.0;
self.micInput.preferredVolume = 0.0;
Expand Down Expand Up @@ -200,11 +275,7 @@ - (int) adjustMultipleOf2:(int)value;
break;
case RPSampleBufferTypeAudioApp:
if (self.audioInput.isReadyForMoreMediaData) {
if(self.enableMic){
[self.audioInput appendSampleBuffer:sampleBuffer];
} else {
[self muteAudioInBuffer:sampleBuffer];
}
[self.audioInput appendSampleBuffer:sampleBuffer];
}
break;
case RPSampleBufferTypeAudioMic:
Expand Down Expand Up @@ -282,7 +353,7 @@ - (int) adjustMultipleOf2:(int)value;
cleanRejecte:(RCTPromiseRejectBlock)reject)
{

NSArray *pathDocuments = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSArray *pathDocuments = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *path = pathDocuments[0];
NSLog(@"startCapture: %@", path);
[[NSFileManager defaultManager] removeItemAtPath:path error:nil];
Expand Down
19 changes: 10 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-native-record-screen",
"version": "0.6.1",
"name": "@closetothe/react-native-record-screen",
"version": "0.0.7",
"description": "react-native-record-screen",
"main": "lib/commonjs/index",
"module": "lib/module/index",
Expand Down Expand Up @@ -43,19 +43,20 @@
"ios",
"android"
],
"repository": "https://github.com/yutasuzuki/react-native-record-screen",
"author": "Yuta Suzuki <yutasuzuki.r@gmail.com> (https://github.com/yutasuzuki)",
"repository": "https://github.com/closettothe/react-native-record-screen",
"author": "Jamiel Rahi <app.gildew@gmail.com> (https://github.com/closetothe)",
"license": "MIT",
"bugs": {
"url": "https://github.com/yutasuzuki/react-native-record-screen/issues"
"url": "https://github.com/closetothe/react-native-record-screen/issues"
},
"homepage": "https://github.com/yutasuzuki/react-native-record-screen#readme",
"homepage": "https://github.com/closetothe/react-native-record-screen#readme",
"publishConfig": {
"registry": "https://registry.npmjs.org/"
"registry": "https://registry.npmjs.org/",
"access": "public"
},
"devDependencies": {
"@evilmartians/lefthook": "^1.2.2",
"@commitlint/config-conventional": "^17.0.2",
"@evilmartians/lefthook": "^1.2.2",
"@react-native-community/eslint-config": "^3.0.2",
"@release-it/conventional-changelog": "^5.0.0",
"@types/jest": "^28.1.2",
Expand All @@ -72,7 +73,7 @@
"react": "18.2.0",
"react-native": "0.72.0",
"react-native-builder-bob": "^0.20.0",
"release-it": "^15.0.0",
"release-it": "^16.1.5",
"typescript": "^5.0.2"
},
"resolutions": {
Expand Down
13 changes: 10 additions & 3 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export type RecordScreenConfigType = {
fps?: number;
bitrate?: number;
mic?: boolean;
width?: number;
height?: number;
};

export type RecordingSuccessResponse = {
Expand All @@ -34,6 +36,7 @@ type RecordScreenNativeModule = {
setup(
config: RecordScreenConfigType & { width: number; height: number }
): void;
requestPermission(): Promise<'denied' | 'granted'>;
startRecording(): Promise<RecordingStartResponse>;
stopRecording(): Promise<RecordingResponse>;
clean(): Promise<string>;
Expand All @@ -45,17 +48,21 @@ const RS = RecordScreen as RecordScreenNativeModule;

class ReactNativeRecordScreenClass {
private setup(config: RecordScreenConfigType = {}): void {
const { width, height } = Dimensions.get('window');
const window = Dimensions.get('window');
RS.setup({
mic: true,
width,
height,
width: config.width ?? window.width * 2,
height: config.height ?? window.height * 2,
fps: 60,
bitrate: 1920 * 1080 * 144,
...config,
});
}

requestPermission() {
return RS.requestPermission();
}

startRecording(config: RecordScreenConfigType = {}) {
this.setup(config);
return RS.startRecording();
Expand Down
Loading