io.appium.settings 4.2.0 → 4.2.1
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/apks/settings_apk-debug.apk +0 -0
- package/package.json +2 -8
- package/app/build.gradle +0 -38
- package/app/src/main/AndroidManifest.xml +0 -149
- package/app/src/main/java/io/appium/settings/AppiumIME.java +0 -153
- package/app/src/main/java/io/appium/settings/ForegroundService.java +0 -70
- package/app/src/main/java/io/appium/settings/LocationService.java +0 -215
- package/app/src/main/java/io/appium/settings/LocationTracker.java +0 -313
- package/app/src/main/java/io/appium/settings/NLService.java +0 -134
- package/app/src/main/java/io/appium/settings/Settings.java +0 -260
- package/app/src/main/java/io/appium/settings/UnicodeIME.java +0 -187
- package/app/src/main/java/io/appium/settings/Unlock.java +0 -75
- package/app/src/main/java/io/appium/settings/handlers/AbstractSettingHandler.java +0 -64
- package/app/src/main/java/io/appium/settings/handlers/AnimationSettingHandler.java +0 -66
- package/app/src/main/java/io/appium/settings/handlers/BluetoothConnectionSettingHandler.java +0 -42
- package/app/src/main/java/io/appium/settings/handlers/DataConnectionSettingHandler.java +0 -134
- package/app/src/main/java/io/appium/settings/handlers/LocaleSettingHandler.java +0 -86
- package/app/src/main/java/io/appium/settings/handlers/WiFiConnectionSettingHandler.java +0 -40
- package/app/src/main/java/io/appium/settings/helpers/NotificationHelpers.java +0 -67
- package/app/src/main/java/io/appium/settings/helpers/PlayServicesHelpers.java +0 -30
- package/app/src/main/java/io/appium/settings/helpers/Utils.java +0 -29
- package/app/src/main/java/io/appium/settings/location/FusedLocationProvider.java +0 -99
- package/app/src/main/java/io/appium/settings/location/LocationBuilder.java +0 -80
- package/app/src/main/java/io/appium/settings/location/LocationManagerProvider.java +0 -97
- package/app/src/main/java/io/appium/settings/location/MockLocationProvider.java +0 -31
- package/app/src/main/java/io/appium/settings/notifications/StoredNotification.java +0 -103
- package/app/src/main/java/io/appium/settings/notifications/StoredNotifications.java +0 -50
- package/app/src/main/java/io/appium/settings/receivers/AbstractSettingReceiver.java +0 -62
- package/app/src/main/java/io/appium/settings/receivers/AnimationSettingReceiver.java +0 -40
- package/app/src/main/java/io/appium/settings/receivers/BluetoothConnectionSettingReceiver.java +0 -49
- package/app/src/main/java/io/appium/settings/receivers/ClipboardReceiver.java +0 -91
- package/app/src/main/java/io/appium/settings/receivers/DataConnectionSettingReceiver.java +0 -40
- package/app/src/main/java/io/appium/settings/receivers/HasAction.java +0 -21
- package/app/src/main/java/io/appium/settings/receivers/LocaleSettingReceiver.java +0 -127
- package/app/src/main/java/io/appium/settings/receivers/LocationInfoReceiver.java +0 -67
- package/app/src/main/java/io/appium/settings/receivers/MediaScannerReceiver.java +0 -91
- package/app/src/main/java/io/appium/settings/receivers/NotificationsReceiver.java +0 -100
- package/app/src/main/java/io/appium/settings/receivers/SmsReader.java +0 -113
- package/app/src/main/java/io/appium/settings/receivers/UnpairBluetoothDevicesReceiver.java +0 -84
- package/app/src/main/java/io/appium/settings/receivers/WiFiConnectionSettingReceiver.java +0 -39
- package/app/src/main/java/io/appium/settings/recorder/RecorderConstant.java +0 -94
- package/app/src/main/java/io/appium/settings/recorder/RecorderService.java +0 -202
- package/app/src/main/java/io/appium/settings/recorder/RecorderThread.java +0 -459
- package/app/src/main/java/io/appium/settings/recorder/RecorderUtil.java +0 -298
- package/app/src/main/res/drawable-hdpi/ic_launcher.png +0 -0
- package/app/src/main/res/drawable-ldpi/ic_launcher.png +0 -0
- package/app/src/main/res/drawable-mdpi/ic_launcher.png +0 -0
- package/app/src/main/res/drawable-xhdpi/ic_launcher.png +0 -0
- package/app/src/main/res/drawable-xxhdpi/ic_launcher.png +0 -0
- package/app/src/main/res/drawable-xxxhdpi/ic_launcher.png +0 -0
- package/app/src/main/res/layout/main.xml +0 -29
- package/app/src/main/res/values/dimens.xml +0 -7
- package/app/src/main/res/values/strings.xml +0 -9
- package/app/src/main/res/xml/method.xml +0 -3
- package/build.gradle +0 -17
- package/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/gradle/wrapper/gradle-wrapper.properties +0 -6
- package/gradle.properties +0 -15
- package/gradlew +0 -160
- package/gradlew.bat +0 -90
- package/settings.gradle +0 -1
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Copyright 2012-present Appium Committers
|
|
3
|
-
<p>
|
|
4
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
you may not use this file except in compliance with the License.
|
|
6
|
-
You may obtain a copy of the License at
|
|
7
|
-
<p>
|
|
8
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
<p>
|
|
10
|
-
Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
See the License for the specific language governing permissions and
|
|
14
|
-
limitations under the License.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
package io.appium.settings.recorder;
|
|
18
|
-
|
|
19
|
-
import android.app.Service;
|
|
20
|
-
import android.content.Context;
|
|
21
|
-
import android.content.Intent;
|
|
22
|
-
import android.media.projection.MediaProjection;
|
|
23
|
-
import android.media.projection.MediaProjectionManager;
|
|
24
|
-
import android.os.Build;
|
|
25
|
-
import android.os.IBinder;
|
|
26
|
-
import android.util.DisplayMetrics;
|
|
27
|
-
import android.util.Log;
|
|
28
|
-
import android.util.Size;
|
|
29
|
-
|
|
30
|
-
import androidx.annotation.Nullable;
|
|
31
|
-
import androidx.annotation.RequiresApi;
|
|
32
|
-
import io.appium.settings.helpers.NotificationHelpers;
|
|
33
|
-
|
|
34
|
-
import static io.appium.settings.recorder.RecorderConstant.ACTION_RECORDING_FILENAME;
|
|
35
|
-
import static io.appium.settings.recorder.RecorderConstant.ACTION_RECORDING_MAX_DURATION;
|
|
36
|
-
import static io.appium.settings.recorder.RecorderConstant.ACTION_RECORDING_PRIORITY;
|
|
37
|
-
import static io.appium.settings.recorder.RecorderConstant.ACTION_RECORDING_RESOLUTION;
|
|
38
|
-
import static io.appium.settings.recorder.RecorderConstant.ACTION_RECORDING_RESULT_CODE;
|
|
39
|
-
import static io.appium.settings.recorder.RecorderConstant.ACTION_RECORDING_ROTATION;
|
|
40
|
-
import static io.appium.settings.recorder.RecorderConstant.ACTION_RECORDING_START;
|
|
41
|
-
import static io.appium.settings.recorder.RecorderConstant.ACTION_RECORDING_STOP;
|
|
42
|
-
import static io.appium.settings.recorder.RecorderConstant.RECORDING_MAX_DURATION_DEFAULT_MS;
|
|
43
|
-
import static io.appium.settings.recorder.RecorderConstant.RECORDING_PRIORITY_DEFAULT;
|
|
44
|
-
import static io.appium.settings.recorder.RecorderConstant.RECORDING_ROTATION_DEFAULT_DEGREE;
|
|
45
|
-
|
|
46
|
-
public class RecorderService extends Service {
|
|
47
|
-
private static final String TAG = "RecorderService";
|
|
48
|
-
|
|
49
|
-
private static RecorderThread recorderThread;
|
|
50
|
-
|
|
51
|
-
public RecorderService() {
|
|
52
|
-
super();
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
@Override
|
|
56
|
-
public void onDestroy() {
|
|
57
|
-
Log.v(TAG, "onDestroy called: Stopping recorder");
|
|
58
|
-
if (recorderThread != null && recorderThread.isRecordingRunning()) {
|
|
59
|
-
recorderThread.stopRecording();
|
|
60
|
-
}
|
|
61
|
-
super.onDestroy();
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
@Nullable
|
|
65
|
-
@Override
|
|
66
|
-
public IBinder onBind(final Intent intent) {
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
@RequiresApi(api = Build.VERSION_CODES.Q)
|
|
71
|
-
@Override
|
|
72
|
-
public int onStartCommand(final Intent intent, final int flags, final int startId) {
|
|
73
|
-
if (intent == null) {
|
|
74
|
-
Log.e(TAG, "onStartCommand: Unable to retrieve recording intent");
|
|
75
|
-
return START_NOT_STICKY;
|
|
76
|
-
}
|
|
77
|
-
final String action = intent.getAction();
|
|
78
|
-
if (action == null) {
|
|
79
|
-
Log.e(TAG, "onStartCommand: Unable to retrieve recording intent:action");
|
|
80
|
-
return START_NOT_STICKY;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
int result = START_STICKY;
|
|
84
|
-
if (ACTION_RECORDING_START.equals(action)) {
|
|
85
|
-
showNotification(); // TODO is this really necessary
|
|
86
|
-
|
|
87
|
-
MediaProjectionManager mMediaProjectionManager =
|
|
88
|
-
(MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
|
|
89
|
-
|
|
90
|
-
if (mMediaProjectionManager != null) {
|
|
91
|
-
startRecord(mMediaProjectionManager, intent);
|
|
92
|
-
} else {
|
|
93
|
-
Log.e(TAG, "onStartCommand: " +
|
|
94
|
-
"Unable to retrieve MediaProjectionManager instance");
|
|
95
|
-
result = START_NOT_STICKY;
|
|
96
|
-
}
|
|
97
|
-
} else if (ACTION_RECORDING_STOP.equals(action)) {
|
|
98
|
-
Log.v(TAG, "onStartCommand: Received recording stop intent, stopping recording");
|
|
99
|
-
stopRecord();
|
|
100
|
-
result = START_NOT_STICKY;
|
|
101
|
-
} else {
|
|
102
|
-
Log.v(TAG, "onStartCommand: Received unknown recording intent with action: "
|
|
103
|
-
+ action);
|
|
104
|
-
result = START_NOT_STICKY;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return result;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* start recording
|
|
112
|
-
*/
|
|
113
|
-
@RequiresApi(api = Build.VERSION_CODES.Q)
|
|
114
|
-
private void startRecord(MediaProjectionManager mediaProjectionManager,
|
|
115
|
-
final Intent intent) {
|
|
116
|
-
if (recorderThread != null) {
|
|
117
|
-
if (recorderThread.isRecordingRunning()) {
|
|
118
|
-
Log.v(TAG, "Recording is already continuing, exiting");
|
|
119
|
-
return;
|
|
120
|
-
} else {
|
|
121
|
-
Log.w(TAG, "Recording is stopped, " +
|
|
122
|
-
"but recording instance is still alive, starting recording");
|
|
123
|
-
recorderThread = null;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
int resultCode = intent.getIntExtra(ACTION_RECORDING_RESULT_CODE, 0);
|
|
128
|
-
// get MediaProjection
|
|
129
|
-
final MediaProjection projection = mediaProjectionManager.getMediaProjection(resultCode,
|
|
130
|
-
intent);
|
|
131
|
-
if (projection == null) {
|
|
132
|
-
Log.e(TAG, "Recording is stopped, Unable to retrieve MediaProjection instance");
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
String outputFilePath = intent.getStringExtra(ACTION_RECORDING_FILENAME);
|
|
137
|
-
if (outputFilePath == null) {
|
|
138
|
-
Log.e(TAG, "Recording is stopped, Unable to retrieve outputFilePath instance");
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/* TODO we need to rotate frames that comes from virtual screen before writing to file via muxer,
|
|
143
|
-
* for handling landscape mode properly, we need to find a way to rotate images somehow fast and reliable
|
|
144
|
-
*/
|
|
145
|
-
int recordingRotationDegree = intent.getIntExtra(ACTION_RECORDING_ROTATION,
|
|
146
|
-
RECORDING_ROTATION_DEFAULT_DEGREE);
|
|
147
|
-
|
|
148
|
-
DisplayMetrics metrics = getResources().getDisplayMetrics();
|
|
149
|
-
int rawWidth = metrics.widthPixels;
|
|
150
|
-
int rawHeight = metrics.heightPixels;
|
|
151
|
-
int rawDpi = metrics.densityDpi;
|
|
152
|
-
|
|
153
|
-
String recordingResolutionMode = intent.getStringExtra(ACTION_RECORDING_RESOLUTION);
|
|
154
|
-
|
|
155
|
-
Size recordingResolution = RecorderUtil.
|
|
156
|
-
getRecordingResolution(recordingResolutionMode);
|
|
157
|
-
|
|
158
|
-
int resolutionWidth = recordingResolution.getWidth();
|
|
159
|
-
int resolutionHeight = recordingResolution.getHeight();
|
|
160
|
-
|
|
161
|
-
/*
|
|
162
|
-
MediaCodec's tested supported resolutions (as per CTS tests) are for landscape mode as default (1920x1080, 1280x720 etc.)
|
|
163
|
-
but if phone or tablet is in portrait mode (usually it is),
|
|
164
|
-
we need to flip width/height to match it
|
|
165
|
-
*/
|
|
166
|
-
if (rawWidth < rawHeight) {
|
|
167
|
-
resolutionWidth = recordingResolution.getHeight();
|
|
168
|
-
resolutionHeight = recordingResolution.getWidth();
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
Log.v(TAG, String.format("Starting recording with resolution(widthxheight): (%dx%d)",
|
|
172
|
-
resolutionWidth, resolutionHeight));
|
|
173
|
-
|
|
174
|
-
int recordingPriority = intent.getIntExtra(ACTION_RECORDING_PRIORITY,
|
|
175
|
-
RECORDING_PRIORITY_DEFAULT);
|
|
176
|
-
|
|
177
|
-
int recordingMaxDuration = intent.getIntExtra(ACTION_RECORDING_MAX_DURATION,
|
|
178
|
-
RECORDING_MAX_DURATION_DEFAULT_MS);
|
|
179
|
-
|
|
180
|
-
recorderThread = new RecorderThread(projection, outputFilePath,
|
|
181
|
-
resolutionWidth, resolutionHeight, rawDpi,
|
|
182
|
-
recordingRotationDegree, recordingPriority, recordingMaxDuration);
|
|
183
|
-
recorderThread.startRecording();
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* stop recording
|
|
188
|
-
*/
|
|
189
|
-
private void stopRecord() {
|
|
190
|
-
if (recorderThread != null) {
|
|
191
|
-
recorderThread.stopRecording();
|
|
192
|
-
recorderThread = null;
|
|
193
|
-
}
|
|
194
|
-
stopSelf();
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
private void showNotification() {
|
|
198
|
-
// Set the info for the views that show in the notification panel.
|
|
199
|
-
startForeground(NotificationHelpers.APPIUM_NOTIFICATION_IDENTIFIER,
|
|
200
|
-
NotificationHelpers.getNotification(this));
|
|
201
|
-
}
|
|
202
|
-
}
|
|
@@ -1,459 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Copyright 2012-present Appium Committers
|
|
3
|
-
<p>
|
|
4
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
you may not use this file except in compliance with the License.
|
|
6
|
-
You may obtain a copy of the License at
|
|
7
|
-
<p>
|
|
8
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
<p>
|
|
10
|
-
Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
See the License for the specific language governing permissions and
|
|
14
|
-
limitations under the License.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
package io.appium.settings.recorder;
|
|
18
|
-
|
|
19
|
-
import android.hardware.display.DisplayManager;
|
|
20
|
-
import android.hardware.display.VirtualDisplay;
|
|
21
|
-
import android.media.AudioAttributes;
|
|
22
|
-
import android.media.AudioFormat;
|
|
23
|
-
import android.media.AudioPlaybackCaptureConfiguration;
|
|
24
|
-
import android.media.AudioRecord;
|
|
25
|
-
import android.media.MediaCodec;
|
|
26
|
-
import android.media.MediaCodecInfo;
|
|
27
|
-
import android.media.MediaFormat;
|
|
28
|
-
import android.media.MediaMuxer;
|
|
29
|
-
import android.media.projection.MediaProjection;
|
|
30
|
-
import android.os.Build;
|
|
31
|
-
import android.os.Handler;
|
|
32
|
-
import android.os.Looper;
|
|
33
|
-
import android.util.Log;
|
|
34
|
-
import android.view.Surface;
|
|
35
|
-
|
|
36
|
-
import java.io.IOException;
|
|
37
|
-
import java.nio.ByteBuffer;
|
|
38
|
-
|
|
39
|
-
import androidx.annotation.RequiresApi;
|
|
40
|
-
|
|
41
|
-
import static io.appium.settings.recorder.RecorderConstant.BPS_IN_MBPS;
|
|
42
|
-
import static io.appium.settings.recorder.RecorderConstant.NANOSECONDS_IN_MICROSECOND;
|
|
43
|
-
import static io.appium.settings.recorder.RecorderConstant.NO_TIMESTAMP_SET;
|
|
44
|
-
import static io.appium.settings.recorder.RecorderConstant.NO_TRACK_INDEX_SET;
|
|
45
|
-
import static io.appium.settings.recorder.RecorderConstant.RECORDING_DEFAULT_VIDEO_MIME_TYPE;
|
|
46
|
-
import static io.appium.settings.recorder.RecorderConstant.VIDEO_CODEC_DEFAULT_FRAME_RATE;
|
|
47
|
-
|
|
48
|
-
public class RecorderThread implements Runnable {
|
|
49
|
-
|
|
50
|
-
private static final String TAG = "RecorderThread";
|
|
51
|
-
|
|
52
|
-
private final MediaProjection mediaProjection;
|
|
53
|
-
private final String outputFilePath;
|
|
54
|
-
private final int videoWidth;
|
|
55
|
-
private final int videoHeight;
|
|
56
|
-
private final int videoDpi;
|
|
57
|
-
private final int recordingRotation;
|
|
58
|
-
private final int recordingPriority;
|
|
59
|
-
private final int recordingMaxDuration;
|
|
60
|
-
|
|
61
|
-
private boolean muxerStarted = false;
|
|
62
|
-
private boolean isStartTimestampInitialized = false;
|
|
63
|
-
private long startTimestampUs = System.nanoTime() / NANOSECONDS_IN_MICROSECOND;
|
|
64
|
-
private long lastAudioTimestampUs = NO_TIMESTAMP_SET;
|
|
65
|
-
|
|
66
|
-
private int videoTrackIndex = NO_TRACK_INDEX_SET;
|
|
67
|
-
private int audioTrackIndex = NO_TRACK_INDEX_SET;
|
|
68
|
-
|
|
69
|
-
private volatile boolean stopped = false;
|
|
70
|
-
private volatile boolean audioStopped = false;
|
|
71
|
-
private volatile boolean hasAsyncError = false;
|
|
72
|
-
|
|
73
|
-
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
|
74
|
-
private final VirtualDisplay.Callback displayCallback = new VirtualDisplay.Callback() {
|
|
75
|
-
@Override
|
|
76
|
-
public void onPaused() {
|
|
77
|
-
super.onPaused();
|
|
78
|
-
Log.v(TAG, "VirtualDisplay callback: Display streaming paused");
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
@Override
|
|
82
|
-
public void onStopped() {
|
|
83
|
-
super.onStopped();
|
|
84
|
-
if (!stopped) {
|
|
85
|
-
hasAsyncError = true;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
public RecorderThread(MediaProjection mediaProjection, String outputFilePath,
|
|
91
|
-
int videoWidth, int videoHeight, int videoDpi, int recordingRotation,
|
|
92
|
-
int recordingPriority, int recordingMaxDuration) {
|
|
93
|
-
this.mediaProjection = mediaProjection;
|
|
94
|
-
this.outputFilePath = outputFilePath;
|
|
95
|
-
this.videoWidth = videoWidth;
|
|
96
|
-
this.videoHeight = videoHeight;
|
|
97
|
-
this.videoDpi = videoDpi;
|
|
98
|
-
this.recordingRotation = recordingRotation;
|
|
99
|
-
this.recordingPriority = recordingPriority;
|
|
100
|
-
this.recordingMaxDuration = recordingMaxDuration;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
public void startRecording() {
|
|
104
|
-
stopped = false;
|
|
105
|
-
Thread recordingThread = new Thread(this);
|
|
106
|
-
recordingThread.start();
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
public void stopRecording() {
|
|
110
|
-
stopped = true;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
public boolean isRecordingRunning() {
|
|
114
|
-
return !stopped;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
|
118
|
-
private MediaFormat initVideoEncoderFormat(String videoMime, int videoWidth,
|
|
119
|
-
int videoHeight, int videoBitrate,
|
|
120
|
-
int videoFrameRate) {
|
|
121
|
-
MediaFormat encoderFormat = MediaFormat.createVideoFormat(videoMime, videoWidth,
|
|
122
|
-
videoHeight);
|
|
123
|
-
encoderFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,
|
|
124
|
-
MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
|
|
125
|
-
encoderFormat.setInteger(MediaFormat.KEY_BIT_RATE, videoBitrate);
|
|
126
|
-
encoderFormat.setInteger(MediaFormat.KEY_FRAME_RATE, videoFrameRate);
|
|
127
|
-
encoderFormat.setInteger(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER,
|
|
128
|
-
RecorderConstant.AUDIO_CODEC_REPEAT_PREV_FRAME_AFTER_MS);
|
|
129
|
-
encoderFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,
|
|
130
|
-
RecorderConstant.AUDIO_CODEC_I_FRAME_INTERVAL_MS);
|
|
131
|
-
return encoderFormat;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
|
135
|
-
private VirtualDisplay initVirtualDisplay(MediaProjection mediaProjection,
|
|
136
|
-
Surface surface, Handler handler,
|
|
137
|
-
int videoWidth, int videoHeight, int videoDpi) {
|
|
138
|
-
return mediaProjection.createVirtualDisplay("Appium Screen Recorder",
|
|
139
|
-
videoWidth, videoHeight, videoDpi,
|
|
140
|
-
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
|
|
141
|
-
surface, displayCallback, handler);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
|
145
|
-
private MediaCodec initAudioCodec(int sampleRate) throws IOException {
|
|
146
|
-
// TODO set channelCount 2 try stereo quality
|
|
147
|
-
MediaFormat encoderFormat = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC,
|
|
148
|
-
sampleRate, RecorderConstant.AUDIO_CODEC_CHANNEL_COUNT);
|
|
149
|
-
encoderFormat.setInteger(MediaFormat.KEY_BIT_RATE,
|
|
150
|
-
RecorderConstant.AUDIO_CODEC_DEFAULT_BITRATE);
|
|
151
|
-
|
|
152
|
-
MediaCodec audioEncoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
|
|
153
|
-
audioEncoder.configure(encoderFormat, null, null,
|
|
154
|
-
MediaCodec.CONFIGURE_FLAG_ENCODE);
|
|
155
|
-
return audioEncoder;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
@RequiresApi(api = Build.VERSION_CODES.Q)
|
|
159
|
-
private AudioRecord initAudioRecord(MediaProjection mediaProjection, int sampleRate) {
|
|
160
|
-
int channelConfig = AudioFormat.CHANNEL_IN_MONO;
|
|
161
|
-
int minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig,
|
|
162
|
-
AudioFormat.ENCODING_PCM_16BIT);
|
|
163
|
-
|
|
164
|
-
AudioFormat audioFormat = new AudioFormat.Builder()
|
|
165
|
-
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
|
|
166
|
-
.setSampleRate(sampleRate)
|
|
167
|
-
.setChannelMask(AudioFormat.CHANNEL_IN_MONO)
|
|
168
|
-
.build();
|
|
169
|
-
|
|
170
|
-
AudioRecord.Builder audioRecordBuilder = new AudioRecord.Builder();
|
|
171
|
-
AudioPlaybackCaptureConfiguration apcc =
|
|
172
|
-
new AudioPlaybackCaptureConfiguration.Builder(mediaProjection)
|
|
173
|
-
.addMatchingUsage(AudioAttributes.USAGE_MEDIA)
|
|
174
|
-
.build();
|
|
175
|
-
return audioRecordBuilder.setAudioFormat(audioFormat)
|
|
176
|
-
.setBufferSizeInBytes(4 * minBufferSize)
|
|
177
|
-
.setAudioPlaybackCaptureConfig(apcc)
|
|
178
|
-
.build();
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
|
182
|
-
private Thread initAudioRecordThread(MediaCodec audioEncoder, final AudioRecord audioRecord,
|
|
183
|
-
int priority) {
|
|
184
|
-
return new Thread(new Runnable() {
|
|
185
|
-
@Override
|
|
186
|
-
public void run() {
|
|
187
|
-
Thread.currentThread().setPriority(priority);
|
|
188
|
-
try {
|
|
189
|
-
audioRecord.startRecording();
|
|
190
|
-
} catch (Exception e) {
|
|
191
|
-
hasAsyncError = true;
|
|
192
|
-
e.printStackTrace();
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
try {
|
|
196
|
-
while (!audioStopped) {
|
|
197
|
-
int index = audioEncoder.dequeueInputBuffer(
|
|
198
|
-
RecorderConstant.MEDIA_QUEUE_BUFFERING_DEFAULT_TIMEOUT_MS);
|
|
199
|
-
if (index < 0) {
|
|
200
|
-
continue;
|
|
201
|
-
}
|
|
202
|
-
ByteBuffer inputBuffer = audioEncoder.getInputBuffer(index);
|
|
203
|
-
if (inputBuffer == null) {
|
|
204
|
-
if (!stopped) {
|
|
205
|
-
hasAsyncError = true;
|
|
206
|
-
}
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
inputBuffer.clear();
|
|
210
|
-
int read = audioRecord.read(inputBuffer, inputBuffer.capacity());
|
|
211
|
-
if (read <= 0) {
|
|
212
|
-
if (!stopped) {
|
|
213
|
-
hasAsyncError = true;
|
|
214
|
-
}
|
|
215
|
-
break;
|
|
216
|
-
}
|
|
217
|
-
audioEncoder.queueInputBuffer(index, 0, read,
|
|
218
|
-
getPresentationTimeUs(), 0);
|
|
219
|
-
}
|
|
220
|
-
} catch (Exception e) {
|
|
221
|
-
if (!stopped) {
|
|
222
|
-
Log.e(TAG, "Recording stopped, Audio Thread error", e);
|
|
223
|
-
hasAsyncError = true;
|
|
224
|
-
e.printStackTrace();
|
|
225
|
-
}
|
|
226
|
-
} finally {
|
|
227
|
-
audioRecord.stop();
|
|
228
|
-
audioRecord.release();
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
private long getPresentationTimeUs() {
|
|
235
|
-
if (!isStartTimestampInitialized) {
|
|
236
|
-
startTimestampUs =
|
|
237
|
-
System.nanoTime() / RecorderConstant.NANOSECONDS_IN_MICROSECOND;
|
|
238
|
-
isStartTimestampInitialized = true;
|
|
239
|
-
}
|
|
240
|
-
return (System.nanoTime() / RecorderConstant.NANOSECONDS_IN_MICROSECOND
|
|
241
|
-
- startTimestampUs);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
private int calculateBitRate(int width, int height, int frameRate) {
|
|
245
|
-
return (int) (RecorderConstant.BITRATE_MULTIPLIER *
|
|
246
|
-
frameRate * width * height);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
private void startMuxerIfSetUp(MediaMuxer muxer) {
|
|
250
|
-
if (audioTrackIndex >= 0 && videoTrackIndex >= 0) {
|
|
251
|
-
muxer.start();
|
|
252
|
-
muxerStarted = true;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
|
257
|
-
private boolean writeAudioBufferToFile(MediaCodec audioEncoder, MediaMuxer muxer,
|
|
258
|
-
MediaCodec.BufferInfo bufferInfo) {
|
|
259
|
-
int encoderStatus;
|
|
260
|
-
|
|
261
|
-
encoderStatus = audioEncoder.dequeueOutputBuffer(bufferInfo, 0);
|
|
262
|
-
if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
|
|
263
|
-
if (audioTrackIndex > 0) {
|
|
264
|
-
Log.e(TAG, "Recording stopped, audioTrackIndex greater than zero");
|
|
265
|
-
return false;
|
|
266
|
-
}
|
|
267
|
-
audioTrackIndex = muxer.addTrack(audioEncoder.getOutputFormat());
|
|
268
|
-
startMuxerIfSetUp(muxer);
|
|
269
|
-
} else if (encoderStatus < 0 && encoderStatus != MediaCodec.INFO_TRY_AGAIN_LATER) {
|
|
270
|
-
Log.w(TAG, "Unexpected result from audio encoder.dequeueOutputBuffer: "
|
|
271
|
-
+ encoderStatus + ", however continuing recording");
|
|
272
|
-
} else if (encoderStatus >= 0) {
|
|
273
|
-
ByteBuffer encodedData = audioEncoder.getOutputBuffer(encoderStatus);
|
|
274
|
-
if (encodedData == null) {
|
|
275
|
-
Log.e(TAG, "Recording stopped, " +
|
|
276
|
-
"Unable to retrieve output buffer of audio encoder");
|
|
277
|
-
return false;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
if (bufferInfo.presentationTimeUs > this.lastAudioTimestampUs
|
|
281
|
-
&& muxerStarted && bufferInfo.size != 0 &&
|
|
282
|
-
(bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
|
|
283
|
-
this.lastAudioTimestampUs = bufferInfo.presentationTimeUs;
|
|
284
|
-
muxer.writeSampleData(audioTrackIndex, encodedData, bufferInfo);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
audioEncoder.releaseOutputBuffer(encoderStatus, false);
|
|
288
|
-
|
|
289
|
-
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
|
|
290
|
-
Log.v(TAG, "Recording stopped, audio encoder buffer reached end of stream");
|
|
291
|
-
return false;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
return true;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
|
298
|
-
private boolean writeVideoBufferToFile(MediaCodec videoEncoder, MediaMuxer muxer,
|
|
299
|
-
MediaCodec.BufferInfo bufferInfo) {
|
|
300
|
-
int encoderStatus;
|
|
301
|
-
|
|
302
|
-
encoderStatus = videoEncoder.dequeueOutputBuffer(bufferInfo,
|
|
303
|
-
RecorderConstant.MEDIA_QUEUE_BUFFERING_DEFAULT_TIMEOUT_MS);
|
|
304
|
-
if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
|
|
305
|
-
if (videoTrackIndex > 0) {
|
|
306
|
-
Log.e(TAG, "Recording stopped, videoTrackIndex greater than zero");
|
|
307
|
-
return false;
|
|
308
|
-
}
|
|
309
|
-
videoTrackIndex = muxer.addTrack(videoEncoder.getOutputFormat());
|
|
310
|
-
startMuxerIfSetUp(muxer);
|
|
311
|
-
} else if (encoderStatus < 0 && encoderStatus != MediaCodec.INFO_TRY_AGAIN_LATER) {
|
|
312
|
-
Log.w(TAG, "Unexpected result from encoder.dequeueOutputBuffer: "
|
|
313
|
-
+ encoderStatus + ", however continuing recording");
|
|
314
|
-
} else if (encoderStatus >= 0) {
|
|
315
|
-
ByteBuffer encodedData = videoEncoder.getOutputBuffer(encoderStatus);
|
|
316
|
-
if (encodedData == null) {
|
|
317
|
-
Log.w(TAG, "Recording stopped, " +
|
|
318
|
-
"Unable to retrieve output buffer of videoEncoder");
|
|
319
|
-
return false;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
if (bufferInfo.size != 0 &&
|
|
323
|
-
(bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
|
|
324
|
-
bufferInfo.presentationTimeUs = getPresentationTimeUs();
|
|
325
|
-
muxer.writeSampleData(videoTrackIndex, encodedData, bufferInfo);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
videoEncoder.releaseOutputBuffer(encoderStatus, false);
|
|
329
|
-
|
|
330
|
-
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
|
|
331
|
-
Log.v(TAG, "Recording stopped, video encoder buffer reached end of stream");
|
|
332
|
-
return false;
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
return true;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
@RequiresApi(api = Build.VERSION_CODES.Q)
|
|
339
|
-
@Override
|
|
340
|
-
public void run() {
|
|
341
|
-
VirtualDisplay virtualDisplay = null;
|
|
342
|
-
MediaCodec videoEncoder = null;
|
|
343
|
-
MediaCodec audioEncoder = null;
|
|
344
|
-
Surface surface = null;
|
|
345
|
-
Thread audioRecordThread = null;
|
|
346
|
-
MediaMuxer muxer = null;
|
|
347
|
-
try {
|
|
348
|
-
videoEncoder = MediaCodec.createEncoderByType(RECORDING_DEFAULT_VIDEO_MIME_TYPE);
|
|
349
|
-
|
|
350
|
-
MediaCodecInfo.VideoCapabilities videoEncoderCapabilities = videoEncoder
|
|
351
|
-
.getCodecInfo().getCapabilitiesForType(RECORDING_DEFAULT_VIDEO_MIME_TYPE)
|
|
352
|
-
.getVideoCapabilities();
|
|
353
|
-
|
|
354
|
-
int videoFrameRate = Math.min(VIDEO_CODEC_DEFAULT_FRAME_RATE,
|
|
355
|
-
videoEncoderCapabilities.getSupportedFrameRates().getUpper());
|
|
356
|
-
|
|
357
|
-
int videoBitrate = videoEncoderCapabilities.getBitrateRange()
|
|
358
|
-
.clamp(calculateBitRate(this.videoWidth, this.videoHeight, videoFrameRate));
|
|
359
|
-
|
|
360
|
-
Log.i(TAG, String.format("Recording starting with frame rate = %d FPS " +
|
|
361
|
-
"and bitrate = %5.2f Mbps",
|
|
362
|
-
videoFrameRate, videoBitrate / BPS_IN_MBPS));
|
|
363
|
-
|
|
364
|
-
MediaFormat videoEncoderFormat =
|
|
365
|
-
initVideoEncoderFormat(RECORDING_DEFAULT_VIDEO_MIME_TYPE,
|
|
366
|
-
this.videoWidth, this.videoHeight, videoBitrate, videoFrameRate);
|
|
367
|
-
|
|
368
|
-
videoEncoder.configure(videoEncoderFormat, null, null,
|
|
369
|
-
MediaCodec.CONFIGURE_FLAG_ENCODE);
|
|
370
|
-
surface = videoEncoder.createInputSurface();
|
|
371
|
-
videoEncoder.start();
|
|
372
|
-
|
|
373
|
-
Handler handler = new Handler(Looper.getMainLooper());
|
|
374
|
-
virtualDisplay = initVirtualDisplay(this.mediaProjection, surface, handler,
|
|
375
|
-
this.videoWidth, this.videoHeight, this.videoDpi);
|
|
376
|
-
|
|
377
|
-
int sampleRate = RecorderConstant.AUDIO_CODEC_SAMPLE_RATE_HZ;
|
|
378
|
-
audioEncoder = initAudioCodec(sampleRate);
|
|
379
|
-
audioEncoder.start();
|
|
380
|
-
|
|
381
|
-
AudioRecord audioRecord = initAudioRecord(this.mediaProjection, sampleRate);
|
|
382
|
-
|
|
383
|
-
muxer = new MediaMuxer(this.outputFilePath,
|
|
384
|
-
MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
|
|
385
|
-
|
|
386
|
-
// set output file orientation info
|
|
387
|
-
// note: this method must be run before muxer.start()
|
|
388
|
-
muxer.setOrientationHint(recordingRotation);
|
|
389
|
-
|
|
390
|
-
audioRecordThread = initAudioRecordThread(audioEncoder, audioRecord,
|
|
391
|
-
this.recordingPriority);
|
|
392
|
-
audioRecordThread.start();
|
|
393
|
-
|
|
394
|
-
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
|
395
|
-
lastAudioTimestampUs = NO_TIMESTAMP_SET;
|
|
396
|
-
|
|
397
|
-
long recordingStartTime = System.currentTimeMillis();
|
|
398
|
-
|
|
399
|
-
while (!stopped && !hasAsyncError) {
|
|
400
|
-
if (!writeAudioBufferToFile(audioEncoder, muxer, bufferInfo)) {
|
|
401
|
-
break;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
if (videoTrackIndex >= 0 && audioTrackIndex < 0) {
|
|
405
|
-
continue; // wait for audio config before processing any video data frames
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
if (!writeVideoBufferToFile(videoEncoder, muxer, bufferInfo)) {
|
|
409
|
-
break;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
if ((System.currentTimeMillis() - recordingStartTime) >= this.recordingMaxDuration) {
|
|
413
|
-
Log.v(TAG, "Recording stopped, reached maximum duration");
|
|
414
|
-
stopped = true;
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
} catch (Exception mainException) {
|
|
418
|
-
Log.e(TAG, "run: Exception occurred during recording", mainException);
|
|
419
|
-
} finally {
|
|
420
|
-
if (muxer != null) {
|
|
421
|
-
muxer.stop();
|
|
422
|
-
muxer.release();
|
|
423
|
-
muxer = null;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
if (virtualDisplay != null) {
|
|
427
|
-
virtualDisplay.release();
|
|
428
|
-
virtualDisplay = null;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
if (surface != null) {
|
|
432
|
-
surface.release();
|
|
433
|
-
surface = null;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
if (videoEncoder != null) {
|
|
437
|
-
videoEncoder.stop();
|
|
438
|
-
videoEncoder.release();
|
|
439
|
-
videoEncoder = null;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
if (audioRecordThread != null) {
|
|
443
|
-
audioStopped = true;
|
|
444
|
-
try {
|
|
445
|
-
audioRecordThread.join();
|
|
446
|
-
audioRecordThread = null;
|
|
447
|
-
} catch (InterruptedException e) {
|
|
448
|
-
Log.w(TAG, "Error releasing resources, audioRecordThread: ", e);
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
if (audioEncoder != null) {
|
|
453
|
-
audioEncoder.stop();
|
|
454
|
-
audioEncoder.release();
|
|
455
|
-
audioEncoder = null;
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
}
|