├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── question.md └── stale.yml ├── .gitignore ├── .idea └── codeStyles │ └── Project.xml ├── License.txt ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── release │ ├── HBRecorderDemo.apk │ └── output-metadata.json └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── hbisoft │ │ └── hbrecorderexample │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── hbisoft │ │ │ └── hbrecorderexample │ │ │ ├── MainActivity.java │ │ │ └── SettingsActivity.java │ └── res │ │ ├── drawable │ │ ├── icon.png │ │ └── ripple_effect.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── menu │ │ └── menu_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values │ │ ├── arrays.xml │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ ├── backup_descriptor.xml │ │ └── pref_main.xml │ └── test │ └── java │ └── com │ └── hbisoft │ └── hbrecorderexample │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── hbrecorder ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── hbisoft │ │ └── hbrecorder │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── hbisoft │ │ │ └── hbrecorder │ │ │ ├── Constants.java │ │ │ ├── Countdown.java │ │ │ ├── FileObserver.java │ │ │ ├── HBRecorder.java │ │ │ ├── HBRecorderCodecInfo.java │ │ │ ├── HBRecorderListener.java │ │ │ ├── MyListener.java │ │ │ ├── NotificationReceiver.java │ │ │ └── ScreenRecordService.java │ └── res │ │ ├── drawable │ │ └── icon.png │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── hbisoft │ └── hbrecorder │ └── ExampleUnitTest.java ├── jitpack.yml └── settings.gradle /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Issue template for a bug reports 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | READ THIS BEFORE OPENING AN ISSUE: 11 | ----------------------- 12 | - Fill in all the information below. 13 | - Have a look at previously asked questions to see if your issue has been resolved. 14 | - Properly format your issue. 15 | - All issues should be asked in English. 16 | - Delete all the above before posting your issue 17 | 18 | **Describe the bug** 19 | A clear and concise description of what the bug is. 20 | 21 | **Log** 22 | Please provide a well formatted bug report (by adding 4 spaces before the log) 23 | 24 | **Can it be reproduced in demo app** 25 | Yes or no 26 | 27 | **HBRecorder version** 28 | for example 1.0.1 29 | 30 | **Device information** 31 | - Make/model 32 | - SDK version 33 | 34 | **Screenshots** 35 | If applicable, add screenshots to help explain your problem. 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask a question related to the project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | Before filing a bug: 11 | ----------------------- 12 | - Have a look at previously asked questions to see if your question has been asked before. 13 | - Properly format your question. 14 | - All questions should be asked in English. 15 | - Questions that are not related to the project will be deleted 16 | - Delete all the above before posting 17 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-stale - https://github.com/probot/stale 2 | 3 | # Number of days of inactivity before an Issue or Pull Request becomes stale 4 | daysUntilStale: 7 5 | 6 | # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. 7 | # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. 8 | daysUntilClose: 3 9 | 10 | # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable 11 | exemptLabels: 12 | - is:bug 13 | - is:enhancement 14 | - is:discussion 15 | 16 | # Label to use when marking as stale 17 | staleLabel: status:stale 18 | 19 | # Comment to post when marking as stale. Set to `false` to disable 20 | markComment: > 21 | This issue has been automatically marked as stale because it has not had 22 | activity in the last 7 days. It will be closed if no further activity 23 | occurs within the next 3 days. Thank you for your contributions. 24 | # Limit to only `issues` or `pulls` 25 | only: issues 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | /.idea/*.xml 11 | .DS_Store 12 | /build 13 | /captures 14 | .externalNativeBuild 15 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | xmlns:android 14 | 15 | ^$ 16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 | xmlns:.* 25 | 26 | ^$ 27 | 28 | 29 | BY_NAME 30 | 31 |
32 |
33 | 34 | 35 | 36 | .*:id 37 | 38 | http://schemas.android.com/apk/res/android 39 | 40 | 41 | 42 |
43 |
44 | 45 | 46 | 47 | .*:name 48 | 49 | http://schemas.android.com/apk/res/android 50 | 51 | 52 | 53 |
54 |
55 | 56 | 57 | 58 | name 59 | 60 | ^$ 61 | 62 | 63 | 64 |
65 |
66 | 67 | 68 | 69 | style 70 | 71 | ^$ 72 | 73 | 74 | 75 |
76 |
77 | 78 | 79 | 80 | .* 81 | 82 | ^$ 83 | 84 | 85 | BY_NAME 86 | 87 |
88 |
89 | 90 | 91 | 92 | .* 93 | 94 | http://schemas.android.com/apk/res/android 95 | 96 | 97 | ANDROID_ATTRIBUTE_ORDER 98 | 99 |
100 |
101 | 102 | 103 | 104 | .* 105 | 106 | .* 107 | 108 | 109 | BY_NAME 110 | 111 |
112 |
113 |
114 |
115 |
116 |
-------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2020] [HBiSoft] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **

Creating and maintaining a library like this requires a significant amount of time and effort.

** 2 | 3 | **

If you’d like to show your appreciation, you can do so below:

** 4 | 5 |

Buy Me A Coffee

6 | 7 | --- 8 |
9 | 10 | # HBRecorder 11 | [![](https://jitpack.io/v/HBiSoft/HBRecorder.svg)](https://jitpack.io/#HBiSoft/HBRecorder) 12 | [![Android Arsenal]( https://img.shields.io/badge/Android%20Arsenal-HBRecorder-green.svg?style=flat )]( https://android-arsenal.com/details/1/7897 ) 13 | 14 |

Lightweight screen and audio recording Android library

Requires API level 21>

15 | 16 |

17 | 18 |
19 | 20 |

Demo:

21 | 22 |

Download the demo app here

23 | 24 |

25 | 26 |

27 | 28 | 29 | **Adding the library to your project:** 30 | --- 31 | Add the following in your root build.gradle at the end of repositories: 32 | 33 | ```java 34 | allprojects { 35 | repositories { 36 | ... 37 | maven { url 'https://jitpack.io' } 38 | } 39 | } 40 | ``` 41 | 42 | Implement library in your app level build.gradle: 43 | 44 | ```java 45 | dependencies { 46 | implementation 'com.github.HBiSoft:HBRecorder:3.0.9' 47 | } 48 | ``` 49 | 50 | 51 | **Implementing the library:** 52 | --- 53 | 1. In your `Activity`, first implement `HBRecorder`, as shown below: 54 | 55 | ```java 56 | public class MainActivity extends AppCompatActivity implements HBRecorderListener { 57 | ``` 58 | 59 | 2. `Alt+Enter` to implement the following methods: 60 | 61 | ```java 62 | @Override 63 | public void HBRecorderOnStart() { 64 | //When the recording starts 65 | } 66 | 67 | @Override 68 | public void HBRecorderOnComplete() { 69 | //After file was created 70 | } 71 | @Override 72 | public void HBRecorderOnError(int errorCode) { 73 | //When an error occurs 74 | } 75 | 76 | @Override 77 | public void HBRecorderOnPause() { 78 | //When recording was paused 79 | } 80 | 81 | @Override 82 | public void HBRecorderOnResume() { 83 | //When recording was resumed 84 | } 85 | ``` 86 | 87 | 3. Init `HBRecorder` as shown below: 88 | ```java 89 | public class MainActivity extends AppCompatActivity implements HBRecorderListener { 90 | HBRecorder hbRecorder; 91 | 92 | @Override 93 | protected void onCreate(Bundle savedInstanceState) { 94 | super.onCreate(savedInstanceState); 95 | setContentView(R.layout.activity_main); 96 | 97 | //Init HBRecorder 98 | hbRecorder = new HBRecorder(this, this); 99 | 100 | } 101 | ``` 102 | 103 | 4. Add the following permissions in your manifest: 104 | ```java 105 | 106 | 107 | 108 | 109 | 110 | 111 | ``` 112 | 113 | That's it, `HBRecorder` is now ready to be used. 114 | 115 | --- 116 | 117 | When you want to start capturing your screen, it is important you do it as shown below: 118 | --- 119 | ```java 120 | private void startRecordingScreen() { 121 | MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE); 122 | Intent permissionIntent = mediaProjectionManager != null ? mediaProjectionManager.createScreenCaptureIntent() : null; 123 | startActivityForResult(permissionIntent, SCREEN_RECORD_REQUEST_CODE); 124 | } 125 | 126 | @Override 127 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 128 | super.onActivityResult(requestCode, resultCode, data); 129 | if (requestCode == SCREEN_RECORD_REQUEST_CODE) { 130 | if (resultCode == RESULT_OK) { 131 | //Start screen recording 132 | hbRecorder.startScreenRecording(data, resultCode); 133 | 134 | } 135 | } 136 | } 137 | ``` 138 | 139 | All available methods: 140 | --- 141 | ```java 142 | // Set the output path as a String 143 | // Only use this on devices running Android 9 and lower or you have to add android:requestLegacyExternalStorage="true" in your manifest 144 | // Defaults to - Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES) 145 | hbrecorder.setOutputPath(String); 146 | // Set output Uri 147 | // Only use this on devices running Android 10> 148 | // When setting a Uri ensure you pass the same name to HBRecorder as what you set in ContentValues (DISPLAY_NAME and TITLE) 149 | hbRecorder.setOutputUri(Uri); 150 | // Set file name as String 151 | // Defaults to - quality + time stamp. For example HD-2019-08-14-10-09-58.mp4 152 | hbrecorder.setFileName(String); 153 | // Set audio bitrate as int 154 | // Defaults to - 128000 155 | hbrecorder.setAudioBitrate(int); 156 | // Set audio sample rate as int 157 | // Defaults to - 44100 158 | hbrecorder.setAudioSamplingRate(int); 159 | // Enable/Disable audio 160 | // Defaults to true 161 | hbrecorder.isAudioEnabled(boolean); 162 | // Enable/Disable HD Video 163 | // Defaults to true 164 | hbrecorder.recordHDVideo(boolean); 165 | // Get file path as String 166 | hbrecorder.getFilePath(); 167 | // Get file name as String 168 | hbrecorder.getFileName(); 169 | // Start recording screen by passing it as Intent inside onActivityResult 170 | hbrecorder.startScreenRecording(Intent); 171 | // Pause screen recording (only available for devices running 24>) 172 | hbrecorder.pauseScreenRecording(); 173 | // Resume screen recording 174 | hbreccorder.resumeScreenRecording(); 175 | // Stop screen recording 176 | hbrecorder.stopScreenRecording(); 177 | // Check if recording is in progress 178 | hbrecorder.isBusyRecording(); 179 | // Set notification icon by passing, for example R.drawable.myicon 180 | // Defaults to R.drawable.icon 181 | hbrecorder.setNotificationSmallIcon(int); 182 | // Set notification icon using byte array 183 | hbrecorder.setNotificationSmallIcon(byte[]); 184 | // Set notification icon using vector drawable 185 | hbRecorder.setNotificationSmallIconVector(vector); 186 | // Set notification title 187 | // Defaults to "Recording your screen" 188 | hbrecorder.setNotificationTitle(String); 189 | // Set notification description 190 | // Defaults to "Drag down to stop the recording" 191 | hbrecorder.setNotificationDescription(String); 192 | // Set notification stop button text 193 | // Defaults to "STOP RECORDING" 194 | hbrecorder.setNotificationButtonText(String); 195 | // Set output orientation (in degrees) 196 | hbrecorder.setOrientationHint(int); 197 | // Set max output file size 198 | hbrecorder.setMaxFileSize(long); 199 | // Set max time (in seconds) 200 | hbRecorder.setMaxDuration(int); 201 | ``` 202 | 203 | Custom setting: 204 | --- 205 | When you want to enable custom settings you must first call: 206 | ```java 207 | hbRecorder.enableCustomSettings(); 208 | ``` 209 | Then you can set the following: 210 | ```java 211 | //MUST BE ONE OF THE FOLLOWING - https://developer.android.com/reference/android/media/MediaRecorder.AudioSource.html 212 | hbRecorder.setAudioSource(String); 213 | //MUST BE ONE OF THE FOLLOWING - https://developer.android.com/reference/android/media/MediaRecorder.VideoEncoder.html 214 | hbRecorder.setVideoEncoder(String); 215 | //If nothing is provided, it will select the highest value supported by your device 216 | hbRecorder.setScreenDimensions(HeightInPx, WidthInPx); 217 | //Frame rate is device dependent 218 | //You can use Camcoderprofile to determine the frame rate 219 | hbRecorder.setVideoFrameRate(int); 220 | //The bitrate is also dependent on the device and the frame rate that is set 221 | hbRecorder.setVideoBitrate(int); 222 | //MUST BE ONE OF THE FOLLOWING - https://developer.android.com/reference/android/media/MediaRecorder.OutputFormat.html 223 | hbRecorder.setOutputFormat(String); 224 | ``` 225 | 226 | --- 227 | It is important to note that limitations are device dependent. It is best to set the video encoder to "DEFAULT" and let `MediaRecorder` pick the best encoder. 228 | 229 | In the demo app you will have the option to test different video encoders, bitrate, frame rate and output formats. If your device does not support any of the parameters you have selected `HBRecorderOnError` will be called. 230 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdk 34 5 | defaultConfig { 6 | applicationId "com.hbisoft.hbrecorderexample" 7 | minSdk 17 8 | targetSdk 34 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | 20 | compileOptions { 21 | sourceCompatibility JavaVersion.VERSION_1_8 22 | targetCompatibility JavaVersion.VERSION_1_8 23 | } 24 | namespace 'com.hbisoft.hbrecorderexample' 25 | 26 | } 27 | 28 | dependencies { 29 | implementation fileTree(dir: 'libs', include: ['*.jar']) 30 | implementation 'androidx.appcompat:appcompat:1.6.1' 31 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 32 | implementation 'androidx.legacy:legacy-support-core-utils:1.0.0' 33 | implementation 'androidx.preference:preference:1.2.0' 34 | testImplementation 'junit:junit:4.13.2' 35 | androidTestImplementation 'androidx.test:runner:1.5.2' 36 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' 37 | implementation project(path: ':hbrecorder') 38 | } 39 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/release/HBRecorderDemo.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HBiSoft/HBRecorder/b8c941de636b77376b818bebc61fd87ac4c03975/app/release/HBRecorderDemo.apk -------------------------------------------------------------------------------- /app/release/output-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "artifactType": { 4 | "type": "APK", 5 | "kind": "Directory" 6 | }, 7 | "applicationId": "com.hbisoft.hbrecorderexample", 8 | "variantName": "release", 9 | "elements": [ 10 | { 11 | "type": "SINGLE", 12 | "filters": [], 13 | "attributes": [], 14 | "versionCode": 1, 15 | "versionName": "1.0", 16 | "outputFile": "app-release.apk" 17 | } 18 | ], 19 | "elementType": "File" 20 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/hbisoft/hbrecorderexample/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.hbisoft.hbrecorderexample; 2 | 3 | import android.content.Context; 4 | import androidx.test.InstrumentationRegistry; 5 | import androidx.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.hbisoft.hbrecorderexample", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 24 | 25 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/hbisoft/hbrecorderexample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.hbisoft.hbrecorderexample; 2 | 3 | import android.Manifest; 4 | import android.content.ContentResolver; 5 | import android.content.ContentValues; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.SharedPreferences; 9 | import android.content.pm.PackageManager; 10 | import android.graphics.Bitmap; 11 | import android.graphics.BitmapFactory; 12 | import android.media.MediaScannerConnection; 13 | import android.media.projection.MediaProjectionManager; 14 | import android.net.Uri; 15 | import android.os.Build; 16 | import android.os.Bundle; 17 | import android.os.Environment; 18 | import android.preference.PreferenceManager; 19 | import android.provider.MediaStore; 20 | import android.util.Log; 21 | import android.view.Menu; 22 | import android.view.MenuItem; 23 | import android.view.View; 24 | import android.widget.Button; 25 | import android.widget.CheckBox; 26 | import android.widget.CompoundButton; 27 | import android.widget.EditText; 28 | import android.widget.RadioGroup; 29 | import androidx.appcompat.widget.SwitchCompat; 30 | import android.widget.Toast; 31 | 32 | import androidx.annotation.DrawableRes; 33 | import androidx.annotation.NonNull; 34 | import androidx.annotation.RequiresApi; 35 | import androidx.appcompat.app.AppCompatActivity; 36 | import androidx.core.app.ActivityCompat; 37 | import androidx.core.content.ContextCompat; 38 | 39 | import com.hbisoft.hbrecorder.HBRecorder; 40 | import com.hbisoft.hbrecorder.HBRecorderCodecInfo; 41 | import com.hbisoft.hbrecorder.HBRecorderListener; 42 | 43 | import java.io.ByteArrayOutputStream; 44 | import java.io.File; 45 | import java.sql.Date; 46 | import java.text.SimpleDateFormat; 47 | import java.util.ArrayList; 48 | import java.util.HashMap; 49 | import java.util.Locale; 50 | import java.util.Map; 51 | 52 | import static android.content.res.Configuration.ORIENTATION_PORTRAIT; 53 | import static com.hbisoft.hbrecorder.Constants.MAX_FILE_SIZE_REACHED_ERROR; 54 | import static com.hbisoft.hbrecorder.Constants.SETTINGS_ERROR; 55 | 56 | 57 | /** 58 | * Created by HBiSoft on 13 Aug 2019 59 | * Copyright (c) 2019 . All rights reserved. 60 | */ 61 | 62 | /* 63 | * Implementation Steps 64 | * 65 | * 1. Implement HBRecorderListener by calling implements HBRecorderListener 66 | * After this you have to implement the methods by pressing (Alt + Enter) 67 | * 68 | * 2. Declare HBRecorder 69 | * 70 | * 3. Init implements HBRecorderListener by calling hbRecorder = new HBRecorder(this, this); 71 | * 72 | * 4. Set adjust provided settings 73 | * 74 | * 5. Start recording by first calling: 75 | * MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE); 76 | Intent permissionIntent = mediaProjectionManager != null ? mediaProjectionManager.createScreenCaptureIntent() : null; 77 | startActivityForResult(permissionIntent, SCREEN_RECORD_REQUEST_CODE); 78 | 79 | * 6. Then in onActivityResult call hbRecorder.onActivityResult(resultCode, data, this); 80 | * 81 | * 7. Then you can start recording by calling hbRecorder.startScreenRecording(data); 82 | * 83 | * */ 84 | 85 | @SuppressWarnings({"SameParameterValue"}) 86 | public class MainActivity extends AppCompatActivity implements HBRecorderListener { 87 | //Permissions 88 | private static final int SCREEN_RECORD_REQUEST_CODE = 777; 89 | private static final int PERMISSION_REQ_ID_RECORD_AUDIO = 22; 90 | private static final int PERMISSION_REQ_POST_NOTIFICATIONS = 33; 91 | private static final int PERMISSION_REQ_ID_WRITE_EXTERNAL_STORAGE = PERMISSION_REQ_ID_RECORD_AUDIO + 1; 92 | private static final int PERMISSION_REQ_ID_FOREGROUND_SERVICE_MEDIA_PROJECTION = PERMISSION_REQ_ID_WRITE_EXTERNAL_STORAGE + 1; 93 | private boolean hasPermissions = false; 94 | private boolean hasAudioPermissions = false; 95 | 96 | //Declare HBRecorder 97 | private HBRecorder hbRecorder; 98 | 99 | //Start/Stop Button 100 | private Button startbtn; 101 | 102 | //HD/SD quality 103 | private RadioGroup radioGroup; 104 | 105 | //Should record/show audio/notification 106 | private CheckBox recordAudioCheckBox; 107 | 108 | //Reference to checkboxes and radio buttons 109 | boolean wasHDSelected = true; 110 | boolean isAudioEnabled = true; 111 | 112 | //Should custom settings be used 113 | SwitchCompat custom_settings_switch; 114 | 115 | // Max file size in K 116 | private EditText maxFileSizeInK; 117 | 118 | 119 | @Override 120 | protected void onCreate(Bundle savedInstanceState) { 121 | super.onCreate(savedInstanceState); 122 | setContentView(R.layout.activity_main); 123 | 124 | initViews(); 125 | setOnClickListeners(); 126 | setRadioGroupCheckListener(); 127 | setRecordAudioCheckBoxListener(); 128 | 129 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 130 | //Init HBRecorder 131 | hbRecorder = new HBRecorder(this, this); 132 | 133 | //When the user returns to the application, some UI changes might be necessary, 134 | //check if recording is in progress and make changes accordingly 135 | if (hbRecorder.isBusyRecording()) { 136 | startbtn.setText(R.string.stop_recording); 137 | } 138 | } 139 | 140 | // Examples of how to use the HBRecorderCodecInfo class to get codec info 141 | HBRecorderCodecInfo hbRecorderCodecInfo = new HBRecorderCodecInfo(); 142 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { 143 | int mWidth = hbRecorder.getDefaultWidth(); 144 | int mHeight = hbRecorder.getDefaultHeight(); 145 | String mMimeType = "video/avc"; 146 | int mFPS = 30; 147 | if (hbRecorderCodecInfo.isMimeTypeSupported(mMimeType)) { 148 | String defaultVideoEncoder = hbRecorderCodecInfo.getDefaultVideoEncoderName(mMimeType); 149 | boolean isSizeAndFramerateSupported = hbRecorderCodecInfo.isSizeAndFramerateSupported(mWidth, mHeight, mFPS, mMimeType, ORIENTATION_PORTRAIT); 150 | Log.e("EXAMPLE", "THIS IS AN EXAMPLE OF HOW TO USE THE (HBRecorderCodecInfo) TO GET CODEC INFO:"); 151 | Log.e("HBRecorderCodecInfo", "defaultVideoEncoder for (" + mMimeType + ") -> " + defaultVideoEncoder); 152 | Log.e("HBRecorderCodecInfo", "MaxSupportedFrameRate -> " + hbRecorderCodecInfo.getMaxSupportedFrameRate(mWidth, mHeight, mMimeType)); 153 | Log.e("HBRecorderCodecInfo", "MaxSupportedBitrate -> " + hbRecorderCodecInfo.getMaxSupportedBitrate(mMimeType)); 154 | Log.e("HBRecorderCodecInfo", "isSizeAndFramerateSupported @ Width = "+mWidth+" Height = "+mHeight+" FPS = "+mFPS+" -> " + isSizeAndFramerateSupported); 155 | Log.e("HBRecorderCodecInfo", "isSizeSupported @ Width = "+mWidth+" Height = "+mHeight+" -> " + hbRecorderCodecInfo.isSizeSupported(mWidth, mHeight, mMimeType)); 156 | Log.e("HBRecorderCodecInfo", "Default Video Format = " + hbRecorderCodecInfo.getDefaultVideoFormat()); 157 | 158 | HashMap supportedVideoMimeTypes = hbRecorderCodecInfo.getSupportedVideoMimeTypes(); 159 | for (Map.Entry entry : supportedVideoMimeTypes.entrySet()) { 160 | Log.e("HBRecorderCodecInfo", "Supported VIDEO encoders and mime types : " + entry.getKey() + " -> " + entry.getValue()); 161 | } 162 | 163 | HashMap supportedAudioMimeTypes = hbRecorderCodecInfo.getSupportedAudioMimeTypes(); 164 | for (Map.Entry entry : supportedAudioMimeTypes.entrySet()) { 165 | Log.e("HBRecorderCodecInfo", "Supported AUDIO encoders and mime types : " + entry.getKey() + " -> " + entry.getValue()); 166 | } 167 | 168 | ArrayList supportedVideoFormats = hbRecorderCodecInfo.getSupportedVideoFormats(); 169 | for (int j = 0; j < supportedVideoFormats.size(); j++) { 170 | Log.e("HBRecorderCodecInfo", "Available Video Formats : " + supportedVideoFormats.get(j)); 171 | } 172 | }else{ 173 | Log.e("HBRecorderCodecInfo", "MimeType not supported"); 174 | } 175 | 176 | } 177 | 178 | } 179 | 180 | //Create Folder 181 | //Only call this on Android 9 and lower (getExternalStoragePublicDirectory is deprecated) 182 | //This can still be used on Android 10> but you will have to add android:requestLegacyExternalStorage="true" in your Manifest 183 | private void createFolder() { 184 | File f1 = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES), "HBRecorder"); 185 | if (!f1.exists()) { 186 | if (f1.mkdirs()) { 187 | Log.i("Folder ", "created"); 188 | } 189 | } 190 | } 191 | 192 | //Init Views 193 | private void initViews() { 194 | startbtn = findViewById(R.id.button_start); 195 | radioGroup = findViewById(R.id.radio_group); 196 | recordAudioCheckBox = findViewById(R.id.audio_check_box); 197 | custom_settings_switch = findViewById(R.id.custom_settings_switch); 198 | } 199 | 200 | private void saveAudioPreference(boolean isEnabled) { 201 | SharedPreferences preferences = getSharedPreferences("AppPreferences", MODE_PRIVATE); 202 | SharedPreferences.Editor editor = preferences.edit(); 203 | editor.putBoolean("hasAudioPermissions", isEnabled); 204 | editor.apply(); 205 | } 206 | 207 | private boolean hasAudioPreference() { 208 | SharedPreferences preferences = getSharedPreferences("AppPreferences", MODE_PRIVATE); 209 | return preferences.getBoolean("hasAudioPermissions", false); // Default to false if not set 210 | } 211 | 212 | //Start Button OnClickListener 213 | private void setOnClickListeners() { 214 | startbtn.setOnClickListener(v -> { 215 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 216 | // first check if permissions were granted 217 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { // SDK 34 218 | if (isAudioEnabled) { 219 | if (checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS, PERMISSION_REQ_POST_NOTIFICATIONS) 220 | && checkSelfPermission(Manifest.permission.RECORD_AUDIO, PERMISSION_REQ_ID_RECORD_AUDIO) 221 | && checkSelfPermission(Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION, PERMISSION_REQ_ID_FOREGROUND_SERVICE_MEDIA_PROJECTION)) { 222 | hasPermissions = true; 223 | saveAudioPreference(true); 224 | } 225 | }else{ 226 | if (checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS, PERMISSION_REQ_POST_NOTIFICATIONS) 227 | && checkSelfPermission(Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION, PERMISSION_REQ_ID_FOREGROUND_SERVICE_MEDIA_PROJECTION)) { 228 | hasPermissions = true; 229 | saveAudioPreference(false); 230 | } 231 | } 232 | } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { // SDK 33 233 | if (isAudioEnabled) { 234 | if (checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS, PERMISSION_REQ_POST_NOTIFICATIONS) 235 | && checkSelfPermission(Manifest.permission.RECORD_AUDIO, PERMISSION_REQ_ID_RECORD_AUDIO)) { 236 | hasPermissions = true; 237 | saveAudioPreference(true); 238 | } 239 | }else{ 240 | if (checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS, PERMISSION_REQ_POST_NOTIFICATIONS)) { 241 | hasPermissions = true; 242 | } 243 | } 244 | 245 | } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { 246 | if (checkSelfPermission(Manifest.permission.RECORD_AUDIO, PERMISSION_REQ_ID_RECORD_AUDIO)) { 247 | hasPermissions = true; 248 | saveAudioPreference(true); 249 | 250 | } 251 | } else { 252 | if (checkSelfPermission(Manifest.permission.RECORD_AUDIO, PERMISSION_REQ_ID_RECORD_AUDIO) 253 | && checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, PERMISSION_REQ_ID_WRITE_EXTERNAL_STORAGE)) { 254 | hasPermissions = true; 255 | saveAudioPreference(true); 256 | } 257 | } 258 | 259 | if (hasPermissions) { 260 | // check if recording is in progress and stop it if it is 261 | if (hbRecorder.isBusyRecording()) { 262 | hbRecorder.stopScreenRecording(); 263 | startbtn.setText(R.string.start_recording); 264 | } else { 265 | // else start recording 266 | if (!hasAudioPermissions && isAudioEnabled) { 267 | if (checkSelfPermission(Manifest.permission.RECORD_AUDIO, PERMISSION_REQ_ID_RECORD_AUDIO)){ 268 | hasPermissions = true; 269 | saveAudioPreference(true); 270 | startRecordingScreen(); 271 | } 272 | 273 | }else { 274 | startRecordingScreen(); 275 | } 276 | } 277 | } 278 | } else { 279 | showLongToast("This library requires API 21>"); 280 | } 281 | }); 282 | } 283 | 284 | //Check if HD/SD Video should be recorded 285 | private void setRadioGroupCheckListener() { 286 | radioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { 287 | @Override 288 | public void onCheckedChanged(RadioGroup radioGroup, int checkedId) { 289 | 290 | if (checkedId == R.id.hd_button) { 291 | //Ser HBRecorder to HD 292 | wasHDSelected = true; 293 | } else if (checkedId == R.id.sd_button) { 294 | //Ser HBRecorder to SD 295 | wasHDSelected = false; 296 | } 297 | } 298 | }); 299 | } 300 | 301 | //Check if audio should be recorded 302 | private void setRecordAudioCheckBoxListener() { 303 | recordAudioCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 304 | @Override 305 | public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) { 306 | //Enable/Disable audio 307 | isAudioEnabled = isChecked; 308 | } 309 | }); 310 | } 311 | 312 | // Called when recording starts 313 | @Override 314 | public void HBRecorderOnStart() { 315 | Log.e("HBRecorder", "HBRecorderOnStart called"); 316 | } 317 | 318 | //Listener for when the recording is saved successfully 319 | //This will be called after the file was created 320 | @Override 321 | public void HBRecorderOnComplete() { 322 | startbtn.setText(R.string.start_recording); 323 | showLongToast("Saved Successfully"); 324 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 325 | //Update gallery depending on SDK Level 326 | if (hbRecorder.wasUriSet()) { 327 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q ) { 328 | updateGalleryUri(); 329 | } else { 330 | refreshGalleryFile(); 331 | } 332 | }else{ 333 | refreshGalleryFile(); 334 | } 335 | } 336 | 337 | } 338 | 339 | // Called when error occurs 340 | @Override 341 | public void HBRecorderOnError(int errorCode, String reason) { 342 | // Error 38 happens when 343 | // - the selected video encoder is not supported 344 | // - the output format is not supported 345 | // - if another app is using the microphone 346 | 347 | //It is best to use device default 348 | 349 | if (errorCode == SETTINGS_ERROR) { 350 | showLongToast(getString(R.string.settings_not_supported_message)); 351 | } else if ( errorCode == MAX_FILE_SIZE_REACHED_ERROR) { 352 | showLongToast(getString(R.string.max_file_size_reached_message)); 353 | } else { 354 | showLongToast(getString(R.string.general_recording_error_message)); 355 | Log.e("HBRecorderOnError", reason); 356 | } 357 | 358 | startbtn.setText(R.string.start_recording); 359 | 360 | } 361 | 362 | // Called when recording has been paused 363 | @Override 364 | public void HBRecorderOnPause() { 365 | // Called when recording was paused 366 | } 367 | 368 | // Calld when recording has resumed 369 | @Override 370 | public void HBRecorderOnResume() { 371 | // Called when recording was resumed 372 | } 373 | 374 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 375 | private void refreshGalleryFile() { 376 | MediaScannerConnection.scanFile(this, 377 | new String[]{hbRecorder.getFilePath()}, null, 378 | new MediaScannerConnection.OnScanCompletedListener() { 379 | public void onScanCompleted(String path, Uri uri) { 380 | Log.i("ExternalStorage", "Scanned " + path + ":"); 381 | Log.i("ExternalStorage", "-> uri=" + uri); 382 | } 383 | }); 384 | } 385 | 386 | @RequiresApi(api = Build.VERSION_CODES.Q) 387 | private void updateGalleryUri(){ 388 | contentValues.clear(); 389 | contentValues.put(MediaStore.Video.Media.IS_PENDING, 0); 390 | getContentResolver().update(mUri, contentValues, null, null); 391 | } 392 | 393 | //Start recording screen 394 | //It is important to call it like this 395 | //hbRecorder.startScreenRecording(data); should only be called in onActivityResult 396 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 397 | private void startRecordingScreen() { 398 | if (custom_settings_switch.isChecked()) { 399 | //WHEN SETTING CUSTOM SETTINGS YOU MUST SET THIS!!! 400 | hbRecorder.enableCustomSettings(); 401 | customSettings(); 402 | MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE); 403 | Intent permissionIntent = mediaProjectionManager != null ? mediaProjectionManager.createScreenCaptureIntent() : null; 404 | startActivityForResult(permissionIntent, SCREEN_RECORD_REQUEST_CODE); 405 | } else { 406 | quickSettings(); 407 | MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE); 408 | Intent permissionIntent = mediaProjectionManager != null ? mediaProjectionManager.createScreenCaptureIntent() : null; 409 | startActivityForResult(permissionIntent, SCREEN_RECORD_REQUEST_CODE); 410 | } 411 | startbtn.setText(R.string.stop_recording); 412 | } 413 | 414 | String output_format; 415 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 416 | // Example of how to set custom settings 417 | private void customSettings() { 418 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 419 | 420 | //Is audio enabled 421 | boolean audio_enabled = prefs.getBoolean("key_record_audio", true); 422 | hbRecorder.isAudioEnabled(audio_enabled); 423 | 424 | //Audio Source 425 | String audio_source = prefs.getString("key_audio_source", null); 426 | if (audio_source != null) { 427 | switch (audio_source) { 428 | case "0": 429 | hbRecorder.setAudioSource("DEFAULT"); 430 | break; 431 | case "1": 432 | hbRecorder.setAudioSource("CAMCODER"); 433 | break; 434 | case "2": 435 | hbRecorder.setAudioSource("MIC"); 436 | break; 437 | } 438 | } 439 | 440 | //Video Encoder 441 | String video_encoder = prefs.getString("key_video_encoder", null); 442 | if (video_encoder != null) { 443 | switch (video_encoder) { 444 | case "0": 445 | hbRecorder.setVideoEncoder("DEFAULT"); 446 | break; 447 | case "1": 448 | hbRecorder.setVideoEncoder("H264"); 449 | break; 450 | case "2": 451 | hbRecorder.setVideoEncoder("H263"); 452 | break; 453 | case "3": 454 | hbRecorder.setVideoEncoder("HEVC"); 455 | break; 456 | case "4": 457 | hbRecorder.setVideoEncoder("MPEG_4_SP"); 458 | break; 459 | case "5": 460 | hbRecorder.setVideoEncoder("VP8"); 461 | break; 462 | } 463 | } 464 | 465 | //NOTE - THIS MIGHT NOT BE SUPPORTED SIZES FOR YOUR DEVICE 466 | //Video Dimensions 467 | String video_resolution = prefs.getString("key_video_resolution", null); 468 | if (video_resolution != null) { 469 | switch (video_resolution) { 470 | case "0": 471 | hbRecorder.setScreenDimensions(426, 240); 472 | break; 473 | case "1": 474 | hbRecorder.setScreenDimensions(640, 360); 475 | break; 476 | case "2": 477 | hbRecorder.setScreenDimensions(854, 480); 478 | break; 479 | case "3": 480 | hbRecorder.setScreenDimensions(1280, 720); 481 | break; 482 | case "4": 483 | hbRecorder.setScreenDimensions(1920, 1080); 484 | break; 485 | } 486 | } 487 | 488 | //Video Frame Rate 489 | String video_frame_rate = prefs.getString("key_video_fps", null); 490 | if (video_frame_rate != null) { 491 | switch (video_frame_rate) { 492 | case "0": 493 | hbRecorder.setVideoFrameRate(60); 494 | break; 495 | case "1": 496 | hbRecorder.setVideoFrameRate(50); 497 | break; 498 | case "2": 499 | hbRecorder.setVideoFrameRate(48); 500 | break; 501 | case "3": 502 | hbRecorder.setVideoFrameRate(30); 503 | break; 504 | case "4": 505 | hbRecorder.setVideoFrameRate(25); 506 | break; 507 | case "5": 508 | hbRecorder.setVideoFrameRate(24); 509 | break; 510 | } 511 | } 512 | 513 | //Video Bitrate 514 | String video_bit_rate = prefs.getString("key_video_bitrate", null); 515 | if (video_bit_rate != null) { 516 | switch (video_bit_rate) { 517 | case "1": 518 | hbRecorder.setVideoBitrate(12000000); 519 | break; 520 | case "2": 521 | hbRecorder.setVideoBitrate(8000000); 522 | break; 523 | case "3": 524 | hbRecorder.setVideoBitrate(7500000); 525 | break; 526 | case "4": 527 | hbRecorder.setVideoBitrate(5000000); 528 | break; 529 | case "5": 530 | hbRecorder.setVideoBitrate(4000000); 531 | break; 532 | case "6": 533 | hbRecorder.setVideoBitrate(2500000); 534 | break; 535 | case "7": 536 | hbRecorder.setVideoBitrate(1500000); 537 | break; 538 | case "8": 539 | hbRecorder.setVideoBitrate(1000000); 540 | break; 541 | } 542 | } 543 | 544 | //Output Format 545 | output_format = prefs.getString("key_output_format", null); 546 | if (output_format != null) { 547 | switch (output_format) { 548 | case "0": 549 | hbRecorder.setOutputFormat("DEFAULT"); 550 | break; 551 | case "1": 552 | hbRecorder.setOutputFormat("MPEG_4"); 553 | break; 554 | case "2": 555 | hbRecorder.setOutputFormat("THREE_GPP"); 556 | break; 557 | case "3": 558 | hbRecorder.setOutputFormat("WEBM"); 559 | break; 560 | } 561 | } 562 | 563 | } 564 | 565 | //Get/Set the selected settings 566 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 567 | private void quickSettings() { 568 | hbRecorder.setAudioBitrate(128000); 569 | hbRecorder.setAudioSamplingRate(44100); 570 | hbRecorder.recordHDVideo(wasHDSelected); 571 | hbRecorder.isAudioEnabled(isAudioEnabled); 572 | //Customise Notification 573 | hbRecorder.setNotificationSmallIcon(R.drawable.icon); 574 | //hbRecorder.setNotificationSmallIconVector(R.drawable.ic_baseline_videocam_24); 575 | hbRecorder.setNotificationTitle(getString(R.string.stop_recording_notification_title)); 576 | hbRecorder.setNotificationDescription(getString(R.string.stop_recording_notification_message)); 577 | } 578 | 579 | // Example of how to set the max file size 580 | 581 | /*@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 582 | private void setRecorderMaxFileSize() { 583 | String s = maxFileSizeInK.getText().toString(); 584 | long maxFileSizeInKilobytes; 585 | try { 586 | maxFileSizeInKilobytes = Long.parseLong(s); 587 | } catch (NumberFormatException e) { 588 | maxFileSizeInKilobytes = 0; 589 | } 590 | hbRecorder.setMaxFileSize(maxFileSizeInKilobytes * 1024); // Convert to bytes 591 | 592 | }*/ 593 | 594 | @Override 595 | public boolean onCreateOptionsMenu(Menu menu) { 596 | getMenuInflater().inflate(R.menu.menu_main, menu); 597 | return true; 598 | } 599 | 600 | @Override 601 | public boolean onOptionsItemSelected(MenuItem item) { 602 | int id = item.getItemId(); 603 | 604 | if (id == R.id.action_settings) { 605 | // launch settings activity 606 | startActivity(new Intent(MainActivity.this, SettingsActivity.class)); 607 | return true; 608 | } 609 | 610 | return super.onOptionsItemSelected(item); 611 | } 612 | 613 | //Check if permissions was granted 614 | private boolean checkSelfPermission(String permission, int requestCode) { 615 | if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { 616 | ActivityCompat.requestPermissions(this, new String[]{permission}, requestCode); 617 | return false; 618 | } 619 | return true; 620 | } 621 | 622 | //Handle permissions 623 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 624 | @Override 625 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 626 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 627 | switch (requestCode) { 628 | case PERMISSION_REQ_POST_NOTIFICATIONS: 629 | if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { 630 | if (isAudioEnabled) { 631 | checkSelfPermission(Manifest.permission.RECORD_AUDIO, PERMISSION_REQ_ID_RECORD_AUDIO); 632 | }else { 633 | checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, PERMISSION_REQ_ID_WRITE_EXTERNAL_STORAGE); 634 | } 635 | } else { 636 | hasPermissions = false; 637 | showLongToast("No permission for " + Manifest.permission.POST_NOTIFICATIONS); 638 | } 639 | break; 640 | case PERMISSION_REQ_ID_RECORD_AUDIO: 641 | if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { 642 | checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, PERMISSION_REQ_ID_WRITE_EXTERNAL_STORAGE); 643 | } else { 644 | hasPermissions = false; 645 | showLongToast("No permission for " + Manifest.permission.RECORD_AUDIO); 646 | } 647 | break; 648 | case PERMISSION_REQ_ID_WRITE_EXTERNAL_STORAGE: 649 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { 650 | hasPermissions = true; 651 | startRecordingScreen(); 652 | } else { 653 | if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { 654 | checkSelfPermission(Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION, PERMISSION_REQ_ID_FOREGROUND_SERVICE_MEDIA_PROJECTION); 655 | } else { 656 | hasPermissions = false; 657 | showLongToast("No permission for " + Manifest.permission.WRITE_EXTERNAL_STORAGE); 658 | } 659 | } 660 | break; 661 | case PERMISSION_REQ_ID_FOREGROUND_SERVICE_MEDIA_PROJECTION: 662 | if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { 663 | hasPermissions = true; 664 | startRecordingScreen(); 665 | } else { 666 | hasPermissions = false; 667 | showLongToast("No permission for " + Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION); 668 | } 669 | break; 670 | default: 671 | break; 672 | } 673 | } 674 | 675 | 676 | @Override 677 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 678 | super.onActivityResult(requestCode, resultCode, data); 679 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 680 | if (requestCode == SCREEN_RECORD_REQUEST_CODE) { 681 | if (resultCode == RESULT_OK) { 682 | //Set file path or Uri depending on SDK version 683 | setOutputPath(); 684 | //Start screen recording 685 | hbRecorder.startScreenRecording(data, resultCode); 686 | 687 | }else{ 688 | startbtn.setText(R.string.start_recording); 689 | } 690 | } 691 | } 692 | } 693 | 694 | //For Android 10> we will pass a Uri to HBRecorder 695 | //This is not necessary - You can still use getExternalStoragePublicDirectory 696 | //But then you will have to add android:requestLegacyExternalStorage="true" in your Manifest 697 | //IT IS IMPORTANT TO SET THE FILE NAME THE SAME AS THE NAME YOU USE FOR TITLE AND DISPLAY_NAME 698 | ContentResolver resolver; 699 | ContentValues contentValues; 700 | Uri mUri; 701 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 702 | private void setOutputPath() { 703 | String filename = generateFileName(); 704 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { 705 | resolver = getContentResolver(); 706 | contentValues = new ContentValues(); 707 | contentValues.put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/" + "HBRecorder"); 708 | contentValues.put(MediaStore.Video.Media.TITLE, filename); 709 | contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, filename); 710 | if (output_format != null) { 711 | contentValues.put(MediaStore.MediaColumns.MIME_TYPE, getMimeTypeForOutputFormat(output_format)); 712 | }else { 713 | contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4"); 714 | } 715 | mUri = resolver.insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, contentValues); 716 | //FILE NAME SHOULD BE THE SAME 717 | hbRecorder.setFileName(filename); 718 | hbRecorder.setOutputUri(mUri); 719 | }else{ 720 | createFolder(); 721 | hbRecorder.setOutputPath(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES) +"/HBRecorder"); 722 | } 723 | } 724 | 725 | // Passing the MIME_TYPE to ContentValues() depending on what output format was selected 726 | // This is just to demonstrate for the demo app - more can be added 727 | private String getMimeTypeForOutputFormat(String outputFormat) { 728 | String mimetype = "video/mp4"; 729 | switch (outputFormat) { 730 | // We do not know what the devices DEFAULT (0) is 731 | // For the sake of this demo app we will set it to mp4 732 | case "0": 733 | mimetype = "video/mp4"; 734 | break; 735 | case "1": 736 | mimetype = "video/mp4"; 737 | break; 738 | case "2": 739 | mimetype = "video/3gpp"; 740 | break; 741 | case "3": 742 | mimetype = "video/webm"; 743 | break; 744 | default: 745 | mimetype = "video/mp4"; 746 | break; 747 | } 748 | return mimetype; 749 | } 750 | 751 | //Generate a timestamp to be used as a file name 752 | private String generateFileName() { 753 | SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault()); 754 | Date curDate = new Date(System.currentTimeMillis()); 755 | return formatter.format(curDate).replace(" ", ""); 756 | } 757 | 758 | //Show Toast 759 | private void showLongToast(final String msg) { 760 | Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_LONG).show(); 761 | } 762 | 763 | //drawable to byte[] 764 | private byte[] drawable2ByteArray(@DrawableRes int drawableId) { 765 | Bitmap icon = BitmapFactory.decodeResource(getResources(), drawableId); 766 | ByteArrayOutputStream stream = new ByteArrayOutputStream(); 767 | icon.compress(Bitmap.CompressFormat.PNG, 100, stream); 768 | return stream.toByteArray(); 769 | } 770 | } 771 | -------------------------------------------------------------------------------- /app/src/main/java/com/hbisoft/hbrecorderexample/SettingsActivity.java: -------------------------------------------------------------------------------- 1 | package com.hbisoft.hbrecorderexample; 2 | 3 | import android.content.SharedPreferences; 4 | import android.os.Bundle; 5 | import android.view.MenuItem; 6 | 7 | import androidx.appcompat.app.AppCompatActivity; 8 | import androidx.preference.ListPreference; 9 | import androidx.preference.Preference; 10 | import androidx.preference.PreferenceFragmentCompat; 11 | import androidx.preference.PreferenceManager; 12 | import androidx.preference.SwitchPreference; 13 | 14 | public class SettingsActivity extends AppCompatActivity { 15 | 16 | @Override 17 | protected void onCreate(Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | if (getSupportActionBar()!=null) { 20 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 21 | } 22 | 23 | // load settings fragment 24 | getSupportFragmentManager().beginTransaction().replace(android.R.id.content, new MainPreferenceFragment()).commit(); 25 | } 26 | 27 | public static class MainPreferenceFragment extends PreferenceFragmentCompat implements Preference.OnPreferenceChangeListener{ 28 | ListPreference key_video_resolution, key_audio_source, key_video_encoder, key_video_fps, key_video_bitrate, key_output_format; 29 | SwitchPreference key_record_audio; 30 | 31 | @Override 32 | public void onCreate(final Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | addPreferencesFromResource(R.xml.pref_main); 35 | 36 | key_record_audio = findPreference(getString(R.string.key_record_audio)); 37 | 38 | key_audio_source = findPreference(getString(R.string.key_audio_source)); 39 | if (key_audio_source != null) { 40 | key_audio_source.setOnPreferenceChangeListener(this); 41 | } 42 | 43 | key_video_encoder = findPreference(getString(R.string.key_video_encoder)); 44 | if (key_video_encoder != null) { 45 | key_video_encoder.setOnPreferenceChangeListener(this); 46 | } 47 | 48 | key_video_resolution = findPreference(getString(R.string.key_video_resolution)); 49 | if (key_video_resolution != null) { 50 | key_video_resolution.setOnPreferenceChangeListener(this); 51 | } 52 | 53 | key_video_fps = findPreference(getString(R.string.key_video_fps)); 54 | if (key_video_fps != null) { 55 | key_video_fps.setOnPreferenceChangeListener(this); 56 | } 57 | 58 | key_video_bitrate = findPreference(getString(R.string.key_video_bitrate)); 59 | if (key_video_bitrate != null) { 60 | key_video_bitrate.setOnPreferenceChangeListener(this); 61 | } 62 | 63 | key_output_format = findPreference(getString(R.string.key_output_format)); 64 | if (key_output_format != null) { 65 | key_output_format.setOnPreferenceChangeListener(this); 66 | } 67 | 68 | setPreviousSelectedAsSummary(); 69 | 70 | } 71 | 72 | @Override 73 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 74 | 75 | } 76 | 77 | @Override 78 | public boolean onPreferenceChange(Preference preference, Object newValue) { 79 | String preferenceKey = preference.getKey(); 80 | ListPreference listPreference; 81 | switch (preferenceKey) { 82 | case "key_audio_source": 83 | listPreference = findPreference(getString(R.string.key_audio_source)); 84 | if (listPreference != null) { 85 | listPreference.setSummary(listPreference.getEntries()[listPreference.findIndexOfValue(newValue.toString())]); 86 | } 87 | break; 88 | case "key_video_encoder": 89 | listPreference = findPreference(getString(R.string.key_video_encoder)); 90 | if (listPreference != null) { 91 | listPreference.setSummary(listPreference.getEntries()[listPreference.findIndexOfValue(newValue.toString())]); 92 | listPreference.setValue(newValue.toString()); 93 | } 94 | break; 95 | case "key_video_resolution": 96 | listPreference = findPreference(getString(R.string.key_video_resolution)); 97 | if (listPreference != null) { 98 | listPreference.setSummary(listPreference.getEntries()[listPreference.findIndexOfValue(newValue.toString())]); 99 | listPreference.setValue(newValue.toString()); 100 | } 101 | break; 102 | case "key_video_fps": 103 | listPreference = findPreference(getString(R.string.key_video_fps)); 104 | if (listPreference != null) { 105 | listPreference.setSummary(listPreference.getEntries()[listPreference.findIndexOfValue(newValue.toString())]); 106 | listPreference.setValue(newValue.toString()); 107 | } 108 | 109 | break; 110 | case "key_video_bitrate": 111 | listPreference = findPreference(getString(R.string.key_video_bitrate)); 112 | if (listPreference != null) { 113 | listPreference.setSummary(listPreference.getEntries()[listPreference.findIndexOfValue(newValue.toString())]); 114 | listPreference.setValue(newValue.toString()); 115 | } 116 | break; 117 | case "key_output_format": 118 | listPreference = findPreference(getString(R.string.key_output_format)); 119 | if (listPreference != null) { 120 | listPreference.setSummary(listPreference.getEntries()[listPreference.findIndexOfValue(newValue.toString())]); 121 | listPreference.setValue(newValue.toString()); 122 | } 123 | break; 124 | } 125 | 126 | return true; 127 | } 128 | 129 | private void setPreviousSelectedAsSummary() { 130 | if (getActivity() != null) { 131 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); 132 | String video_resolution = prefs.getString("key_video_resolution", null); 133 | boolean audio_enabled = prefs.getBoolean("key_record_audio", true); 134 | String audio_source = prefs.getString("key_audio_source", null); 135 | String video_encoder = prefs.getString("key_video_encoder", null); 136 | String video_frame_rate = prefs.getString("key_video_fps", null); 137 | String video_bit_rate = prefs.getString("key_video_bitrate", null); 138 | String output_format = prefs.getString("key_output_format", null); 139 | 140 | /*Record Audio Prefs*/ 141 | key_record_audio.setChecked(audio_enabled); 142 | 143 | /*Audio Source Prefs*/ 144 | if (audio_source != null) { 145 | int index = key_audio_source.findIndexOfValue(audio_source); 146 | key_audio_source.setSummary(key_audio_source.getEntries()[index]); 147 | 148 | } else { 149 | String defaultSummary = PreferenceManager.getDefaultSharedPreferences(key_audio_source.getContext()).getString(key_audio_source.getKey(), ""); 150 | key_audio_source.setSummary(defaultSummary); 151 | } 152 | 153 | /*Video Encoder Prefs*/ 154 | if (video_encoder != null) { 155 | int index = key_video_encoder.findIndexOfValue(video_encoder); 156 | key_video_encoder.setSummary(key_video_encoder.getEntries()[index]); 157 | 158 | } else { 159 | String defaultSummary = PreferenceManager.getDefaultSharedPreferences(key_video_encoder.getContext()).getString(key_video_encoder.getKey(), ""); 160 | key_video_encoder.setSummary(defaultSummary); 161 | } 162 | 163 | /*Video Resolution Prefs*/ 164 | if (video_resolution != null) { 165 | int index = key_video_resolution.findIndexOfValue(video_resolution); 166 | key_video_resolution.setSummary(key_video_resolution.getEntries()[index]); 167 | 168 | } else { 169 | String defaultSummary = PreferenceManager.getDefaultSharedPreferences(key_video_resolution.getContext()).getString(key_video_resolution.getKey(), ""); 170 | key_video_resolution.setSummary(defaultSummary); 171 | } 172 | 173 | /*Video Frame Rate Prefs*/ 174 | if (video_frame_rate != null) { 175 | int index = key_video_fps.findIndexOfValue(video_frame_rate); 176 | key_video_fps.setSummary(key_video_fps.getEntries()[index]); 177 | 178 | } else { 179 | String defaultSummary = PreferenceManager.getDefaultSharedPreferences(key_video_fps.getContext()).getString(key_video_fps.getKey(), ""); 180 | key_video_fps.setSummary(defaultSummary); 181 | } 182 | 183 | /*Video Bit Rate Prefs*/ 184 | if (video_bit_rate != null) { 185 | int index = key_video_bitrate.findIndexOfValue(video_bit_rate); 186 | key_video_bitrate.setSummary(key_video_bitrate.getEntries()[index]); 187 | 188 | } else { 189 | String defaultSummary = PreferenceManager.getDefaultSharedPreferences(key_video_bitrate.getContext()).getString(key_video_bitrate.getKey(), ""); 190 | key_video_bitrate.setSummary(defaultSummary); 191 | } 192 | 193 | /*Output Format Prefs*/ 194 | if (output_format != null) { 195 | int index = key_output_format.findIndexOfValue(output_format); 196 | key_output_format.setSummary(key_output_format.getEntries()[index]); 197 | 198 | } else { 199 | String defaultSummary = PreferenceManager.getDefaultSharedPreferences(key_output_format.getContext()).getString(key_output_format.getKey(), ""); 200 | key_output_format.setSummary(defaultSummary); 201 | } 202 | 203 | } 204 | 205 | } 206 | } 207 | 208 | @Override 209 | public boolean onOptionsItemSelected(MenuItem item) { 210 | if (item.getItemId() == android.R.id.home) { 211 | onBackPressed(); 212 | } 213 | return super.onOptionsItemSelected(item); 214 | } 215 | 216 | } 217 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HBiSoft/HBRecorder/b8c941de636b77376b818bebc61fd87ac4c03975/app/src/main/res/drawable/icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ripple_effect.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 22 | 23 | 31 | 32 | 39 | 40 | 46 | 47 | 48 | 49 | 50 | 60 | 61 | 71 | 72 | 82 | 83 | 93 | 94 | 100 | 101 | 109 | 110 | 116 | 117 | 118 | 119 | 120 | 125 | 126 |