forked from hardcoresushi/DroidFS
TODO.md & Update dependencies
This commit is contained in:
parent
2bbf003df5
commit
e51bd2ceba
26
TODO.md
Normal file
26
TODO.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# TODO
|
||||||
|
|
||||||
|
Here is a list of features that it would be nice to have in DroidFS.
|
||||||
|
|
||||||
|
## Security
|
||||||
|
- [hardened_malloc](https://github.com/GrapheneOS/hardened_malloc) compatibility ([#181](https://github.com/hardcore-sushi/DroidFS/issues/181))
|
||||||
|
- Internal keyboard for passwords
|
||||||
|
|
||||||
|
## UX
|
||||||
|
- File associations editor
|
||||||
|
- Modifiable CryFS scrypt parameters
|
||||||
|
- Alert dialog showing details of file operations
|
||||||
|
- Internal file browser to select volumes
|
||||||
|
|
||||||
|
## Health
|
||||||
|
- F-Droid ABI split
|
||||||
|
- OpenSSL & FFmpeg as git submodules (useful for F-Droid)
|
||||||
|
- Remove all android:configChanges from AndroidManifest.xml
|
||||||
|
- More efficient thumbnails cache
|
||||||
|
- Guide for translators
|
||||||
|
- Usage & code documentation
|
||||||
|
- Automated tests
|
||||||
|
|
||||||
|
## And:
|
||||||
|
- All the [feature requests on the GitHub repo](https://github.com/hardcore-sushi/DroidFS/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement)
|
||||||
|
- All the [feature requests on the Gitea repo](https://forge.chapril.org/hardcoresushi/DroidFS/issues?q=&state=open&labels=748)
|
@ -110,16 +110,16 @@ dependencies {
|
|||||||
implementation "androidx.preference:preference-ktx:1.2.0"
|
implementation "androidx.preference:preference-ktx:1.2.0"
|
||||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
||||||
implementation 'com.google.android.material:material:1.8.0'
|
implementation 'com.google.android.material:material:1.8.0'
|
||||||
implementation "com.github.bumptech.glide:glide:4.13.2"
|
implementation 'com.github.bumptech.glide:glide:4.15.1'
|
||||||
implementation "androidx.biometric:biometric-ktx:1.2.0-alpha05"
|
implementation "androidx.biometric:biometric-ktx:1.2.0-alpha05"
|
||||||
|
|
||||||
def exoplayer_version = "2.18.5"
|
def exoplayer_version = "2.18.6"
|
||||||
implementation "com.google.android.exoplayer:exoplayer-core:$exoplayer_version"
|
implementation "com.google.android.exoplayer:exoplayer-core:$exoplayer_version"
|
||||||
implementation "com.google.android.exoplayer:exoplayer-ui:$exoplayer_version"
|
implementation "com.google.android.exoplayer:exoplayer-ui:$exoplayer_version"
|
||||||
|
|
||||||
implementation "androidx.concurrent:concurrent-futures:1.1.0"
|
implementation "androidx.concurrent:concurrent-futures:1.1.0"
|
||||||
|
|
||||||
def camerax_version = "1.3.0-alpha05"
|
def camerax_version = "1.3.0-alpha06"
|
||||||
implementation "androidx.camera:camera-camera2:$camerax_version"
|
implementation "androidx.camera:camera-camera2:$camerax_version"
|
||||||
implementation "androidx.camera:camera-lifecycle:$camerax_version"
|
implementation "androidx.camera:camera-lifecycle:$camerax_version"
|
||||||
implementation "androidx.camera:camera-view:$camerax_version"
|
implementation "androidx.camera:camera-view:$camerax_version"
|
||||||
|
@ -223,9 +223,9 @@ public final class SucklessRecorder implements VideoOutput {
|
|||||||
*/
|
*/
|
||||||
DISABLED,
|
DISABLED,
|
||||||
/**
|
/**
|
||||||
* The recording is being recorded with audio.
|
* Audio recording is enabled for the running recording.
|
||||||
*/
|
*/
|
||||||
ACTIVE,
|
ENABLED,
|
||||||
/**
|
/**
|
||||||
* The audio encoder encountered errors.
|
* The audio encoder encountered errors.
|
||||||
*/
|
*/
|
||||||
@ -817,6 +817,24 @@ public final class SucklessRecorder implements VideoOutput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void mute(@NonNull SucklessRecording activeRecording, boolean muted) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
if (!isSameRecording(activeRecording, mPendingRecordingRecord) && !isSameRecording(
|
||||||
|
activeRecording, mActiveRecordingRecord)) {
|
||||||
|
// If this Recording is no longer active, log and treat as a no-op.
|
||||||
|
// This is not technically an error since the recording can be finalized
|
||||||
|
// asynchronously.
|
||||||
|
Logger.d(TAG,
|
||||||
|
"mute() called on a recording that is no longer active: "
|
||||||
|
+ activeRecording.getOutputOptions());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
RecordingRecord finalRecordingRecord = isSameRecording(activeRecording,
|
||||||
|
mPendingRecordingRecord) ? mPendingRecordingRecord : mActiveRecordingRecord;
|
||||||
|
mSequentialExecutor.execute(() -> muteInternal(finalRecordingRecord, muted));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void finalizePendingRecording(@NonNull RecordingRecord recordingToFinalize,
|
private void finalizePendingRecording(@NonNull RecordingRecord recordingToFinalize,
|
||||||
@VideoRecordError int error, @Nullable Throwable cause) {
|
@VideoRecordError int error, @Nullable Throwable cause) {
|
||||||
recordingToFinalize.finalizeRecording(Uri.EMPTY);
|
recordingToFinalize.finalizeRecording(Uri.EMPTY);
|
||||||
@ -949,8 +967,8 @@ public final class SucklessRecorder implements VideoOutput {
|
|||||||
(transformationInfo) -> mSurfaceTransformationInfo = transformationInfo);
|
(transformationInfo) -> mSurfaceTransformationInfo = transformationInfo);
|
||||||
Size surfaceSize = surfaceRequest.getResolution();
|
Size surfaceSize = surfaceRequest.getResolution();
|
||||||
// Fetch and cache nearest encoder profiles, if one exists.
|
// Fetch and cache nearest encoder profiles, if one exists.
|
||||||
VideoCapabilities capabilities =
|
LegacyVideoCapabilities capabilities =
|
||||||
VideoCapabilities.from(surfaceRequest.getCamera().getCameraInfo());
|
LegacyVideoCapabilities.from(surfaceRequest.getCamera().getCameraInfo());
|
||||||
Quality highestSupportedQuality = capabilities.findHighestSupportedQualityFor(surfaceSize);
|
Quality highestSupportedQuality = capabilities.findHighestSupportedQualityFor(surfaceSize);
|
||||||
Logger.d(TAG, "Using supported quality of " + highestSupportedQuality
|
Logger.d(TAG, "Using supported quality of " + highestSupportedQuality
|
||||||
+ " for surface size " + surfaceSize);
|
+ " for surface size " + surfaceSize);
|
||||||
@ -1368,13 +1386,13 @@ public final class SucklessRecorder implements VideoOutput {
|
|||||||
// Fall-through
|
// Fall-through
|
||||||
case ERROR_SOURCE:
|
case ERROR_SOURCE:
|
||||||
// Fall-through
|
// Fall-through
|
||||||
case ACTIVE:
|
case ENABLED:
|
||||||
// Fall-through
|
// Fall-through
|
||||||
case DISABLED:
|
case DISABLED:
|
||||||
throw new AssertionError(
|
throw new AssertionError(
|
||||||
"Incorrectly invoke startInternal in audio state " + mAudioState);
|
"Incorrectly invoke startInternal in audio state " + mAudioState);
|
||||||
case IDLING:
|
case IDLING:
|
||||||
setAudioState(recordingToStart.hasAudioEnabled() ? AudioState.ACTIVE
|
setAudioState(recordingToStart.hasAudioEnabled() ? AudioState.ENABLED
|
||||||
: AudioState.DISABLED);
|
: AudioState.DISABLED);
|
||||||
break;
|
break;
|
||||||
case INITIALIZING:
|
case INITIALIZING:
|
||||||
@ -1385,7 +1403,7 @@ public final class SucklessRecorder implements VideoOutput {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
setupAudio(recordingToStart);
|
setupAudio(recordingToStart);
|
||||||
setAudioState(AudioState.ACTIVE);
|
setAudioState(AudioState.ENABLED);
|
||||||
} catch (AudioSourceAccessException | InvalidConfigException e) {
|
} catch (AudioSourceAccessException | InvalidConfigException e) {
|
||||||
Logger.e(TAG, "Unable to create audio resource with error: ", e);
|
Logger.e(TAG, "Unable to create audio resource with error: ", e);
|
||||||
AudioState audioState;
|
AudioState audioState;
|
||||||
@ -1403,7 +1421,7 @@ public final class SucklessRecorder implements VideoOutput {
|
|||||||
|
|
||||||
initEncoderAndAudioSourceCallbacks(recordingToStart);
|
initEncoderAndAudioSourceCallbacks(recordingToStart);
|
||||||
if (isAudioEnabled()) {
|
if (isAudioEnabled()) {
|
||||||
mAudioSource.start();
|
mAudioSource.start(recordingToStart.isMuted());
|
||||||
mAudioEncoder.start();
|
mAudioEncoder.start();
|
||||||
}
|
}
|
||||||
mVideoEncoder.start();
|
mVideoEncoder.start();
|
||||||
@ -1535,8 +1553,6 @@ public final class SucklessRecorder implements VideoOutput {
|
|||||||
public void onSilenceStateChanged(boolean silenced) {
|
public void onSilenceStateChanged(boolean silenced) {
|
||||||
if (mIsAudioSourceSilenced != silenced) {
|
if (mIsAudioSourceSilenced != silenced) {
|
||||||
mIsAudioSourceSilenced = silenced;
|
mIsAudioSourceSilenced = silenced;
|
||||||
mAudioErrorCause = silenced ? new IllegalStateException(
|
|
||||||
"The audio source has been silenced.") : null;
|
|
||||||
updateInProgressStatusEvent();
|
updateInProgressStatusEvent();
|
||||||
} else {
|
} else {
|
||||||
Logger.w(TAG, "Audio source silenced transitions"
|
Logger.w(TAG, "Audio source silenced transitions"
|
||||||
@ -1579,9 +1595,9 @@ public final class SucklessRecorder implements VideoOutput {
|
|||||||
@Override
|
@Override
|
||||||
public void onEncodedData(@NonNull EncodedData encodedData) {
|
public void onEncodedData(@NonNull EncodedData encodedData) {
|
||||||
if (mAudioState == AudioState.DISABLED) {
|
if (mAudioState == AudioState.DISABLED) {
|
||||||
throw new AssertionError(
|
encodedData.close();
|
||||||
"Audio is not enabled but audio encoded data is "
|
throw new AssertionError("Audio is not enabled but audio "
|
||||||
+ "produced.");
|
+ "encoded data is being produced.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the media muxer doesn't yet exist, we may need to create and
|
// If the media muxer doesn't yet exist, we may need to create and
|
||||||
@ -1847,6 +1863,19 @@ public final class SucklessRecorder implements VideoOutput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExecutedBy("mSequentialExecutor")
|
||||||
|
private void muteInternal(@NonNull RecordingRecord recordingToMute, boolean muted) {
|
||||||
|
if (recordingToMute.isMuted() == muted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
recordingToMute.mute(muted);
|
||||||
|
// Only mute/unmute audio source if recording is in-progress and it is not already stopping.
|
||||||
|
if (mInProgressRecording == recordingToMute && !mInProgressRecordingStopping
|
||||||
|
&& mAudioSource != null) {
|
||||||
|
mAudioSource.mute(muted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
|
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
|
||||||
static void notifyEncoderSourceStopped(@NonNull Encoder encoder) {
|
static void notifyEncoderSourceStopped(@NonNull Encoder encoder) {
|
||||||
if (encoder instanceof SucklessEncoderImpl) {
|
if (encoder instanceof SucklessEncoderImpl) {
|
||||||
@ -1941,8 +1970,10 @@ public final class SucklessRecorder implements VideoOutput {
|
|||||||
// Audio will not be initialized until the first recording with audio enabled is
|
// Audio will not be initialized until the first recording with audio enabled is
|
||||||
// started. So if the audio state is INITIALIZING, consider the audio is disabled.
|
// started. So if the audio state is INITIALIZING, consider the audio is disabled.
|
||||||
return AudioStats.AUDIO_STATE_DISABLED;
|
return AudioStats.AUDIO_STATE_DISABLED;
|
||||||
case ACTIVE:
|
case ENABLED:
|
||||||
if (mIsAudioSourceSilenced) {
|
if (mInProgressRecording != null && mInProgressRecording.isMuted()) {
|
||||||
|
return AudioStats.AUDIO_STATE_MUTED;
|
||||||
|
} else if (mIsAudioSourceSilenced) {
|
||||||
return AudioStats.AUDIO_STATE_SOURCE_SILENCED;
|
return AudioStats.AUDIO_STATE_SOURCE_SILENCED;
|
||||||
} else {
|
} else {
|
||||||
return AudioStats.AUDIO_STATE_ACTIVE;
|
return AudioStats.AUDIO_STATE_ACTIVE;
|
||||||
@ -1971,7 +2002,7 @@ public final class SucklessRecorder implements VideoOutput {
|
|||||||
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
|
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
|
||||||
@ExecutedBy("mSequentialExecutor")
|
@ExecutedBy("mSequentialExecutor")
|
||||||
boolean isAudioEnabled() {
|
boolean isAudioEnabled() {
|
||||||
return mAudioState == AudioState.ACTIVE;
|
return mAudioState == AudioState.ENABLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
|
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
|
||||||
@ -2043,7 +2074,7 @@ public final class SucklessRecorder implements VideoOutput {
|
|||||||
break;
|
break;
|
||||||
case DISABLED:
|
case DISABLED:
|
||||||
// Fall-through
|
// Fall-through
|
||||||
case ACTIVE:
|
case ENABLED:
|
||||||
setAudioState(AudioState.IDLING);
|
setAudioState(AudioState.IDLING);
|
||||||
mAudioSource.stop();
|
mAudioSource.stop();
|
||||||
break;
|
break;
|
||||||
@ -2497,6 +2528,8 @@ public final class SucklessRecorder implements VideoOutput {
|
|||||||
/* no-op by default */
|
/* no-op by default */
|
||||||
});
|
});
|
||||||
|
|
||||||
|
private final AtomicBoolean mMuted = new AtomicBoolean(false);
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
static RecordingRecord from(@NonNull SucklessPendingRecording pendingRecording, long recordingId) {
|
static RecordingRecord from(@NonNull SucklessPendingRecording pendingRecording, long recordingId) {
|
||||||
return new AutoValue_SucklessRecorder_RecordingRecord(
|
return new AutoValue_SucklessRecorder_RecordingRecord(
|
||||||
@ -2769,6 +2802,14 @@ public final class SucklessRecorder implements VideoOutput {
|
|||||||
finalizeRecordingInternal(mRecordingFinalizer.getAndSet(null), uri);
|
finalizeRecordingInternal(mRecordingFinalizer.getAndSet(null), uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void mute(boolean muted) {
|
||||||
|
mMuted.set(muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isMuted() {
|
||||||
|
return mMuted.get();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close this recording, as if calling {@link #finalizeRecording(Uri)} with parameter
|
* Close this recording, as if calling {@link #finalizeRecording(Uri)} with parameter
|
||||||
* {@link Uri#EMPTY}.
|
* {@link Uri#EMPTY}.
|
||||||
|
@ -159,6 +159,25 @@ public final class SucklessRecording implements AutoCloseable {
|
|||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mutes or un-mutes the current recording.
|
||||||
|
*
|
||||||
|
* <p>The output file will contain an audio track even the whole recording is muted. Create a
|
||||||
|
* recording without calling {@link PendingRecording#withAudioEnabled()} to record a file
|
||||||
|
* with no audio track.
|
||||||
|
*
|
||||||
|
* <p>Muting or unmuting a recording that isn't created
|
||||||
|
* {@link PendingRecording#withAudioEnabled()} with audio enabled is no-op.
|
||||||
|
*
|
||||||
|
* @param muted mutes the recording if {@code true}, un-mutes otherwise.
|
||||||
|
*/
|
||||||
|
public void mute(boolean muted) {
|
||||||
|
if (mIsClosed.get()) {
|
||||||
|
throw new IllegalStateException("The recording has been stopped.");
|
||||||
|
}
|
||||||
|
mRecorder.mute(this, muted);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close this recording.
|
* Close this recording.
|
||||||
*
|
*
|
||||||
|
@ -1675,4 +1675,3 @@ public class SucklessEncoderImpl implements Encoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1683,4 +1683,3 @@ public class EncoderImpl implements Encoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -226,9 +226,9 @@ public final class Recorder implements VideoOutput {
|
|||||||
*/
|
*/
|
||||||
DISABLED,
|
DISABLED,
|
||||||
/**
|
/**
|
||||||
* The recording is being recorded with audio.
|
* Audio recording is enabled for the running recording.
|
||||||
*/
|
*/
|
||||||
ACTIVE,
|
ENABLED,
|
||||||
/**
|
/**
|
||||||
* The audio encoder encountered errors.
|
* The audio encoder encountered errors.
|
||||||
*/
|
*/
|
||||||
@ -907,6 +907,24 @@ public final class Recorder implements VideoOutput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void mute(@NonNull Recording activeRecording, boolean muted) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
if (!isSameRecording(activeRecording, mPendingRecordingRecord) && !isSameRecording(
|
||||||
|
activeRecording, mActiveRecordingRecord)) {
|
||||||
|
// If this Recording is no longer active, log and treat as a no-op.
|
||||||
|
// This is not technically an error since the recording can be finalized
|
||||||
|
// asynchronously.
|
||||||
|
Logger.d(TAG,
|
||||||
|
"mute() called on a recording that is no longer active: "
|
||||||
|
+ activeRecording.getOutputOptions());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
RecordingRecord finalRecordingRecord = isSameRecording(activeRecording,
|
||||||
|
mPendingRecordingRecord) ? mPendingRecordingRecord : mActiveRecordingRecord;
|
||||||
|
mSequentialExecutor.execute(() -> muteInternal(finalRecordingRecord, muted));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void finalizePendingRecording(@NonNull RecordingRecord recordingToFinalize,
|
private void finalizePendingRecording(@NonNull RecordingRecord recordingToFinalize,
|
||||||
@VideoRecordError int error, @Nullable Throwable cause) {
|
@VideoRecordError int error, @Nullable Throwable cause) {
|
||||||
recordingToFinalize.finalizeRecording(Uri.EMPTY);
|
recordingToFinalize.finalizeRecording(Uri.EMPTY);
|
||||||
@ -1039,8 +1057,8 @@ public final class Recorder implements VideoOutput {
|
|||||||
(transformationInfo) -> mSurfaceTransformationInfo = transformationInfo);
|
(transformationInfo) -> mSurfaceTransformationInfo = transformationInfo);
|
||||||
Size surfaceSize = surfaceRequest.getResolution();
|
Size surfaceSize = surfaceRequest.getResolution();
|
||||||
// Fetch and cache nearest encoder profiles, if one exists.
|
// Fetch and cache nearest encoder profiles, if one exists.
|
||||||
VideoCapabilities capabilities =
|
LegacyVideoCapabilities capabilities =
|
||||||
VideoCapabilities.from(surfaceRequest.getCamera().getCameraInfo());
|
LegacyVideoCapabilities.from(surfaceRequest.getCamera().getCameraInfo());
|
||||||
Quality highestSupportedQuality = capabilities.findHighestSupportedQualityFor(surfaceSize);
|
Quality highestSupportedQuality = capabilities.findHighestSupportedQualityFor(surfaceSize);
|
||||||
Logger.d(TAG, "Using supported quality of " + highestSupportedQuality
|
Logger.d(TAG, "Using supported quality of " + highestSupportedQuality
|
||||||
+ " for surface size " + surfaceSize);
|
+ " for surface size " + surfaceSize);
|
||||||
@ -1473,13 +1491,13 @@ public final class Recorder implements VideoOutput {
|
|||||||
// Fall-through
|
// Fall-through
|
||||||
case ERROR_SOURCE:
|
case ERROR_SOURCE:
|
||||||
// Fall-through
|
// Fall-through
|
||||||
case ACTIVE:
|
case ENABLED:
|
||||||
// Fall-through
|
// Fall-through
|
||||||
case DISABLED:
|
case DISABLED:
|
||||||
throw new AssertionError(
|
throw new AssertionError(
|
||||||
"Incorrectly invoke startInternal in audio state " + mAudioState);
|
"Incorrectly invoke startInternal in audio state " + mAudioState);
|
||||||
case IDLING:
|
case IDLING:
|
||||||
setAudioState(recordingToStart.hasAudioEnabled() ? AudioState.ACTIVE
|
setAudioState(recordingToStart.hasAudioEnabled() ? AudioState.ENABLED
|
||||||
: AudioState.DISABLED);
|
: AudioState.DISABLED);
|
||||||
break;
|
break;
|
||||||
case INITIALIZING:
|
case INITIALIZING:
|
||||||
@ -1490,7 +1508,7 @@ public final class Recorder implements VideoOutput {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
setupAudio(recordingToStart);
|
setupAudio(recordingToStart);
|
||||||
setAudioState(AudioState.ACTIVE);
|
setAudioState(AudioState.ENABLED);
|
||||||
} catch (AudioSourceAccessException | InvalidConfigException e) {
|
} catch (AudioSourceAccessException | InvalidConfigException e) {
|
||||||
Logger.e(TAG, "Unable to create audio resource with error: ", e);
|
Logger.e(TAG, "Unable to create audio resource with error: ", e);
|
||||||
AudioState audioState;
|
AudioState audioState;
|
||||||
@ -1508,7 +1526,7 @@ public final class Recorder implements VideoOutput {
|
|||||||
|
|
||||||
initEncoderAndAudioSourceCallbacks(recordingToStart);
|
initEncoderAndAudioSourceCallbacks(recordingToStart);
|
||||||
if (isAudioEnabled()) {
|
if (isAudioEnabled()) {
|
||||||
mAudioSource.start();
|
mAudioSource.start(recordingToStart.isMuted());
|
||||||
mAudioEncoder.start();
|
mAudioEncoder.start();
|
||||||
}
|
}
|
||||||
mVideoEncoder.start();
|
mVideoEncoder.start();
|
||||||
@ -1640,8 +1658,6 @@ public final class Recorder implements VideoOutput {
|
|||||||
public void onSilenceStateChanged(boolean silenced) {
|
public void onSilenceStateChanged(boolean silenced) {
|
||||||
if (mIsAudioSourceSilenced != silenced) {
|
if (mIsAudioSourceSilenced != silenced) {
|
||||||
mIsAudioSourceSilenced = silenced;
|
mIsAudioSourceSilenced = silenced;
|
||||||
mAudioErrorCause = silenced ? new IllegalStateException(
|
|
||||||
"The audio source has been silenced.") : null;
|
|
||||||
updateInProgressStatusEvent();
|
updateInProgressStatusEvent();
|
||||||
} else {
|
} else {
|
||||||
Logger.w(TAG, "Audio source silenced transitions"
|
Logger.w(TAG, "Audio source silenced transitions"
|
||||||
@ -1684,9 +1700,9 @@ public final class Recorder implements VideoOutput {
|
|||||||
@Override
|
@Override
|
||||||
public void onEncodedData(@NonNull EncodedData encodedData) {
|
public void onEncodedData(@NonNull EncodedData encodedData) {
|
||||||
if (mAudioState == AudioState.DISABLED) {
|
if (mAudioState == AudioState.DISABLED) {
|
||||||
throw new AssertionError(
|
encodedData.close();
|
||||||
"Audio is not enabled but audio encoded data is "
|
throw new AssertionError("Audio is not enabled but audio "
|
||||||
+ "produced.");
|
+ "encoded data is being produced.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the media muxer doesn't yet exist, we may need to create and
|
// If the media muxer doesn't yet exist, we may need to create and
|
||||||
@ -1952,6 +1968,19 @@ public final class Recorder implements VideoOutput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExecutedBy("mSequentialExecutor")
|
||||||
|
private void muteInternal(@NonNull RecordingRecord recordingToMute, boolean muted) {
|
||||||
|
if (recordingToMute.isMuted() == muted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
recordingToMute.mute(muted);
|
||||||
|
// Only mute/unmute audio source if recording is in-progress and it is not already stopping.
|
||||||
|
if (mInProgressRecording == recordingToMute && !mInProgressRecordingStopping
|
||||||
|
&& mAudioSource != null) {
|
||||||
|
mAudioSource.mute(muted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
|
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
|
||||||
static void notifyEncoderSourceStopped(@NonNull Encoder encoder) {
|
static void notifyEncoderSourceStopped(@NonNull Encoder encoder) {
|
||||||
if (encoder instanceof EncoderImpl) {
|
if (encoder instanceof EncoderImpl) {
|
||||||
@ -2046,8 +2075,10 @@ public final class Recorder implements VideoOutput {
|
|||||||
// Audio will not be initialized until the first recording with audio enabled is
|
// Audio will not be initialized until the first recording with audio enabled is
|
||||||
// started. So if the audio state is INITIALIZING, consider the audio is disabled.
|
// started. So if the audio state is INITIALIZING, consider the audio is disabled.
|
||||||
return AudioStats.AUDIO_STATE_DISABLED;
|
return AudioStats.AUDIO_STATE_DISABLED;
|
||||||
case ACTIVE:
|
case ENABLED:
|
||||||
if (mIsAudioSourceSilenced) {
|
if (mInProgressRecording != null && mInProgressRecording.isMuted()) {
|
||||||
|
return AudioStats.AUDIO_STATE_MUTED;
|
||||||
|
} else if (mIsAudioSourceSilenced) {
|
||||||
return AudioStats.AUDIO_STATE_SOURCE_SILENCED;
|
return AudioStats.AUDIO_STATE_SOURCE_SILENCED;
|
||||||
} else {
|
} else {
|
||||||
return AudioStats.AUDIO_STATE_ACTIVE;
|
return AudioStats.AUDIO_STATE_ACTIVE;
|
||||||
@ -2076,7 +2107,7 @@ public final class Recorder implements VideoOutput {
|
|||||||
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
|
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
|
||||||
@ExecutedBy("mSequentialExecutor")
|
@ExecutedBy("mSequentialExecutor")
|
||||||
boolean isAudioEnabled() {
|
boolean isAudioEnabled() {
|
||||||
return mAudioState == AudioState.ACTIVE;
|
return mAudioState == AudioState.ENABLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
|
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
|
||||||
@ -2148,7 +2179,7 @@ public final class Recorder implements VideoOutput {
|
|||||||
break;
|
break;
|
||||||
case DISABLED:
|
case DISABLED:
|
||||||
// Fall-through
|
// Fall-through
|
||||||
case ACTIVE:
|
case ENABLED:
|
||||||
setAudioState(AudioState.IDLING);
|
setAudioState(AudioState.IDLING);
|
||||||
mAudioSource.stop();
|
mAudioSource.stop();
|
||||||
break;
|
break;
|
||||||
@ -2602,6 +2633,8 @@ public final class Recorder implements VideoOutput {
|
|||||||
/* no-op by default */
|
/* no-op by default */
|
||||||
});
|
});
|
||||||
|
|
||||||
|
private final AtomicBoolean mMuted = new AtomicBoolean(false);
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
static RecordingRecord from(@NonNull PendingRecording pendingRecording, long recordingId) {
|
static RecordingRecord from(@NonNull PendingRecording pendingRecording, long recordingId) {
|
||||||
return new AutoValue_Recorder_RecordingRecord(
|
return new AutoValue_Recorder_RecordingRecord(
|
||||||
@ -2930,6 +2963,14 @@ public final class Recorder implements VideoOutput {
|
|||||||
finalizeRecordingInternal(mRecordingFinalizer.getAndSet(null), uri);
|
finalizeRecordingInternal(mRecordingFinalizer.getAndSet(null), uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void mute(boolean muted) {
|
||||||
|
mMuted.set(muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isMuted() {
|
||||||
|
return mMuted.get();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close this recording, as if calling {@link #finalizeRecording(Uri)} with parameter
|
* Close this recording, as if calling {@link #finalizeRecording(Uri)} with parameter
|
||||||
* {@link Uri#EMPTY}.
|
* {@link Uri#EMPTY}.
|
||||||
|
@ -156,6 +156,25 @@ public final class Recording implements AutoCloseable {
|
|||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mutes or un-mutes the current recording.
|
||||||
|
*
|
||||||
|
* <p>The output file will contain an audio track even the whole recording is muted. Create a
|
||||||
|
* recording without calling {@link PendingRecording#withAudioEnabled()} to record a file
|
||||||
|
* with no audio track.
|
||||||
|
*
|
||||||
|
* <p>Muting or unmuting a recording that isn't created
|
||||||
|
* {@link PendingRecording#withAudioEnabled()} with audio enabled is no-op.
|
||||||
|
*
|
||||||
|
* @param muted mutes the recording if {@code true}, un-mutes otherwise.
|
||||||
|
*/
|
||||||
|
public void mute(boolean muted) {
|
||||||
|
if (mIsClosed.get()) {
|
||||||
|
throw new IllegalStateException("The recording has been stopped.");
|
||||||
|
}
|
||||||
|
mRecorder.mute(this, muted);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close this recording.
|
* Close this recording.
|
||||||
*
|
*
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = "1.8.10"
|
ext.kotlin_version = '1.8.21'
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
Loading…
Reference in New Issue
Block a user