From 518a325583b511b6533b26c7437bfa1c05a31817 Mon Sep 17 00:00:00 2001 From: Jimmy Lucidarme Date: Sun, 23 May 2021 00:33:19 +0200 Subject: [PATCH 01/29] WIP refacto --- .../com/fpvout/digiview/H264Extractor.java | 23 +-- .../InputStreamBufferedDataSource.java | 7 +- .../digiview/InputStreamDataSource.java | 7 +- .../com/fpvout/digiview/MainActivity.java | 167 ++++++++--------- .../java/com/fpvout/digiview/OverlayView.java | 2 + .../fpvout/digiview/PerformancePreset.java | 24 +-- .../digiview/UsbDeviceBroadcastReceiver.java | 5 +- .../fpvout/digiview/UsbMaskConnection.java | 43 ++++- .../fpvout/digiview/VideoReaderExoplayer.java | 171 ++++++++++-------- .../main/java/usb/AndroidUSBInputStream.java | 47 +---- .../main/java/usb/AndroidUSBOutputStream.java | 73 +------- 11 files changed, 251 insertions(+), 318 deletions(-) diff --git a/app/src/main/java/com/fpvout/digiview/H264Extractor.java b/app/src/main/java/com/fpvout/digiview/H264Extractor.java index f59a02c..d48597e 100644 --- a/app/src/main/java/com/fpvout/digiview/H264Extractor.java +++ b/app/src/main/java/com/fpvout/digiview/H264Extractor.java @@ -1,17 +1,16 @@ package com.fpvout.digiview; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.extractor.ExtractorOutput; -import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.ts.H264Reader; +import com.google.android.exoplayer2.extractor.ts.SeiReader; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader; +import com.google.android.exoplayer2.util.NonNullApi; import com.google.android.exoplayer2.util.ParsableByteArray; -import com.google.android.exoplayer2.extractor.ts.SeiReader; import java.io.IOException; import java.util.ArrayList; @@ -21,9 +20,6 @@ * Extracts data from H264 bitstreams. */ public final class H264Extractor implements Extractor { - /** Factory for {@link H264Extractor} instances. */ - public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new H264Extractor()}; - private static int MAX_SYNC_FRAME_SIZE = 131072; private long firstSampleTimestampUs; @@ -33,33 +29,27 @@ public final class H264Extractor implements Extractor { private boolean startedPacket; - public H264Extractor() { - this(0); - } - public H264Extractor(int mMaxSyncFrameSize, int mSampleTime) { this(0, mMaxSyncFrameSize, mSampleTime); } - public H264Extractor(long firstSampleTimestampUs) { - this(firstSampleTimestampUs, MAX_SYNC_FRAME_SIZE, (int) sampleTime); - } - public H264Extractor(long firstSampleTimestampUs, int mMaxSyncFrameSize, int mSampleTime) { MAX_SYNC_FRAME_SIZE = mMaxSyncFrameSize; sampleTime = mSampleTime; this.firstSampleTimestampUs = firstSampleTimestampUs; - reader = new H264Reader(new SeiReader(new ArrayList()),false,true); + reader = new H264Reader(new SeiReader(new ArrayList<>()), false, true); sampleData = new ParsableByteArray(MAX_SYNC_FRAME_SIZE); } // Extractor implementation. @Override - public boolean sniff(ExtractorInput input) throws IOException { + @NonNullApi + public boolean sniff(ExtractorInput input) { return true; } @Override + @NonNullApi public void init(ExtractorOutput output) { reader.createTracks(output, new TsPayloadReader.TrackIdGenerator(0, 1)); output.endTracks(); @@ -78,6 +68,7 @@ public void release() { } @Override + @NonNullApi public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException { int bytesRead = input.read(sampleData.getData(), 0, MAX_SYNC_FRAME_SIZE); if (bytesRead == C.RESULT_END_OF_INPUT) { diff --git a/app/src/main/java/com/fpvout/digiview/InputStreamBufferedDataSource.java b/app/src/main/java/com/fpvout/digiview/InputStreamBufferedDataSource.java index 7ba7af9..11fb41d 100644 --- a/app/src/main/java/com/fpvout/digiview/InputStreamBufferedDataSource.java +++ b/app/src/main/java/com/fpvout/digiview/InputStreamBufferedDataSource.java @@ -1,6 +1,5 @@ package com.fpvout.digiview; -import android.content.Context; import android.net.Uri; import com.google.android.exoplayer2.C; @@ -19,8 +18,7 @@ public class InputStreamBufferedDataSource implements DataSource { private static final String ERROR_THREAD_NOT_INITIALIZED = "Read thread not initialized, call first 'startReadThread()'"; private static final long READ_TIMEOUT = 200; - private Context context; - private DataSpec dataSpec; + private final DataSpec dataSpec; private InputStream inputStream; private long bytesRemaining; private boolean opened; @@ -30,8 +28,7 @@ public class InputStreamBufferedDataSource implements DataSource { private boolean working; - public InputStreamBufferedDataSource(Context context, DataSpec dataSpec, InputStream inputStream) { - this.context = context; + public InputStreamBufferedDataSource(DataSpec dataSpec, InputStream inputStream) { this.dataSpec = dataSpec; this.inputStream = inputStream; startReadThread(); diff --git a/app/src/main/java/com/fpvout/digiview/InputStreamDataSource.java b/app/src/main/java/com/fpvout/digiview/InputStreamDataSource.java index 7b10606..1e5f6ad 100644 --- a/app/src/main/java/com/fpvout/digiview/InputStreamDataSource.java +++ b/app/src/main/java/com/fpvout/digiview/InputStreamDataSource.java @@ -1,6 +1,5 @@ package com.fpvout.digiview; -import android.content.Context; import android.net.Uri; import com.google.android.exoplayer2.C; @@ -13,14 +12,12 @@ import java.io.InputStream; public class InputStreamDataSource implements DataSource { - private Context context; - private DataSpec dataSpec; + private final DataSpec dataSpec; private InputStream inputStream; private long bytesRemaining; private boolean opened; - public InputStreamDataSource(Context context, DataSpec dataSpec, InputStream inputStream) { - this.context = context; + public InputStreamDataSource(DataSpec dataSpec, InputStream inputStream) { this.dataSpec = dataSpec; this.inputStream = inputStream; } diff --git a/app/src/main/java/com/fpvout/digiview/MainActivity.java b/app/src/main/java/com/fpvout/digiview/MainActivity.java index f471c71..31bc346 100644 --- a/app/src/main/java/com/fpvout/digiview/MainActivity.java +++ b/app/src/main/java/com/fpvout/digiview/MainActivity.java @@ -3,7 +3,6 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.LayoutTransition; -import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -25,24 +24,20 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.preference.PreferenceManager; -import java.util.HashMap; - import io.sentry.SentryLevel; import io.sentry.android.core.SentryAndroid; +import static com.fpvout.digiview.UsbMaskConnection.ACTION_USB_PERMISSION; +import static com.fpvout.digiview.VideoReaderExoplayer.VideoReaderEventMessageCode; import static com.fpvout.digiview.VideoReaderExoplayer.VideoZoomedIn; public class MainActivity extends AppCompatActivity implements UsbDeviceListener { - private static final String ACTION_USB_PERMISSION = "com.fpvout.digiview.USB_PERMISSION"; private static final String TAG = "DIGIVIEW"; - private static final int VENDOR_ID = 11427; - private static final int PRODUCT_ID = 31; - private int shortAnimationDuration; + private final int shortAnimationDuration = getResources().getInteger(android.R.integer.config_shortAnimTime); private float buttonAlpha = 1; private View settingsButton; private View watermarkView; private OverlayView overlayView; - PendingIntent permissionIntent; UsbDeviceBroadcastReceiver usbDeviceBroadcastReceiver; UsbManager usbManager; UsbDevice usbDevice; @@ -65,63 +60,66 @@ protected void onCreate(Bundle savedInstanceState) { checkDataCollectionAgreement(); // Hide top bar and status bar - View decorView = getWindow().getDecorView(); - decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY - | View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_FULLSCREEN); - ActionBar actionBar = getSupportActionBar(); - if (actionBar != null) { - actionBar.hide(); - } + setFullscreen(); // Prevent screen from sleeping getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); - permissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0); + // Register app for auto launch usbDeviceBroadcastReceiver = new UsbDeviceBroadcastReceiver(this); - IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION); registerReceiver(usbDeviceBroadcastReceiver, filter); IntentFilter filterDetached = new IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED); registerReceiver(usbDeviceBroadcastReceiver, filterDetached); - shortAnimationDuration = getResources().getInteger(android.R.integer.config_shortAnimTime); watermarkView = findViewById(R.id.watermarkView); overlayView = findViewById(R.id.overlayView); fpvView = findViewById(R.id.fpvView); - settingsButton = findViewById(R.id.settingsButton); - settingsButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent intent = new Intent(v.getContext(), SettingsActivity.class); - v.getContext().startActivity(intent); - } - }); sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + // Enable resizing animations ((ViewGroup) findViewById(R.id.mainLayout)).getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING); setupGestureDetectors(); - mUsbMaskConnection = new UsbMaskConnection(); - mVideoReader = new VideoReaderExoplayer(fpvView, overlayView, this); + settingsButton.setOnClickListener(v -> { + Intent intent = new Intent(v.getContext(), SettingsActivity.class); + v.getContext().startActivity(intent); + }); + Handler videoReaderEventListener = new Handler(this.getMainLooper(), msg -> onVideoReaderEvent((VideoReaderEventMessageCode) msg.obj)); + mVideoReader = new VideoReaderExoplayer(fpvView, this, videoReaderEventListener); + + mUsbMaskConnection = new UsbMaskConnection(); if (!usbConnected) { - if (searchDevice()) { + usbDevice = UsbMaskConnection.searchDevice(usbManager, getApplicationContext()); + if (usbDevice != null) { + Log.i(TAG, "USB - usbDevice attached"); + showOverlay(R.string.usb_device_found, OverlayStatus.Connected); connect(); } else { - showOverlay(R.string.waiting_for_usb_device, OverlayStatus.Connected); + showOverlay(R.string.waiting_for_usb_device, OverlayStatus.Disconnected); } } } + private void setFullscreen() { + View decorView = getWindow().getDecorView(); + decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + | View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_FULLSCREEN); + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.hide(); + } + } + private void setupGestureDetectors() { gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() { @Override @@ -181,7 +179,7 @@ private void updateVideoZoom() { private void cancelButtonAnimation() { Handler handler = settingsButton.getHandler(); if (handler != null) { - settingsButton.getHandler().removeCallbacksAndMessages(null); + handler.removeCallbacksAndMessages(null); } } @@ -221,17 +219,41 @@ private void autoHideSettingsButton() { if (overlayView.getVisibility() == View.VISIBLE) return; if (buttonAlpha == 0) return; - settingsButton.postDelayed(new Runnable() { - @Override - public void run() { - buttonAlpha = 0; - settingsButton.animate() - .alpha(0) - .setDuration(shortAnimationDuration); - } + settingsButton.postDelayed(() -> { + buttonAlpha = 0; + settingsButton.animate() + .alpha(0) + .setDuration(shortAnimationDuration); }, 3000); } + private boolean onVideoReaderEvent(VideoReaderEventMessageCode m) { + if (VideoReaderEventMessageCode.WAITING_FOR_VIDEO.equals(m)) { + Log.d(TAG, "event: WAITING_FOR_VIDEO"); + showOverlay(R.string.waiting_for_video, OverlayStatus.Connected); + } else if (VideoReaderEventMessageCode.VIDEO_PLAYING.equals(m)) { + Log.d(TAG, "event: VIDEO_PLAYING"); + hideOverlay(); + } + return false; // false to continue listening + } + + private void showOverlay(int textId, OverlayStatus connected) { + overlayView.show(textId, connected); + updateWatermark(); + autoHideSettingsButton(); + updateVideoZoom(); + + } + + private void hideOverlay() { + overlayView.hide(); + updateWatermark(); + autoHideSettingsButton(); + updateVideoZoom(); + } + + @Override public void usbDeviceApproved(UsbDevice device) { Log.i(TAG, "USB - usbDevice approved"); @@ -247,41 +269,13 @@ public void usbDeviceDetached() { this.onStop(); } - private boolean searchDevice() { - HashMap deviceList = usbManager.getDeviceList(); - if (deviceList.size() <= 0) { - usbDevice = null; - return false; - } - - for (UsbDevice device : deviceList.values()) { - if (device.getVendorId() == VENDOR_ID && device.getProductId() == PRODUCT_ID) { - if (usbManager.hasPermission(device)) { - Log.i(TAG, "USB - usbDevice attached"); - showOverlay(R.string.usb_device_found, OverlayStatus.Connected); - usbDevice = device; - return true; - } - - usbManager.requestPermission(device, permissionIntent); - } - } - - return false; - } private void connect() { usbConnected = true; - mUsbMaskConnection.setUsbDevice(usbManager.openDevice(usbDevice), usbDevice); + mUsbMaskConnection.setUsbDevice(usbManager, usbDevice); mVideoReader.setUsbMaskConnection(mUsbMaskConnection); overlayView.hide(); mVideoReader.start(); - updateWatermark(); - autoHideSettingsButton(); - } - - private void showOverlay() { - } @Override @@ -289,37 +283,20 @@ public void onResume() { super.onResume(); Log.d(TAG, "APP - On Resume"); - View decorView = getWindow().getDecorView(); - decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY - | View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_FULLSCREEN); - ActionBar actionBar = getSupportActionBar(); - if (actionBar != null) { - actionBar.hide(); - } + setFullscreen(); if (!usbConnected) { - if (searchDevice()) { + usbDevice = UsbMaskConnection.searchDevice(usbManager, getApplicationContext()); + if (usbDevice != null) { Log.d(TAG, "APP - On Resume usbDevice device found"); + showOverlay(R.string.usb_device_found, OverlayStatus.Connected); connect(); } else { - showOverlay(R.string.waiting_for_usb_device, OverlayStatus.Connected); + showOverlay(R.string.waiting_for_usb_device, OverlayStatus.Disconnected); } } settingsButton.setAlpha(1); - autoHideSettingsButton(); - updateWatermark(); - updateVideoZoom(); - } - - private void showOverlay(int textId, OverlayStatus connected) { - overlayView.show(textId, connected); - updateWatermark(); - showSettingsButton(); } @Override diff --git a/app/src/main/java/com/fpvout/digiview/OverlayView.java b/app/src/main/java/com/fpvout/digiview/OverlayView.java index b1327c8..8a6c736 100644 --- a/app/src/main/java/com/fpvout/digiview/OverlayView.java +++ b/app/src/main/java/com/fpvout/digiview/OverlayView.java @@ -38,6 +38,8 @@ private void showInfo(String text, OverlayStatus status){ int image = R.drawable.ic_goggles_white; switch(status){ + case Connected: + break; case Disconnected: image = R.drawable.ic_goggles_disconnected_white; break; diff --git a/app/src/main/java/com/fpvout/digiview/PerformancePreset.java b/app/src/main/java/com/fpvout/digiview/PerformancePreset.java index 17b2a8a..cdb8a27 100644 --- a/app/src/main/java/com/fpvout/digiview/PerformancePreset.java +++ b/app/src/main/java/com/fpvout/digiview/PerformancePreset.java @@ -1,19 +1,17 @@ package com.fpvout.digiview; -public class PerformancePreset { - int h264ReaderMaxSyncFrameSize = 131072; - int h264ReaderSampleTime = 10000; - int exoPlayerMinBufferMs = 500; - int exoPlayerMaxBufferMs = 2000; - int exoPlayerBufferForPlaybackMs = 17; - int exoPlayerBufferForPlaybackAfterRebufferMs = 17; - DataSourceType dataSourceType = DataSourceType.INPUT_STREAM; - - private PerformancePreset(){ +import androidx.annotation.NonNull; - } +public class PerformancePreset { + int h264ReaderMaxSyncFrameSize; + int h264ReaderSampleTime; + int exoPlayerMinBufferMs; + int exoPlayerMaxBufferMs; + int exoPlayerBufferForPlaybackMs; + int exoPlayerBufferForPlaybackAfterRebufferMs; + DataSourceType dataSourceType; - private PerformancePreset(int mH264ReaderMaxSyncFrameSize, int mH264ReaderSampleTime, int mExoPlayerMinBufferMs, int mExoPlayerMaxBufferMs, int mExoPlayerBufferForPlaybackMs, int mExoPlayerBufferForPlaybackAfterRebufferMs, DataSourceType mDataSourceType){ + private PerformancePreset(int mH264ReaderMaxSyncFrameSize, int mH264ReaderSampleTime, int mExoPlayerMinBufferMs, int mExoPlayerMaxBufferMs, int mExoPlayerBufferForPlaybackMs, int mExoPlayerBufferForPlaybackAfterRebufferMs, DataSourceType mDataSourceType) { h264ReaderMaxSyncFrameSize = mH264ReaderMaxSyncFrameSize; h264ReaderSampleTime = mH264ReaderSampleTime; exoPlayerMinBufferMs = mExoPlayerMinBufferMs; @@ -63,7 +61,9 @@ static PerformancePreset getPreset(String p) { } } + @Override + @NonNull public String toString() { return "PerformancePreset{" + "h264ReaderMaxSyncFrameSize=" + h264ReaderMaxSyncFrameSize + diff --git a/app/src/main/java/com/fpvout/digiview/UsbDeviceBroadcastReceiver.java b/app/src/main/java/com/fpvout/digiview/UsbDeviceBroadcastReceiver.java index db64b09..e2eebc9 100644 --- a/app/src/main/java/com/fpvout/digiview/UsbDeviceBroadcastReceiver.java +++ b/app/src/main/java/com/fpvout/digiview/UsbDeviceBroadcastReceiver.java @@ -6,11 +6,12 @@ import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; +import static com.fpvout.digiview.UsbMaskConnection.ACTION_USB_PERMISSION; + public class UsbDeviceBroadcastReceiver extends BroadcastReceiver { - private static final String ACTION_USB_PERMISSION = "com.fpvout.digiview.USB_PERMISSION"; private final UsbDeviceListener listener; - public UsbDeviceBroadcastReceiver(UsbDeviceListener listener ){ + public UsbDeviceBroadcastReceiver(UsbDeviceListener listener) { this.listener = listener; } diff --git a/app/src/main/java/com/fpvout/digiview/UsbMaskConnection.java b/app/src/main/java/com/fpvout/digiview/UsbMaskConnection.java index 1d6d90e..b841bd9 100644 --- a/app/src/main/java/com/fpvout/digiview/UsbMaskConnection.java +++ b/app/src/main/java/com/fpvout/digiview/UsbMaskConnection.java @@ -1,19 +1,26 @@ package com.fpvout.digiview; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbInterface; +import android.hardware.usb.UsbManager; import java.io.IOException; +import java.util.HashMap; import usb.AndroidUSBInputStream; import usb.AndroidUSBOutputStream; public class UsbMaskConnection { + public static final String ACTION_USB_PERMISSION = "com.fpvout.digiview.USB_PERMISSION"; + private static final int VENDOR_ID = 11427; + private static final int PRODUCT_ID = 31; private final byte[] magicPacket = "RMVT".getBytes(); private UsbDeviceConnection usbConnection; - private UsbDevice device; private UsbInterface usbInterface; AndroidUSBInputStream mInputStream; AndroidUSBOutputStream mOutputStream; @@ -22,16 +29,23 @@ public class UsbMaskConnection { public UsbMaskConnection() { } - public void setUsbDevice(UsbDeviceConnection c, UsbDevice d) { - usbConnection = c; - device = d; - usbInterface = device.getInterface(3); + public static UsbDevice searchDevice(UsbManager usbManager, Context c) { + PendingIntent permissionIntent = PendingIntent.getBroadcast(c, 0, new Intent(ACTION_USB_PERMISSION), 0); - usbConnection.claimInterface(usbInterface,true); + HashMap deviceList = usbManager.getDeviceList(); + if (deviceList.size() <= 0) { + return null; + } - mOutputStream = new AndroidUSBOutputStream(usbInterface.getEndpoint(0), usbConnection); - mInputStream = new AndroidUSBInputStream(usbInterface.getEndpoint(1), usbInterface.getEndpoint(0), usbConnection); - ready = true; + for (UsbDevice device : deviceList.values()) { + if (device.getVendorId() == VENDOR_ID && device.getProductId() == PRODUCT_ID) { + if (usbManager.hasPermission(device)) { + return device; + } + usbManager.requestPermission(device, permissionIntent); + } + } + return null; } public void start(){ @@ -59,4 +73,15 @@ public void stop() { public boolean isReady() { return ready; } + + public void setUsbDevice(UsbManager usbManager, UsbDevice d) { + usbConnection = usbManager.openDevice(d); + usbInterface = d.getInterface(3); + + usbConnection.claimInterface(usbInterface, true); + + mOutputStream = new AndroidUSBOutputStream(usbInterface.getEndpoint(0), usbConnection); + mInputStream = new AndroidUSBInputStream(usbInterface.getEndpoint(1), usbInterface.getEndpoint(0), usbConnection); + ready = true; + } } diff --git a/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java b/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java index 586b1e3..b897577 100644 --- a/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java +++ b/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java @@ -5,6 +5,7 @@ import android.net.Uri; import android.os.Handler; import android.os.Looper; +import android.os.Message; import android.util.Log; import android.view.SurfaceView; @@ -25,62 +26,58 @@ import com.google.android.exoplayer2.source.ProgressiveMediaSource; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSpec; +import com.google.android.exoplayer2.util.NonNullApi; import com.google.android.exoplayer2.video.VideoListener; import usb.AndroidUSBInputStream; public class VideoReaderExoplayer { - private static final String TAG = "DIGIVIEW"; - private SimpleExoPlayer mPlayer; + private static final String TAG = "DIGIVIEW"; + private Handler videoReaderEventListener; + private SimpleExoPlayer mPlayer; + private UsbMaskConnection mUsbMaskConnection; static final String VideoPreset = "VideoPreset"; - private final OverlayView overlayView; private final SurfaceView surfaceView; private AndroidUSBInputStream inputStream; - private UsbMaskConnection mUsbMaskConnection; + + VideoReaderExoplayer(SurfaceView videoSurface, Context c) { + surfaceView = videoSurface; + context = c; + sharedPreferences = PreferenceManager.getDefaultSharedPreferences(c); + } + private boolean zoomedIn; private final Context context; private PerformancePreset performancePreset = PerformancePreset.getPreset(PerformancePreset.PresetType.DEFAULT); static final String VideoZoomedIn = "VideoZoomedIn"; private final SharedPreferences sharedPreferences; - VideoReaderExoplayer(SurfaceView videoSurface, OverlayView overlayView, Context c) { - surfaceView = videoSurface; - this.overlayView = overlayView; - context = c; - sharedPreferences = PreferenceManager.getDefaultSharedPreferences(c); - } - - VideoReaderExoplayer(SurfaceView videoSurface, OverlayView overlayView, Context c, PerformancePreset p) { - this(videoSurface,overlayView,c); - performancePreset = p; - } + VideoReaderExoplayer(SurfaceView videoSurface, Context c, Handler v) { + this(videoSurface, c); + videoReaderEventListener = v; + } - public void setUsbMaskConnection(UsbMaskConnection connection) { - mUsbMaskConnection = connection; - inputStream = mUsbMaskConnection.mInputStream; - } + public void start() { + zoomedIn = sharedPreferences.getBoolean(VideoZoomedIn, true); + performancePreset = PerformancePreset.getPreset(sharedPreferences.getString(VideoPreset, "default")); - public void start() { - zoomedIn = sharedPreferences.getBoolean(VideoZoomedIn, true); - performancePreset = PerformancePreset.getPreset(sharedPreferences.getString(VideoPreset, "default")); + DefaultLoadControl loadControl = new DefaultLoadControl.Builder().setBufferDurationsMs(performancePreset.exoPlayerMinBufferMs, performancePreset.exoPlayerMaxBufferMs, performancePreset.exoPlayerBufferForPlaybackMs, performancePreset.exoPlayerBufferForPlaybackAfterRebufferMs).build(); + mPlayer = new SimpleExoPlayer.Builder(context).setLoadControl(loadControl).build(); + mPlayer.setVideoSurfaceView(surfaceView); + mPlayer.setVideoScalingMode(C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING); + mPlayer.setWakeMode(C.WAKE_MODE_LOCAL); - DefaultLoadControl loadControl = new DefaultLoadControl.Builder().setBufferDurationsMs(performancePreset.exoPlayerMinBufferMs, performancePreset.exoPlayerMaxBufferMs, performancePreset.exoPlayerBufferForPlaybackMs, performancePreset.exoPlayerBufferForPlaybackAfterRebufferMs).build(); - mPlayer = new SimpleExoPlayer.Builder(context).setLoadControl(loadControl).build(); - mPlayer.setVideoSurfaceView(surfaceView); - mPlayer.setVideoScalingMode(C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING); - mPlayer.setWakeMode(C.WAKE_MODE_LOCAL); + DataSpec dataSpec = new DataSpec(Uri.EMPTY, 0, C.LENGTH_UNSET); - DataSpec dataSpec = new DataSpec(Uri.EMPTY, 0, C.LENGTH_UNSET); + Log.d(TAG, "preset: " + performancePreset); - Log.d(TAG, "preset: " + performancePreset); - - DataSource.Factory dataSourceFactory = () -> { - switch (performancePreset.dataSourceType){ - case INPUT_STREAM: - return (DataSource) new InputStreamDataSource(context, dataSpec, inputStream); + DataSource.Factory dataSourceFactory = () -> { + switch (performancePreset.dataSourceType) { + case INPUT_STREAM: + return (DataSource) new InputStreamDataSource(dataSpec, inputStream); case BUFFERED_INPUT_STREAM: default: - return (DataSource) new InputStreamBufferedDataSource(context, dataSpec, inputStream); + return (DataSource) new InputStreamBufferedDataSource(dataSpec, inputStream); } }; @@ -92,37 +89,48 @@ public void start() { mPlayer.play(); mPlayer.addListener(new ExoPlayer.EventListener() { @Override + @NonNullApi public void onPlayerError(ExoPlaybackException error) { switch (error.type) { case ExoPlaybackException.TYPE_SOURCE: Log.e(TAG, "PLAYER_SOURCE - TYPE_SOURCE: " + error.getSourceException().getMessage()); - overlayView.show(R.string.waiting_for_video, OverlayStatus.Error); - // TODO: let MainActivity know so it can hide watermark/show settings button - (new Handler(Looper.getMainLooper())).postDelayed(() -> { - restart(); - }, 1000); + (new Handler(Looper.getMainLooper())).postDelayed(() -> restart(), 1000); + break; + case ExoPlaybackException.TYPE_REMOTE: + Log.e(TAG, "PLAYER_SOURCE - TYPE_REMOTE: " + error.getSourceException().getMessage()); + break; + case ExoPlaybackException.TYPE_RENDERER: + Log.e(TAG, "PLAYER_SOURCE - TYPE_RENDERER: " + error.getSourceException().getMessage()); + break; + case ExoPlaybackException.TYPE_UNEXPECTED: + Log.e(TAG, "PLAYER_SOURCE - TYPE_UNEXPECTED: " + error.getSourceException().getMessage()); break; } } @Override - public void onPlaybackStateChanged(int state) { - if (state == Player.STATE_ENDED) { - Log.d(TAG, "PLAYER_STATE - ENDED"); - overlayView.show(R.string.waiting_for_video, OverlayStatus.Connected); - // TODO: let MainActivity know so it can hide watermark/show settings button - (new Handler(Looper.getMainLooper())).postDelayed(() -> { - restart(); - }, 1000); - - }else if(state == Player.STATE_READY){ - overlayView.hide(); - // TODO: let MainActivity know so it can show watermark/auto-hide settings button + public void onPlaybackStateChanged(@NonNullApi int state) { + switch (state) { + case Player.STATE_IDLE: + case Player.STATE_READY: + case Player.STATE_BUFFERING: + break; + case Player.STATE_ENDED: + Log.d(TAG, "PLAYER_STATE - ENDED"); + sendEvent(VideoReaderEventMessageCode.WAITING_FOR_VIDEO); // let MainActivity know so it can hide watermark/show settings button + (new Handler(Looper.getMainLooper())).postDelayed(() -> restart(), 1000); + break; } } }); mPlayer.addVideoListener(new VideoListener() { + @Override + public void onRenderedFirstFrame() { + Log.d(TAG, "PLAYER_RENDER - FIRST FRAME"); + sendEvent(VideoReaderEventMessageCode.VIDEO_PLAYING); // let MainActivity know so it can hide watermark/show settings button + } + @Override public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { if (!zoomedIn) { @@ -132,23 +140,36 @@ public void onVideoSizeChanged(int width, int height, int unappliedRotationDegre } } }); + } + + public void setUsbMaskConnection(UsbMaskConnection connection) { + mUsbMaskConnection = connection; + inputStream = mUsbMaskConnection.mInputStream; + } + + private void sendEvent(VideoReaderEventMessageCode eventCode) { + if (videoReaderEventListener != null) { // let MainActivity know so it can hide watermark/show settings button + Message videoReaderEventMessage = new Message(); + videoReaderEventMessage.obj = eventCode; + videoReaderEventListener.sendMessage(videoReaderEventMessage); } + } - public void toggleZoom() { - zoomedIn = !zoomedIn; + public void toggleZoom() { + zoomedIn = !zoomedIn; - SharedPreferences.Editor preferencesEditor = sharedPreferences.edit(); - preferencesEditor.putBoolean(VideoZoomedIn, zoomedIn); - preferencesEditor.apply(); + SharedPreferences.Editor preferencesEditor = sharedPreferences.edit(); + preferencesEditor.putBoolean(VideoZoomedIn, zoomedIn); + preferencesEditor.apply(); - ConstraintLayout.LayoutParams params = (ConstraintLayout.LayoutParams) surfaceView.getLayoutParams(); + ConstraintLayout.LayoutParams params = (ConstraintLayout.LayoutParams) surfaceView.getLayoutParams(); - if (zoomedIn) { - params.dimensionRatio = ""; - } else { - if (mPlayer == null) return; - Format videoFormat = mPlayer.getVideoFormat(); - if (videoFormat == null) return; + if (zoomedIn) { + params.dimensionRatio = ""; + } else { + if (mPlayer == null) return; + Format videoFormat = mPlayer.getVideoFormat(); + if (videoFormat == null) return; params.dimensionRatio = videoFormat.width + ":" + videoFormat.height; } @@ -168,17 +189,19 @@ public void zoomOut() { } } - public void restart() { - mPlayer.release(); + public void restart() { + mPlayer.release(); - if (mUsbMaskConnection.isReady()) { - mUsbMaskConnection.start(); - start(); - } + if (mUsbMaskConnection.isReady()) { + mUsbMaskConnection.start(); + start(); } + } - public void stop() { - if (mPlayer != null) - mPlayer.release(); - } + public void stop() { + if (mPlayer != null) + mPlayer.release(); + } + + public enum VideoReaderEventMessageCode {WAITING_FOR_VIDEO, VIDEO_PLAYING} } diff --git a/app/src/main/java/usb/AndroidUSBInputStream.java b/app/src/main/java/usb/AndroidUSBInputStream.java index 84fdd24..52ca056 100644 --- a/app/src/main/java/usb/AndroidUSBInputStream.java +++ b/app/src/main/java/usb/AndroidUSBInputStream.java @@ -1,54 +1,27 @@ -/* - * Copyright 2019, Digi International Inc. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, you can obtain one at http://mozilla.org/MPL/2.0/. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ package usb; -import java.io.IOException; -import java.io.InputStream; - import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbEndpoint; import android.util.Log; -/** - * This class acts as a wrapper to read data from the USB Interface in Android - * behaving like an {@code InputputStream} class. - */ +import java.io.InputStream; + public class AndroidUSBInputStream extends InputStream { private final String TAG = "USBInputStream"; - // Constants. - private static final int OFFSET = 0; private static final int READ_TIMEOUT = 100; - // Variables. - private UsbDeviceConnection usbConnection; - - private UsbEndpoint receiveEndPoint; + private final UsbDeviceConnection usbConnection; + private final UsbEndpoint receiveEndPoint; private final UsbEndpoint sendEndPoint; - private boolean working = false; - - /** * Class constructor. Instantiates a new {@code AndroidUSBInputStream} * object with the given parameters. * * @param readEndpoint The USB end point to use to read data from. - * @param connection The USB connection to use to read data from. - * + * @param sendEndpoint The USB end point to use to sent data to. + * @param connection The USB connection to use to read data from. * @see UsbDeviceConnection * @see UsbEndpoint */ @@ -59,13 +32,13 @@ public AndroidUSBInputStream( UsbEndpoint readEndpoint, UsbEndpoint sendEndpoint } @Override - public int read() throws IOException { + public int read() { byte[] buffer = new byte[131072]; - return read(buffer, 0, buffer.length); + return read(buffer, 0, buffer.length); } @Override - public int read(byte[] buffer, int offset, int length) throws IOException { + public int read(byte[] buffer, int offset, int length) { int receivedBytes = usbConnection.bulkTransfer(receiveEndPoint, buffer, buffer.length, READ_TIMEOUT); if (receivedBytes <= 0) { // send magic packet again; Would be great to handle this in UsbMaskConnection directly... @@ -78,6 +51,6 @@ public int read(byte[] buffer, int offset, int length) throws IOException { @Override - public void close() throws IOException {} + public void close(){} } diff --git a/app/src/main/java/usb/AndroidUSBOutputStream.java b/app/src/main/java/usb/AndroidUSBOutputStream.java index 627a4e5..b994761 100644 --- a/app/src/main/java/usb/AndroidUSBOutputStream.java +++ b/app/src/main/java/usb/AndroidUSBOutputStream.java @@ -1,29 +1,12 @@ -/* - * Copyright 2019, Digi International Inc. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, you can obtain one at http://mozilla.org/MPL/2.0/. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ package usb; -import java.io.IOException; -import java.io.OutputStream; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; - import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbEndpoint; import android.util.Log; +import java.io.IOException; +import java.io.OutputStream; + /** * This class acts as a wrapper to write data to the USB Interface in Android * behaving like an {@code OutputStream} class. @@ -32,33 +15,24 @@ public class AndroidUSBOutputStream extends OutputStream { // Constants. private static final int WRITE_TIMEOUT = 2000; - - // Variables. - private UsbDeviceConnection usbConnection; - private UsbEndpoint sendEndPoint; + private final UsbDeviceConnection usbConnection; + private final UsbEndpoint sendEndPoint; - private LinkedBlockingQueue writeQueue; - - private boolean streamOpen = true; + private final boolean streamOpen = true; /** * Class constructor. Instantiates a new {@code AndroidUSBOutputStream} * object with the given parameters. - * + * * @param writeEndpoint The USB end point to use to write data to. - * @param connection The USB connection to use to write data to. - * + * @param connection The USB connection to use to write data to. * @see UsbDeviceConnection * @see UsbEndpoint */ public AndroidUSBOutputStream(UsbEndpoint writeEndpoint, UsbDeviceConnection connection) { this.usbConnection = connection; this.sendEndPoint = writeEndpoint; - - writeQueue = new LinkedBlockingQueue<>(512); - DataWriter dataWriter = new DataWriter(); - dataWriter.start(); } /* @@ -85,39 +59,12 @@ public void write(byte[] buffer) { */ @Override public void write(byte[] buffer, int offset, int count) { - final byte[] finalData = new byte[count + offset]; - System.arraycopy(buffer, offset, finalData, 0, count); - try { - writeQueue.add(finalData); - } catch (IllegalStateException e) { - Log.e("USBOutputStream","Could not add data, write queue is full: " + e.getMessage(), e); - } - } - - /** - * Internal class used to write data coming from a queue. - */ - class DataWriter extends Thread { - @Override - public void run() { - while (streamOpen) { - try { - byte[] dataToWrite = writeQueue.poll(100, TimeUnit.MILLISECONDS); - if (dataToWrite == null) - continue; - usbConnection.bulkTransfer(sendEndPoint, dataToWrite, dataToWrite.length, WRITE_TIMEOUT); - Log.d("USBOutputStream","Message sent: " + dataToWrite.toString()); - } catch (InterruptedException e) { - Log.e("USBOutputStream","Interrupted while getting data from the write queue: " + e.getMessage(), e); - } - } - } + usbConnection.bulkTransfer(sendEndPoint, buffer, count, WRITE_TIMEOUT); + Log.d("USBOutputStream", "Message sent: " + buffer.toString()); } @Override public void close() throws IOException { - // Stop the data writer. - streamOpen = false; super.close(); } } From 258e9a28cd4bf257963e45947914f1e43f5a45fa Mon Sep 17 00:00:00 2001 From: Jimmy Lucidarme Date: Mon, 24 May 2021 03:07:59 +0200 Subject: [PATCH 02/29] bug fixes + Proper event handling for player (Observable Java pattern) --- .../com/fpvout/digiview/MainActivity.java | 24 ++++------- .../fpvout/digiview/PerformancePreset.java | 3 +- .../fpvout/digiview/VideoReaderExoplayer.java | 42 +++++++++++-------- .../main/java/usb/AndroidUSBInputStream.java | 5 ++- 4 files changed, 38 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/com/fpvout/digiview/MainActivity.java b/app/src/main/java/com/fpvout/digiview/MainActivity.java index 19d1476..22c909e 100644 --- a/app/src/main/java/com/fpvout/digiview/MainActivity.java +++ b/app/src/main/java/com/fpvout/digiview/MainActivity.java @@ -3,6 +3,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.LayoutTransition; +import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; @@ -27,12 +28,11 @@ import io.sentry.android.core.SentryAndroid; import static com.fpvout.digiview.UsbMaskConnection.ACTION_USB_PERMISSION; -import static com.fpvout.digiview.VideoReaderExoplayer.VideoReaderEventMessageCode; import static com.fpvout.digiview.VideoReaderExoplayer.VideoZoomedIn; public class MainActivity extends AppCompatActivity implements UsbDeviceListener { private static final String TAG = "DIGIVIEW"; - private final int shortAnimationDuration = getResources().getInteger(android.R.integer.config_shortAnimTime); + private int shortAnimationDuration; private float buttonAlpha = 1; private View settingsButton; private View watermarkView; @@ -64,6 +64,7 @@ protected void onCreate(Bundle savedInstanceState) { // Prevent screen from sleeping getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); // Register app for auto launch usbDeviceBroadcastReceiver = new UsbDeviceBroadcastReceiver(this); IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION); @@ -77,7 +78,7 @@ protected void onCreate(Bundle savedInstanceState) { settingsButton = findViewById(R.id.settingsButton); sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - + shortAnimationDuration = getResources().getInteger(android.R.integer.config_shortAnimTime); // Enable resizing animations ((ViewGroup) findViewById(R.id.mainLayout)).getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING); @@ -89,8 +90,9 @@ protected void onCreate(Bundle savedInstanceState) { v.getContext().startActivity(intent); }); - Handler videoReaderEventListener = new Handler(this.getMainLooper(), msg -> onVideoReaderEvent((VideoReaderEventMessageCode) msg.obj)); - mVideoReader = new VideoReaderExoplayer(fpvView, this, videoReaderEventListener); + mVideoReader = new VideoReaderExoplayer(fpvView, this); + mVideoReader.setVideoPlayingEventListener(this::hideOverlay); + mVideoReader.setVideoWaitingEventListener(() -> showOverlay(R.string.waiting_for_video, OverlayStatus.Connected)); mUsbMaskConnection = new UsbMaskConnection(); @@ -227,17 +229,6 @@ private void autoHideSettingsButton() { }, 3000); } - private boolean onVideoReaderEvent(VideoReaderEventMessageCode m) { - if (VideoReaderEventMessageCode.WAITING_FOR_VIDEO.equals(m)) { - Log.d(TAG, "event: WAITING_FOR_VIDEO"); - showOverlay(R.string.waiting_for_video, OverlayStatus.Connected); - } else if (VideoReaderEventMessageCode.VIDEO_PLAYING.equals(m)) { - Log.d(TAG, "event: VIDEO_PLAYING"); - hideOverlay(); - } - return false; // false to continue listening - } - private void showOverlay(int textId, OverlayStatus connected) { overlayView.show(textId, connected); updateWatermark(); @@ -354,6 +345,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { } } + setFullscreen(); } //onActivityResult private void checkDataCollectionAgreement() { diff --git a/app/src/main/java/com/fpvout/digiview/PerformancePreset.java b/app/src/main/java/com/fpvout/digiview/PerformancePreset.java index 3eeba24..44c4f5a 100644 --- a/app/src/main/java/com/fpvout/digiview/PerformancePreset.java +++ b/app/src/main/java/com/fpvout/digiview/PerformancePreset.java @@ -25,7 +25,8 @@ public enum PresetType { DEFAULT, CONSERVATIVE, AGGRESSIVE, - LEGACY + LEGACY, + LEGACY_BUFFERED } public enum DataSourceType { diff --git a/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java b/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java index 7d0cf08..2d695db 100644 --- a/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java +++ b/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java @@ -5,7 +5,6 @@ import android.net.Uri; import android.os.Handler; import android.os.Looper; -import android.os.Message; import android.util.Log; import android.view.SurfaceView; @@ -38,24 +37,30 @@ public class VideoReaderExoplayer { static final String VideoPreset = "VideoPreset"; private final SurfaceView surfaceView; private AndroidUSBInputStream inputStream; - private UsbMaskConnection mUsbMaskConnection; + private UsbMaskConnection mUsbMaskConnection; private boolean zoomedIn; private final Context context; private PerformancePreset performancePreset = PerformancePreset.getPreset(PerformancePreset.PresetType.DEFAULT); static final String VideoZoomedIn = "VideoZoomedIn"; private final SharedPreferences sharedPreferences; + private VideoPlayingListener videoPlayingListener = null; + private VideoWaitingListener videoWaitingListener = null; + + public void setVideoPlayingEventListener(VideoPlayingListener listener) { + this.videoPlayingListener = listener; + } + + public void setVideoWaitingEventListener(VideoWaitingListener listener) { + this.videoWaitingListener = listener; + } + VideoReaderExoplayer(SurfaceView videoSurface, Context c) { surfaceView = videoSurface; context = c; sharedPreferences = PreferenceManager.getDefaultSharedPreferences(c); } - VideoReaderExoplayer(SurfaceView videoSurface, Context c, Handler v) { - this(videoSurface, c); - videoReaderEventListener = v; - } - public void setUsbMaskConnection(UsbMaskConnection connection) { mUsbMaskConnection = connection; inputStream = mUsbMaskConnection.mInputStream; @@ -121,7 +126,8 @@ public void onPlaybackStateChanged(@NonNullApi int state) { break; case Player.STATE_ENDED: Log.d(TAG, "PLAYER_STATE - ENDED"); - sendEvent(VideoReaderEventMessageCode.WAITING_FOR_VIDEO); // let MainActivity know so it can hide watermark/show settings button + if (videoWaitingListener != null) + videoWaitingListener.onVideoWaiting(); // let MainActivity know so it can hide watermark/show settings button (new Handler(Looper.getMainLooper())).postDelayed(() -> restart(), 1000); break; } @@ -132,7 +138,8 @@ public void onPlaybackStateChanged(@NonNullApi int state) { @Override public void onRenderedFirstFrame() { Log.d(TAG, "PLAYER_RENDER - FIRST FRAME"); - sendEvent(VideoReaderEventMessageCode.VIDEO_PLAYING); // let MainActivity know so it can hide watermark/show settings button + if (videoPlayingListener != null) + videoPlayingListener.onVideoPlaying(); // let MainActivity know so it can hide watermark/show settings button } @Override @@ -144,14 +151,15 @@ public void onVideoSizeChanged(int width, int height, int unappliedRotationDegre } } }); - } + } - private void sendEvent(VideoReaderEventMessageCode eventCode) { - if (videoReaderEventListener != null) { // let MainActivity know so it can hide watermark/show settings button - Message videoReaderEventMessage = new Message(); - videoReaderEventMessage.obj = eventCode; - videoReaderEventListener.sendMessage(videoReaderEventMessage); - } + public interface VideoPlayingListener { + void onVideoPlaying(); + } + + + public interface VideoWaitingListener { + void onVideoWaiting(); } public void toggleZoom() { @@ -201,6 +209,4 @@ public void stop() { if (mPlayer != null) mPlayer.release(); } - - public enum VideoReaderEventMessageCode {WAITING_FOR_VIDEO, VIDEO_PLAYING} } diff --git a/app/src/main/java/usb/AndroidUSBInputStream.java b/app/src/main/java/usb/AndroidUSBInputStream.java index 52ca056..5d74542 100644 --- a/app/src/main/java/usb/AndroidUSBInputStream.java +++ b/app/src/main/java/usb/AndroidUSBInputStream.java @@ -4,6 +4,7 @@ import android.hardware.usb.UsbEndpoint; import android.util.Log; +import java.io.IOException; import java.io.InputStream; public class AndroidUSBInputStream extends InputStream { @@ -51,6 +52,8 @@ public int read(byte[] buffer, int offset, int length) { @Override - public void close(){} + public void close() throws IOException { + super.close(); + } } From 8aed1233e1cd463ee8e5d97ced2f265cc99b5b27 Mon Sep 17 00:00:00 2001 From: Jimmy Lucidarme Date: Mon, 24 May 2021 04:27:57 +0200 Subject: [PATCH 03/29] No more warnings + update dependencies --- app/build.gradle | 8 +- .../InputStreamBufferedDataSource.java | 10 +- .../digiview/InputStreamDataSource.java | 8 +- .../com/fpvout/digiview/MainActivity.java | 72 ++++++----- .../fpvout/digiview/VideoReaderExoplayer.java | 85 ++++++------- .../main/java/usb/AndroidUSBInputStream.java | 4 +- .../main/java/usb/AndroidUSBOutputStream.java | 2 - app/src/main/java/usb/CircularByteBuffer.java | 118 ++++++++---------- 8 files changed, 142 insertions(+), 165 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index de58b67..f360733 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,7 +12,7 @@ android { keyAlias digiviewKeyAlias } catch (ex) { - println("You should define mStoreFile, mStorePassword, mKeyPassword and mKeyAlias in ~/.gradle/gradle.properties.") + println("You should define mStoreFile, mStorePassword, mKeyPassword and mKeyAlias in ~/.gradle/gradle.properties : "+ ex.message) } } } @@ -65,14 +65,14 @@ android { dependencies { - implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'com.google.android.material:material:1.3.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' - implementation 'com.google.android.exoplayer:exoplayer:2.13.3' + implementation 'com.google.android.exoplayer:exoplayer:2.14.0' implementation 'io.sentry:sentry-android:4.3.0' implementation 'androidx.preference:preference:1.1.1' - testImplementation 'junit:junit:4.+' + testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' } \ No newline at end of file diff --git a/app/src/main/java/com/fpvout/digiview/InputStreamBufferedDataSource.java b/app/src/main/java/com/fpvout/digiview/InputStreamBufferedDataSource.java index 11fb41d..d6bc5b6 100644 --- a/app/src/main/java/com/fpvout/digiview/InputStreamBufferedDataSource.java +++ b/app/src/main/java/com/fpvout/digiview/InputStreamBufferedDataSource.java @@ -2,6 +2,8 @@ import android.net.Uri; +import androidx.annotation.NonNull; + import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSpec; @@ -20,7 +22,6 @@ public class InputStreamBufferedDataSource implements DataSource { private final DataSpec dataSpec; private InputStream inputStream; - private long bytesRemaining; private boolean opened; private CircularByteBuffer readBuffer; @@ -35,12 +36,13 @@ public InputStreamBufferedDataSource(DataSpec dataSpec, InputStream inputStream) } @Override - public void addTransferListener(TransferListener transferListener) { + public void addTransferListener(@NonNull TransferListener transferListener) { } @Override public long open(DataSpec dataSpec) throws IOException { + long bytesRemaining; try { long skipped = inputStream.skip(dataSpec.position); if (skipped < dataSpec.position) @@ -60,7 +62,7 @@ public long open(DataSpec dataSpec) throws IOException { } @Override - public int read(byte[] buffer, int offset, int readLength) throws IOException { + public int read(@NonNull byte[] buffer, int offset, int readLength) throws IOException { if (readBuffer == null) throw new IOException(ERROR_THREAD_NOT_INITIALIZED); @@ -68,8 +70,6 @@ public int read(byte[] buffer, int offset, int readLength) throws IOException { int readBytes = 0; while (System.currentTimeMillis() < deadLine && readBytes <= 0) readBytes = readBuffer.read(buffer, offset, readLength); - if (readBytes <= 0) - return readBytes; return readBytes; } diff --git a/app/src/main/java/com/fpvout/digiview/InputStreamDataSource.java b/app/src/main/java/com/fpvout/digiview/InputStreamDataSource.java index 1e5f6ad..fe1634c 100644 --- a/app/src/main/java/com/fpvout/digiview/InputStreamDataSource.java +++ b/app/src/main/java/com/fpvout/digiview/InputStreamDataSource.java @@ -2,6 +2,8 @@ import android.net.Uri; +import androidx.annotation.NonNull; + import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSpec; @@ -14,7 +16,6 @@ public class InputStreamDataSource implements DataSource { private final DataSpec dataSpec; private InputStream inputStream; - private long bytesRemaining; private boolean opened; public InputStreamDataSource(DataSpec dataSpec, InputStream inputStream) { @@ -23,12 +24,13 @@ public InputStreamDataSource(DataSpec dataSpec, InputStream inputStream) { } @Override - public void addTransferListener(TransferListener transferListener) { + public void addTransferListener(@NonNull TransferListener transferListener) { } @Override public long open(DataSpec dataSpec) throws IOException { + long bytesRemaining; try { long skipped = inputStream.skip(dataSpec.position); if (skipped < dataSpec.position) @@ -48,7 +50,7 @@ public long open(DataSpec dataSpec) throws IOException { } @Override - public int read(byte[] buffer, int offset, int readLength) throws IOException { + public int read(@NonNull byte[] buffer, int offset, int readLength) throws IOException { return inputStream.read(buffer, offset, readLength); } diff --git a/app/src/main/java/com/fpvout/digiview/MainActivity.java b/app/src/main/java/com/fpvout/digiview/MainActivity.java index 22c909e..1a59c4b 100644 --- a/app/src/main/java/com/fpvout/digiview/MainActivity.java +++ b/app/src/main/java/com/fpvout/digiview/MainActivity.java @@ -20,8 +20,14 @@ import android.view.ViewGroup; import android.view.WindowManager; +import androidx.activity.result.ActivityResult; +import androidx.activity.result.ActivityResultCallback; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; +import androidx.core.view.WindowInsetsCompat; +import androidx.core.view.WindowInsetsControllerCompat; import androidx.preference.PreferenceManager; import io.sentry.SentryLevel; @@ -109,13 +115,13 @@ protected void onCreate(Bundle savedInstanceState) { } private void setFullscreen() { - View decorView = getWindow().getDecorView(); - decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY - | View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_FULLSCREEN); + WindowInsetsControllerCompat insetsControllerCompat = new WindowInsetsControllerCompat(getWindow(), getWindow().getDecorView()); + insetsControllerCompat.hide(WindowInsetsCompat.Type.statusBars() + | WindowInsetsCompat.Type.navigationBars() + | WindowInsetsCompat.Type.captionBar() + | WindowInsetsCompat.Type.ime() + ); + insetsControllerCompat.setSystemBarsBehavior(WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE); ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { actionBar.hide(); @@ -185,14 +191,14 @@ private void cancelButtonAnimation() { } } - private void showSettingsButton() { - cancelButtonAnimation(); - - if (overlayView.getVisibility() == View.VISIBLE) { - buttonAlpha = 1; - settingsButton.setAlpha(1); - } - } +// private void showSettingsButton() { +// cancelButtonAnimation(); +// +// if (overlayView.getVisibility() == View.VISIBLE) { +// buttonAlpha = 1; +// settingsButton.setAlpha(1); +// } +// } private void toggleSettingsButton() { if (buttonAlpha == 1 && overlayView.getVisibility() == View.VISIBLE) return; @@ -327,34 +333,26 @@ protected void onDestroy() { usbConnected = false; } - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); - boolean dataCollectionAccepted = preferences.getBoolean("dataCollectionAccepted", false); - - if (requestCode == 1) { // Data Collection agreement Activity - if (resultCode == RESULT_OK && dataCollectionAccepted) { - SentryAndroid.init(this, options -> options.setBeforeSend((event, hint) -> { - if (SentryLevel.DEBUG.equals(event.getLevel())) - return null; - else - return event; - })); - } - - } - setFullscreen(); - } //onActivityResult - private void checkDataCollectionAgreement() { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); boolean dataCollectionAccepted = preferences.getBoolean("dataCollectionAccepted", false); boolean dataCollectionReplied = preferences.getBoolean("dataCollectionReplied", false); if (!dataCollectionReplied) { Intent intent = new Intent(this, DataCollectionAgreementPopupActivity.class); - startActivityForResult(intent, 1); + ActivityResultLauncher activityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), (ActivityResultCallback) result -> { + if (result.getResultCode() == RESULT_OK) { + SentryAndroid.init(getApplicationContext(), options -> options.setBeforeSend((event, hint) -> { + if (SentryLevel.DEBUG.equals(event.getLevel())) + return null; + else + return event; + })); + } + setFullscreen(); + }); + activityResultLauncher.launch(intent); + + } else if (dataCollectionAccepted) { SentryAndroid.init(this, options -> options.setBeforeSend((event, hint) -> { if (SentryLevel.DEBUG.equals(event.getLevel())) diff --git a/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java b/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java index 2d695db..ba68bb1 100644 --- a/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java +++ b/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java @@ -8,13 +8,13 @@ import android.util.Log; import android.view.SurfaceView; +import androidx.annotation.NonNull; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.preference.PreferenceManager; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.DefaultLoadControl; import com.google.android.exoplayer2.ExoPlaybackException; -import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.Player; @@ -25,14 +25,12 @@ import com.google.android.exoplayer2.source.ProgressiveMediaSource; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSpec; -import com.google.android.exoplayer2.util.NonNullApi; -import com.google.android.exoplayer2.video.VideoListener; +import com.google.android.exoplayer2.video.VideoSize; import usb.AndroidUSBInputStream; public class VideoReaderExoplayer { private static final String TAG = "DIGIVIEW"; - private Handler videoReaderEventListener; private SimpleExoPlayer mPlayer; static final String VideoPreset = "VideoPreset"; private final SurfaceView surfaceView; @@ -96,20 +94,19 @@ public void start() { mPlayer.prepare(); mPlayer.play(); - mPlayer.addListener(new ExoPlayer.EventListener() { - @Override - @NonNullApi - public void onPlayerError(ExoPlaybackException error) { - switch (error.type) { - case ExoPlaybackException.TYPE_SOURCE: - Log.e(TAG, "PLAYER_SOURCE - TYPE_SOURCE: " + error.getSourceException().getMessage()); - (new Handler(Looper.getMainLooper())).postDelayed(() -> restart(), 1000); - break; - case ExoPlaybackException.TYPE_REMOTE: - Log.e(TAG, "PLAYER_SOURCE - TYPE_REMOTE: " + error.getSourceException().getMessage()); - break; - case ExoPlaybackException.TYPE_RENDERER: - Log.e(TAG, "PLAYER_SOURCE - TYPE_RENDERER: " + error.getSourceException().getMessage()); + mPlayer.addListener(new Player.Listener() { + @Override + public void onPlayerError(@NonNull ExoPlaybackException error) { + switch (error.type) { + case ExoPlaybackException.TYPE_SOURCE: + Log.e(TAG, "PLAYER_SOURCE - TYPE_SOURCE: " + error.getSourceException().getMessage()); + (new Handler(Looper.getMainLooper())).postDelayed(() -> restart(), 1000); + break; + case ExoPlaybackException.TYPE_REMOTE: + Log.e(TAG, "PLAYER_SOURCE - TYPE_REMOTE: " + error.getSourceException().getMessage()); + break; + case ExoPlaybackException.TYPE_RENDERER: + Log.e(TAG, "PLAYER_SOURCE - TYPE_RENDERER: " + error.getSourceException().getMessage()); break; case ExoPlaybackException.TYPE_UNEXPECTED: Log.e(TAG, "PLAYER_SOURCE - TYPE_UNEXPECTED: " + error.getSourceException().getMessage()); @@ -117,39 +114,39 @@ public void onPlayerError(ExoPlaybackException error) { } } - @Override - public void onPlaybackStateChanged(@NonNullApi int state) { - switch (state) { - case Player.STATE_IDLE: - case Player.STATE_READY: - case Player.STATE_BUFFERING: - break; - case Player.STATE_ENDED: - Log.d(TAG, "PLAYER_STATE - ENDED"); - if (videoWaitingListener != null) - videoWaitingListener.onVideoWaiting(); // let MainActivity know so it can hide watermark/show settings button - (new Handler(Looper.getMainLooper())).postDelayed(() -> restart(), 1000); + @Override + public void onPlaybackStateChanged(int state) { + switch (state) { + case Player.STATE_IDLE: + case Player.STATE_READY: + case Player.STATE_BUFFERING: + break; + case Player.STATE_ENDED: + Log.d(TAG, "PLAYER_STATE - ENDED"); + if (videoWaitingListener != null) + videoWaitingListener.onVideoWaiting(); // let MainActivity know so it can hide watermark/show settings button + (new Handler(Looper.getMainLooper())).postDelayed(() -> restart(), 1000); break; } } }); - mPlayer.addVideoListener(new VideoListener() { - @Override - public void onRenderedFirstFrame() { - Log.d(TAG, "PLAYER_RENDER - FIRST FRAME"); - if (videoPlayingListener != null) - videoPlayingListener.onVideoPlaying(); // let MainActivity know so it can hide watermark/show settings button - } + mPlayer.addVideoListener(new Player.Listener() { + @Override + public void onRenderedFirstFrame() { + Log.d(TAG, "PLAYER_RENDER - FIRST FRAME"); + if (videoPlayingListener != null) + videoPlayingListener.onVideoPlaying(); // let MainActivity know so it can hide watermark/show settings button + } - @Override - public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { - if (!zoomedIn) { - ConstraintLayout.LayoutParams params = (ConstraintLayout.LayoutParams) surfaceView.getLayoutParams(); - params.dimensionRatio = width + ":" + height; - surfaceView.setLayoutParams(params); - } + @Override + public void onVideoSizeChanged(@NonNull VideoSize videosize) { + if (!zoomedIn) { + ConstraintLayout.LayoutParams params = (ConstraintLayout.LayoutParams) surfaceView.getLayoutParams(); + params.dimensionRatio = videosize.width + ":" + videosize.height; + surfaceView.setLayoutParams(params); } + } }); } diff --git a/app/src/main/java/usb/AndroidUSBInputStream.java b/app/src/main/java/usb/AndroidUSBInputStream.java index 5d74542..571531d 100644 --- a/app/src/main/java/usb/AndroidUSBInputStream.java +++ b/app/src/main/java/usb/AndroidUSBInputStream.java @@ -2,14 +2,12 @@ import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbEndpoint; -import android.util.Log; import java.io.IOException; import java.io.InputStream; public class AndroidUSBInputStream extends InputStream { - private final String TAG = "USBInputStream"; private static final int READ_TIMEOUT = 100; private final UsbDeviceConnection usbConnection; @@ -43,7 +41,7 @@ public int read(byte[] buffer, int offset, int length) { int receivedBytes = usbConnection.bulkTransfer(receiveEndPoint, buffer, buffer.length, READ_TIMEOUT); if (receivedBytes <= 0) { // send magic packet again; Would be great to handle this in UsbMaskConnection directly... - Log.d(TAG, "received buffer empty, sending magic packet again..."); + //Log.d(TAG, "received buffer empty, sending magic packet again..."); usbConnection.bulkTransfer(sendEndPoint, "RMVT".getBytes(), "RMVT".getBytes().length, 2000); receivedBytes = usbConnection.bulkTransfer(receiveEndPoint, buffer, buffer.length, READ_TIMEOUT); } diff --git a/app/src/main/java/usb/AndroidUSBOutputStream.java b/app/src/main/java/usb/AndroidUSBOutputStream.java index 31a410a..da7f042 100644 --- a/app/src/main/java/usb/AndroidUSBOutputStream.java +++ b/app/src/main/java/usb/AndroidUSBOutputStream.java @@ -18,8 +18,6 @@ public class AndroidUSBOutputStream extends OutputStream { private final UsbDeviceConnection usbConnection; private final UsbEndpoint sendEndPoint; - private final boolean streamOpen = true; - /** * Class constructor. Instantiates a new {@code AndroidUSBOutputStream} * object with the given parameters. diff --git a/app/src/main/java/usb/CircularByteBuffer.java b/app/src/main/java/usb/CircularByteBuffer.java index ba45a36..90f9109 100644 --- a/app/src/main/java/usb/CircularByteBuffer.java +++ b/app/src/main/java/usb/CircularByteBuffer.java @@ -1,18 +1,3 @@ -/* - * Copyright 2019, Digi International Inc. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, you can obtain one at http://mozilla.org/MPL/2.0/. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ package usb; /** @@ -21,7 +6,7 @@ public class CircularByteBuffer { // Variables. - private byte[] buffer; + private final byte[] buffer; private int readIndex; private int writeIndex; @@ -58,7 +43,7 @@ public CircularByteBuffer(int size) { * @throws NullPointerException if {@code data == null}. * * @see #read(byte[], int, int) - * @see #skip(int) + // * @see #skip(int) */ public synchronized int write(byte[] data, int offset, int numBytes) { if (data == null) @@ -105,8 +90,8 @@ public synchronized int write(byte[] data, int offset, int numBytes) { * @throws IllegalArgumentException if {@code offset < 0} or * if {@code numBytes < 1}. * @throws NullPointerException if {@code data == null}. - * - * @see #skip(int) + * + // * @see #skip(int) * @see #write(byte[], int, int) */ public synchronized int read(byte[] data, int offset, int numBytes) { @@ -135,54 +120,53 @@ public synchronized int read(byte[] data, int offset, int numBytes) { } else { System.arraycopy(buffer, getReadIndex(), data, offset, buffer.length - getReadIndex()); System.arraycopy(buffer, 0, data, offset + buffer.length - getReadIndex(), numBytes - (buffer.length - getReadIndex())); - readIndex = numBytes-(buffer.length - getReadIndex()); + readIndex = numBytes - (buffer.length - getReadIndex()); } - + // If we have read all bytes, set the buffer as empty. if (readIndex == writeIndex) empty = true; - - return numBytes; - } - /** - * Skips the given number of bytes from the circular byte buffer. - * - * @param numBytes Number of bytes to skip. - * @return The number of bytes actually skipped. - * - * @throws IllegalArgumentException if {@code numBytes < 1}. - * - * @see #read(byte[], int, int) - * @see #write(byte[], int, int) - */ - public synchronized int skip(int numBytes) { - if (numBytes < 1) - throw new IllegalArgumentException("Number of bytes to skip must be greater than 0."); - - // If we are empty, return 0. - if (empty) - return 0; - - if (availableToRead() < numBytes) - return skip(availableToRead()); - if (numBytes < buffer.length - getReadIndex()) - readIndex = getReadIndex() + numBytes; - else - readIndex = numBytes - (buffer.length - getReadIndex()); - - // If we have skipped all bytes, set the buffer as empty. - if (readIndex == writeIndex) - empty = true; - return numBytes; } +// /** +// * Skips the given number of bytes from the circular byte buffer. +// * +// * @param numBytes Number of bytes to skip. +// * @return The number of bytes actually skipped. +// * +// * @throws IllegalArgumentException if {@code numBytes < 1}. +// * +// * @see #read(byte[], int, int) +// * @see #write(byte[], int, int) +// */ +// public synchronized int skip(int numBytes) { +// if (numBytes < 1) +// throw new IllegalArgumentException("Number of bytes to skip must be greater than 0."); +// +// // If we are empty, return 0. +// if (empty) +// return 0; +// +// if (availableToRead() < numBytes) +// return skip(availableToRead()); +// if (numBytes < buffer.length - getReadIndex()) +// readIndex = getReadIndex() + numBytes; +// else +// readIndex = numBytes - (buffer.length - getReadIndex()); +// +// // If we have skipped all bytes, set the buffer as empty. +// if (readIndex == writeIndex) +// empty = true; +// +// return numBytes; +// } + /** * Returns the available number of bytes to read from the byte buffer. - * + * * @return The number of bytes in the buffer available for reading. - * * @see #getCapacity() * @see #read(byte[], int, int) */ @@ -212,22 +196,22 @@ private int getReadIndex() { private int getWriteIndex() { return writeIndex; } - + /** * Returns the circular byte buffer capacity. - * + * * @return The circular byte buffer capacity. */ public int getCapacity() { return buffer.length; } - - /** - * Clears the circular buffer. - */ - public void clearBuffer() { - empty = true; - readIndex = 0; - writeIndex = 0; - } + +// /** +// * Clears the circular buffer. +// */ +// public void clearBuffer() { +// empty = true; +// readIndex = 0; +// writeIndex = 0; +// } } \ No newline at end of file From 7e3a4f89a56cdb5eb709d6635ef679b97f3aac9a Mon Sep 17 00:00:00 2001 From: markusstadtmann Date: Sun, 16 May 2021 09:38:56 +0200 Subject: [PATCH 04/29] add dvr feature --- .idea/compiler.xml | 0 .idea/misc.xml | 0 app/src/main/AndroidManifest.xml | 14 ++++ .../com/fpvout/digiview/MainActivity.java | 82 +++++++++++++++++-- .../fpvout/digiview/VideoReaderExoplayer.java | 5 +- .../fpvout/digiview/dvr/MediaDispatcher.java | 59 +++++++++++++ app/src/main/res/layout/activity_main.xml | 12 +++ app/src/main/res/values/strings.xml | 5 ++ 8 files changed, 169 insertions(+), 8 deletions(-) create mode 100644 .idea/compiler.xml create mode 100644 .idea/misc.xml create mode 100644 app/src/main/java/com/fpvout/digiview/dvr/MediaDispatcher.java diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..e69de29 diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..e69de29 diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7df7ca4..5dc3361 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,6 +4,8 @@ package="com.fpvout.digiview"> + + @@ -22,6 +27,15 @@ android:name=".SettingsActivity" android:label="@string/title_activity_settings"> + + + + + + + + { + if (recorder.isRecording()) { + recorder.stop(); + } else { + recorder.start(); + } + }); + + if (!usbConnected) { + if (searchDevice()) { + connect(); + } else { + overlayView.showOpaque(R.string.waiting_for_usb_device, OverlayStatus.Disconnected); + } + } + + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (hasFocus) { + hideSystemUI(); + } + } + + private void hideSystemUI() { + // Enables regular immersive mode. + // For "lean back" mode, remove SYSTEM_UI_FLAG_IMMERSIVE. + // Or for "sticky immersive," replace it with SYSTEM_UI_FLAG_IMMERSIVE_STICKY + View decorView = getWindow().getDecorView(); + decorView.setSystemUiVisibility( + View.SYSTEM_UI_FLAG_IMMERSIVE + // Set the content to appear under the system bars so that the + // content doesn't resize when the system bars hide and show. + | View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + // Hide the nav bar and status bar + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_FULLSCREEN); + } + + // Shows the system bars by removing all the flags +// except for the ones that make the content appear under the system bars. + private void showSystemUI() { + View decorView = getWindow().getDecorView(); + decorView.setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); } @Override @@ -277,7 +345,7 @@ private void connect() { mUsbMaskConnection.setUsbDevice(usbManager.openDevice(usbDevice), usbDevice); mVideoReader.setUsbMaskConnection(mUsbMaskConnection); overlayView.hide(); - mVideoReader.start(); + mVideoReader.start(recorder); updateWatermark(); autoHideSettingsButton(); showOverlay(R.string.waiting_for_video, OverlayStatus.Connected); diff --git a/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java b/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java index 7c18bd0..af0ca2e 100644 --- a/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java +++ b/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java @@ -9,6 +9,8 @@ import android.util.Log; import android.view.SurfaceView; +import com.fpvout.digiview.dvr.DVR; +import com.fpvout.digiview.dvr.MediaDispatcher; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.preference.PreferenceManager; @@ -61,7 +63,7 @@ public void setUsbMaskConnection(UsbMaskConnection connection) { inputStream = mUsbMaskConnection.mInputStream; } - public void start() { + public void start(DVR recorder) { zoomedIn = sharedPreferences.getBoolean(VideoZoomedIn, true); performancePreset = PerformancePreset.getPreset(sharedPreferences.getString(VideoPreset, "default")); @@ -89,6 +91,7 @@ public void start() { MediaSource mediaSource = new ProgressiveMediaSource.Factory(dataSourceFactory, extractorsFactory).createMediaSource(MediaItem.fromUri(Uri.EMPTY)); mPlayer.setMediaSource(mediaSource); + mPlayer.prepare(); mPlayer.play(); mPlayer.addListener(new ExoPlayer.EventListener() { diff --git a/app/src/main/java/com/fpvout/digiview/dvr/MediaDispatcher.java b/app/src/main/java/com/fpvout/digiview/dvr/MediaDispatcher.java new file mode 100644 index 0000000..76011d2 --- /dev/null +++ b/app/src/main/java/com/fpvout/digiview/dvr/MediaDispatcher.java @@ -0,0 +1,59 @@ +package com.fpvout.digiview.dvr; + +import android.net.Uri; +import android.util.Log; + +import com.google.android.exoplayer2.upstream.DataSource; +import com.google.android.exoplayer2.upstream.DataSpec; +import com.google.android.exoplayer2.upstream.TransferListener; + +import java.io.IOException; + +import androidx.annotation.Nullable; + +public class MediaDispatcher implements DataSource { + public static final String LOG_TAG = "MediaDispatcher"; + private DataSpec mDataSpec; + private String mBuffer; + + @Override + public void addTransferListener(TransferListener transferListener) { + Log.d(LOG_TAG, "addTransferListener"); + } + + @Override + public long open(DataSpec dataSpec) throws IOException { + Log.d(LOG_TAG, "open"); + + mDataSpec = dataSpec; + return Long.MAX_VALUE; + } + + @Override + public int read(byte[] buffer, int offset, int readLength) { + Log.d(LOG_TAG, "read, readLength=" + readLength); + + if (0 == readLength) return 0; + + if (readLength > mBuffer.length()) buffer = mBuffer.getBytes(); + + return mBuffer.length(); + } + + public void write(String buffer) throws InterruptedException { + //My service calls this + mBuffer = buffer; + } + + @Nullable + @Override + public Uri getUri() { + Log.d(LOG_TAG, "getUri"); + return Uri.EMPTY; + } + + @Override + public void close() throws IOException { + Log.d(LOG_TAG, "close"); + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 3d2145d..a5bb79c 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -41,6 +41,18 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + Open-Source License MIT License + DigiView + Connect + recording started + recording stopped + No stream recording could not be started \ No newline at end of file From 98457fe5bd7bdf8b8fc409b1cf029af32db1674b Mon Sep 17 00:00:00 2001 From: markusstadtmann Date: Sun, 16 May 2021 20:44:54 +0200 Subject: [PATCH 05/29] stuff --- app/src/main/AndroidManifest.xml | 11 +--- .../com/fpvout/digiview/MainActivity.java | 25 ++++---- .../fpvout/digiview/VideoReaderExoplayer.java | 5 +- .../fpvout/digiview/dvr/MediaDispatcher.java | 59 ------------------- 4 files changed, 18 insertions(+), 82 deletions(-) delete mode 100644 app/src/main/java/com/fpvout/digiview/dvr/MediaDispatcher.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5dc3361..15b71a4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,6 +5,8 @@ + + @@ -27,15 +29,6 @@ android:name=".SettingsActivity" android:label="@string/title_activity_settings"> - - - - - - - - { if (recorder.isRecording()) { recorder.stop(); } else { - recorder.start(); + try { + recorder.start(); + } catch (IOException e) { + e.printStackTrace(); + } } }); @@ -345,7 +344,7 @@ private void connect() { mUsbMaskConnection.setUsbDevice(usbManager.openDevice(usbDevice), usbDevice); mVideoReader.setUsbMaskConnection(mUsbMaskConnection); overlayView.hide(); - mVideoReader.start(recorder); + mVideoReader.start(); updateWatermark(); autoHideSettingsButton(); showOverlay(R.string.waiting_for_video, OverlayStatus.Connected); @@ -381,6 +380,12 @@ public void onResume() { autoHideSettingsButton(); updateWatermark(); updateVideoZoom(); + + try { + recorder.init(); + } catch (IOException e) { + e.printStackTrace(); + } } private boolean onVideoReaderEvent(VideoReaderExoplayer.VideoReaderEventMessageCode m) { diff --git a/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java b/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java index af0ca2e..8e24c4f 100644 --- a/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java +++ b/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java @@ -8,9 +8,6 @@ import android.os.Message; import android.util.Log; import android.view.SurfaceView; - -import com.fpvout.digiview.dvr.DVR; -import com.fpvout.digiview.dvr.MediaDispatcher; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.preference.PreferenceManager; @@ -63,7 +60,7 @@ public void setUsbMaskConnection(UsbMaskConnection connection) { inputStream = mUsbMaskConnection.mInputStream; } - public void start(DVR recorder) { + public void start() { zoomedIn = sharedPreferences.getBoolean(VideoZoomedIn, true); performancePreset = PerformancePreset.getPreset(sharedPreferences.getString(VideoPreset, "default")); diff --git a/app/src/main/java/com/fpvout/digiview/dvr/MediaDispatcher.java b/app/src/main/java/com/fpvout/digiview/dvr/MediaDispatcher.java deleted file mode 100644 index 76011d2..0000000 --- a/app/src/main/java/com/fpvout/digiview/dvr/MediaDispatcher.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.fpvout.digiview.dvr; - -import android.net.Uri; -import android.util.Log; - -import com.google.android.exoplayer2.upstream.DataSource; -import com.google.android.exoplayer2.upstream.DataSpec; -import com.google.android.exoplayer2.upstream.TransferListener; - -import java.io.IOException; - -import androidx.annotation.Nullable; - -public class MediaDispatcher implements DataSource { - public static final String LOG_TAG = "MediaDispatcher"; - private DataSpec mDataSpec; - private String mBuffer; - - @Override - public void addTransferListener(TransferListener transferListener) { - Log.d(LOG_TAG, "addTransferListener"); - } - - @Override - public long open(DataSpec dataSpec) throws IOException { - Log.d(LOG_TAG, "open"); - - mDataSpec = dataSpec; - return Long.MAX_VALUE; - } - - @Override - public int read(byte[] buffer, int offset, int readLength) { - Log.d(LOG_TAG, "read, readLength=" + readLength); - - if (0 == readLength) return 0; - - if (readLength > mBuffer.length()) buffer = mBuffer.getBytes(); - - return mBuffer.length(); - } - - public void write(String buffer) throws InterruptedException { - //My service calls this - mBuffer = buffer; - } - - @Nullable - @Override - public Uri getUri() { - Log.d(LOG_TAG, "getUri"); - return Uri.EMPTY; - } - - @Override - public void close() throws IOException { - Log.d(LOG_TAG, "close"); - } -} \ No newline at end of file From e519abdb359ad7fd4de53933f5a0a146bf37bf37 Mon Sep 17 00:00:00 2001 From: markusstadtmann Date: Sun, 16 May 2021 20:50:12 +0200 Subject: [PATCH 06/29] rollback --- .idea/compiler.xml | 6 ++++++ .idea/misc.xml | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/.idea/compiler.xml b/.idea/compiler.xml index e69de29..6bf4635 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index e69de29..86146da 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file From 806e25a0adafde750818db0fc3182245968d03cd Mon Sep 17 00:00:00 2001 From: markusstadtmann Date: Wed, 19 May 2021 02:44:20 +0200 Subject: [PATCH 07/29] update dvr --- .../main/java/com/fpvout/digiview/MainActivity.java | 12 +++++++----- .../com/fpvout/digiview/VideoReaderExoplayer.java | 5 +++++ app/src/main/res/values/strings.xml | 2 ++ 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/fpvout/digiview/MainActivity.java b/app/src/main/java/com/fpvout/digiview/MainActivity.java index de8cb24..c5ed890 100644 --- a/app/src/main/java/com/fpvout/digiview/MainActivity.java +++ b/app/src/main/java/com/fpvout/digiview/MainActivity.java @@ -10,7 +10,6 @@ import android.content.SharedPreferences; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; -import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.util.Log; @@ -22,13 +21,10 @@ import android.view.ViewGroup; import android.view.WindowManager; -import android.widget.ImageButton; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; import androidx.preference.PreferenceManager; import com.fpvout.digiview.dvr.DVR; -import com.uncorkedstudios.android.view.recordablesurfaceview.RecordableSurfaceView; - import java.io.IOException; import java.util.HashMap; @@ -160,7 +156,7 @@ public void onScaleEnd(ScaleGestureDetector detector) { mVideoReader = new VideoReaderExoplayer(fpvView, overlayView, this); // Init DVR recorder - recorder = DVR.getInstance(this, fpvView); + recorder = DVR.getInstance(this, mVideoReader, true); findViewById(R.id.recordbt).setOnClickListener(view -> { if (recorder.isRecording()) { recorder.stop(); @@ -181,6 +177,12 @@ public void onScaleEnd(ScaleGestureDetector detector) { } } + + try { + recorder.init(); + } catch (IOException e) { + e.printStackTrace(); + } } @Override diff --git a/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java b/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java index 8e24c4f..e01e351 100644 --- a/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java +++ b/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java @@ -55,6 +55,11 @@ public class VideoReaderExoplayer { videoReaderEventListener = v; } + public InputStreamDataSource getInputDataStream(){ + DataSpec dataSpec = new DataSpec(Uri.EMPTY,0,C.LENGTH_UNSET); + return new InputStreamDataSource(context, dataSpec, inputStream); + } + public void setUsbMaskConnection(UsbMaskConnection connection) { mUsbMaskConnection = connection; inputStream = mUsbMaskConnection.mInputStream; diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7a0bceb..1c077bb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -46,4 +46,6 @@ recording started recording stopped No stream recording could not be started + dvr has been saved + merging audio and video of the dvr \ No newline at end of file From 556beb6c8a5bf2c1fcfe10924a8f1cab3b6c0305 Mon Sep 17 00:00:00 2001 From: markusstadtmann Date: Wed, 19 May 2021 03:17:55 +0200 Subject: [PATCH 08/29] add missing files --- .../java/com/fpvout/digiview/dvr/DVR.java | 157 ++++++++++++++++++ .../helpers/ThreadPerTaskExecutor.java | 9 + app/src/main/res/drawable-v24/record.png | Bin 0 -> 1980 bytes app/src/main/res/drawable-v24/stop.png | Bin 0 -> 1039 bytes 4 files changed, 166 insertions(+) create mode 100644 app/src/main/java/com/fpvout/digiview/dvr/DVR.java create mode 100644 app/src/main/java/com/fpvout/digiview/helpers/ThreadPerTaskExecutor.java create mode 100644 app/src/main/res/drawable-v24/record.png create mode 100644 app/src/main/res/drawable-v24/stop.png diff --git a/app/src/main/java/com/fpvout/digiview/dvr/DVR.java b/app/src/main/java/com/fpvout/digiview/dvr/DVR.java new file mode 100644 index 0000000..e86f4ec --- /dev/null +++ b/app/src/main/java/com/fpvout/digiview/dvr/DVR.java @@ -0,0 +1,157 @@ +package com.fpvout.digiview.dvr; + +import android.Manifest; +import android.app.Activity; +import android.media.MediaRecorder; +import android.os.Environment; +import android.util.Log; +import android.widget.ImageButton; +import android.widget.Toast; + +import com.fpvout.digiview.InputStreamDataSource; +import com.fpvout.digiview.R; +import com.fpvout.digiview.VideoReaderExoplayer; + +import java.io.File; +import java.io.IOException; +import java.util.Date; + +import androidx.core.app.ActivityCompat; +import nl.bravobit.ffmpeg.FFcommandExecuteResponseHandler; +import nl.bravobit.ffmpeg.FFmpeg; +import nl.bravobit.ffmpeg.FFtask; + +public class DVR { + private static final int WRITE_EXTERNAL_STORAGE = 0; + private final Activity _activity; + private boolean _recordAmbientAudio; + private MediaRecorder _recorder; + private boolean recording = false; + private static DVR instance; + private static final String DVR_LOG_TAG = "DVR"; + private String defaultFolder = ""; + private FFmpeg _ffmpeg; + private FFtask _ffTask; + private String _ambietAudio; + private String _videoFile; + private String _dvrFile; + private VideoReaderExoplayer _mPlayer; + + DVR(Activity activity,VideoReaderExoplayer mPlayer, boolean recordAmbientAudio){ + _activity = activity; + _mPlayer = mPlayer; + _recordAmbientAudio = recordAmbientAudio; + defaultFolder = Environment.getExternalStorageDirectory().getAbsolutePath()+ "/" +_activity.getApplicationInfo().loadLabel(_activity.getPackageManager()).toString(); + ActivityCompat.requestPermissions(_activity, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA }, WRITE_EXTERNAL_STORAGE); + + } + + public static DVR getInstance(Activity context,VideoReaderExoplayer mPlayer, boolean recordAmbientAudio){ + if (instance == null) { + instance = new DVR(context,mPlayer, recordAmbientAudio); + } + return instance; + } + + public void init() throws IOException { + _ffmpeg = FFmpeg.getInstance(_activity); + + _recorder = new MediaRecorder(); + _recorder.setAudioSource(MediaRecorder.AudioSource.MIC); + _recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); + _recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); + } + + public void start() throws IOException { + Log.d(DVR_LOG_TAG, "start recording ..."); + Toast.makeText(_activity, _activity.getText(R.string.recording_started), Toast.LENGTH_LONG).show(); + ((ImageButton)_activity.findViewById(R.id.recordbt)).setImageResource(R.drawable.stop); + this.recording = true; + String fileName = String.valueOf(new Date().getTime()); + _ambietAudio = defaultFolder + "tmp_" + fileName + ".mp3"; + _videoFile = defaultFolder + "/tmp_" + fileName + ".mp4"; + _dvrFile = defaultFolder + "/DVR_" + fileName + ".mp4"; + + File objFolder = new File(defaultFolder); + if(!objFolder.exists()){ + objFolder.mkdir(); + } + + if (_recordAmbientAudio) { + _recorder.setOutputFile(_ambietAudio); + _recorder.prepare(); + _recorder.start(); // Ambient Audio Recording is now started + } + InputStreamDataSource inputStreamDataSource = _mPlayer.getInputDataStream(); + if (inputStreamDataSource != null) { + + } + + if (_ffmpeg.isSupported()) { + + + String RTSP_URL = "rtsp://"; + String[] ffmpegCommand = new String[]{ "-i", RTSP_URL, "-acodec", "copy", "-vcodec", "copy", _videoFile}; + _ffTask = _ffmpeg.execute( ffmpegCommand, new FFcommandExecuteResponseHandler() { + @Override + public void onStart() {} + + @Override + public void onProgress(String message) {} + + @Override + public void onFailure(String message) {} + + @Override + public void onSuccess(String message) {} + + @Override + public void onFinish() { + if (new File(_videoFile).exists() && new File(_ambietAudio).exists()) { + Toast.makeText(_activity, _activity.getString(R.string.dvr_merge_audio_video), Toast.LENGTH_LONG).show(); + _ffmpeg.execute(new String[]{"-i", _videoFile, "-i", _ambietAudio, " -shortest", _dvrFile}, new FFcommandExecuteResponseHandler() { + @Override + public void onStart() { + } + + @Override + public void onProgress(String message) { + } + + @Override + public void onFailure(String message) { + } + + @Override + public void onSuccess(String message) { + } + + @Override + public void onFinish() { + Toast.makeText(_activity, _activity.getString(R.string.saved_dvr), Toast.LENGTH_LONG).show(); + } + + }); + } + } + + } ); + } + } + + public boolean isRecording(){ + return recording; + } + + public void stop() { + Log.d(DVR_LOG_TAG, "stop recording ..."); + Toast.makeText(_activity, _activity.getText(R.string.recording_stopped), Toast.LENGTH_LONG).show(); + ((ImageButton)_activity.findViewById(R.id.recordbt)).setImageResource(R.drawable.record); + if (_recordAmbientAudio) { + _recorder.stop(); + } + _ffTask.sendQuitSignal(); + + this.recording = false; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fpvout/digiview/helpers/ThreadPerTaskExecutor.java b/app/src/main/java/com/fpvout/digiview/helpers/ThreadPerTaskExecutor.java new file mode 100644 index 0000000..9e86b2c --- /dev/null +++ b/app/src/main/java/com/fpvout/digiview/helpers/ThreadPerTaskExecutor.java @@ -0,0 +1,9 @@ +package com.fpvout.digiview.helpers; + +import java.util.concurrent.Executor; + +public class ThreadPerTaskExecutor implements Executor { + public void execute(Runnable r) { + new Thread(r).start(); + } +} diff --git a/app/src/main/res/drawable-v24/record.png b/app/src/main/res/drawable-v24/record.png new file mode 100644 index 0000000000000000000000000000000000000000..3868ddccb1505a1009098435c1d6bd14bc6c3226 GIT binary patch literal 1980 zcmV;t2SfOYP) z(Q(^25QaCIJko059`ceIj~h4_;48p!0Z9SpE5I(mF5uEYT!7*N&IQ;F`0|*CYd}^3 zH?7skr{boFxK#BzH{#+~of_U@e{Rf&E-@(l}c1jB7-3;1*cKF!u6)lJo z1)%NlF^U#MDFVpN>4(^_YxZKuT>vFHzK^#4*$N>?0hI3aKC+DpISF8nj_)I@sE~sI zYH@s*EQIjZDbb}?=kJr$2B{)|cB4bDGOc0yGXSG5)BXnkf>S^%*XoSWmjNVHE>1d;-faDFXhBLXP_#8hbR zSN#yN?G_P%gaBflUpq-f5IYPEt$>>28=?~;IwGP|F8ba1(62o4zaEL`Wli+#d_tb% z2wO%?7{%NboTc-dor6!=(PPe@$`k)Ru3rU$Le;L?0)RNbA^L-e&S>|#a6TZ*iirLp zqKEbF6@i*aM&5z*H*(SHM<2}Q-mAp%J1e0KaL5nVWM zre#S)UxbAKx3#YTk~u$P$9Ity0@UW{)j%od|3yUi1vsC$j`v(=37|={PXzE)VsyoM z2zN`n*Am$bA+Lmnk`Vh(jR5L5E;hRa@4jH?6W9Ho+oH`RpP{;d;H$qOS~R<>{@Qu7 zT&_25YT`yK$?gYG3;k@MyF&VUUp8&JQx?If@5Th6>HJN&d?-BRkFjlI6W2yj5v1N2 zvV{$DmvBNylLbeDEQEeY00))RL(tVMIaUh4hg`AQ0FfybbiYsGA9s83Wo$R zs2C|KcYaH(2z==E>5u??2vy?M%oDz;oUkoIh>ikDl0cpqnd49|l#4`@MZE&(4HW}; zE07bPIZg6NaUGZx`uM={ZJz)TKfKSaQ)0A)+9tRw=nBA5h$Os1X8ry!CxWYpL;T`n zwH$!g^FKLn7+pTGtyBmVKuE~BtbE8fl?B_%m!NK7UjVT1&x*FJe>xZ7DDczQArT<- z>NoH<^Y#rq2e7L@7<3faqLBn9nc$cSaND&4nA-n`g#V9DiGj4;R3B}94vhef&Q6^< zvtS#-Kz9LWPKl9~4bcLCs<#s05dhNv^DbQr|M4su|6R_L^8jQ7ICV;l>}-h?fYjc6 z187SVKpB)JfLv~Qo)ajWP8ERf1kjOFV#MNWQ_;<#zE}b%55%Tf>7Ep-bj(UsplpiF0W1-e17=GTKp7M%080erfZ6h`?_RR0@abABRK+V|6>w77 zr~c>vU@S~UT+AzAqn2xkR6n{Z1j07xFdd~-?+r0u-+dTOi% ze055UY-|S?0H2??blxz=SOQKXbqEnaNH_4sDKQW>^Y+cY0AL3cS`_dGef9tJb!Zn5 z8U<`bFwRQr>L<+`LRisv&KpLTJB_}-r7HmFIkjt|zsZ)05An=4@+J6;hnNTu`fTCQ z<56lr-8Ov!h&q8{48F19X5?cvauaydN7M-~eFA{9-c4}WsNj|5I?;rggaAUjf?LZ6 zwme516%+l4{~-Z5p+cJVZn(2KK}0R%&5*4P);h!EUY`yLz?UE;a>M_ubbcV#`635= z91=jUVh#{0f@h8dHHn~gerOjccCPaw0jRMjf*QJmSm%o}CV)ZZ3=kZa*dXUKPYgG0 zJBxI_=&9~gBEZ2f=pjY~ZDKgbA>vXX;S)d7|D98k7&g#VL-g;Aj+ng>80SMybwfQV zG`g~Eu+qDPZMT+al1KbWp(#YLSsXbpa4C7aq`TfZUqZm*p}z$^3J?ifyh3(fNN%v? zt3G5^uMg;X&ToFa|KJpk5Kwhj>hr4|9b3gI&!t(NL& znib!={S|N%hl-6u1wc87poNcCJ0qQA>Q=m6>jAiFL(R6@0wDB46Cbd2$`4nTdcg7% z|2+n5)bvHwt}z0j$OHwx;z+M2Z5-`AZ+}b_t%EmPvR@RZY?IhBRsd9j2nryX4JC$w zF%^IgaS9>n1tc{O5G#Tjg%I0rk?1Ko5fodYnnV!WR* zLlN{}k;(!3>1s!Qn7{XVqe&=E*g&?)W-l#T{n`o25VsD0msL_M0N zx+xSD4Ti{lG?A^EM}_QjoZJP_Qc_eXDzPm^0Q+sBJpI%W2gOx67U>+M?Yz zBo2yl>hFo3duC>JdD`*c&noK@ir;N2{yuO2=FR17#vmZPIrB>Y?o9;>XRL1U=rFZ6 zIv=o0(BB|?$f(25t-X8onG}_uR#6n6@`)A2?muKhc&shi~$` zKIZ)l%lH17c{^%-_0E6lYLy?pvTR`8!4bn|$K#$m?bO%*8{Z|L)?w1nf3}Y4^uvz5 z{-;azZ<`x61l{02aIxS>+k=d|{_E45>=jRIj1`wXa3lgV#qt*@5Rd=7JlG;=!Z1}Ug_ z*F(Mgz!mDYrTN^VzH)_;v^{yEe(Xv|l-u zQRI59{esBV;3$}~d_r_$mhO(bPs1ywM;{Dtn4XyB9QLhr=jorNoC|6;djq-OYOnv_ z5__NLm*ft~Wo9n+jODxMh0iZ%_744i_)6Q7H_vz5J-@8p@M+a2rVr5;NzNa#fvIEv ztzUI=4@^HSt(WETyLmjk)PC=p=gf-vjrrNR_3ewzyr2H6kNy6!%(mb9qa)hp2ZT11f0=%>W#0AMyRZM+sCyO)J ze6$-(pGu|*S}@75e7kC4$MEb>_-&1+FaONn^+f&a+MoAdOIyF(`}hAO)`T=LuxhAh ZoIbCxP~JKBAuyvec)I$ztaD0e0ss_H%!>d3 literal 0 HcmV?d00001 From 2f703fe68192c61f02fffbf68889b9ee2415d864 Mon Sep 17 00:00:00 2001 From: markusstadtmann Date: Thu, 20 May 2021 17:48:25 +0200 Subject: [PATCH 09/29] dvr version first --- .idea/misc.xml | 2 +- app/build.gradle | 4 + app/src/main/AndroidManifest.xml | 1 - .../digiview/InputStreamDataSource.java | 1 + .../com/fpvout/digiview/MainActivity.java | 38 ++--- .../fpvout/digiview/UsbMaskConnection.java | 6 +- .../fpvout/digiview/VideoReaderExoplayer.java | 20 ++- .../java/com/fpvout/digiview/dvr/DVR.java | 159 +++++++++--------- .../com/fpvout/digiview/helpers/Mp4Muxer.java | 142 ++++++++++++++++ .../main/java/usb/AndroidUSBInputStream.java | 7 +- 10 files changed, 266 insertions(+), 114 deletions(-) create mode 100644 app/src/main/java/com/fpvout/digiview/helpers/Mp4Muxer.java diff --git a/.idea/misc.xml b/.idea/misc.xml index 86146da..c393b28 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index de58b67..1b69d42 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -69,6 +69,10 @@ dependencies { implementation 'com.google.android.material:material:1.3.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'com.google.android.exoplayer:exoplayer:2.13.3' + implementation 'org.mp4parser:isoparser:1.9.39' + implementation 'org.mp4parser:muxer:1.9.39' + compile 'org.slf4j:slf4j-nop:1.7.25' + implementation 'org.jcodec:jcodec:0.2.5' implementation 'io.sentry:sentry-android:4.3.0' implementation 'androidx.preference:preference:1.1.1' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 15b71a4..c2f8a43 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -15,7 +15,6 @@ android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" - android:roundIcon="@mipmap/ic_launcher_alpha_round" android:largeHeap="true" android:requestLegacyExternalStorage="true" android:supportsRtl="true" diff --git a/app/src/main/java/com/fpvout/digiview/InputStreamDataSource.java b/app/src/main/java/com/fpvout/digiview/InputStreamDataSource.java index 7b10606..0746faf 100644 --- a/app/src/main/java/com/fpvout/digiview/InputStreamDataSource.java +++ b/app/src/main/java/com/fpvout/digiview/InputStreamDataSource.java @@ -3,6 +3,7 @@ import android.content.Context; import android.net.Uri; +import com.fpvout.digiview.dvr.DVR; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSpec; diff --git a/app/src/main/java/com/fpvout/digiview/MainActivity.java b/app/src/main/java/com/fpvout/digiview/MainActivity.java index c5ed890..3ceec05 100644 --- a/app/src/main/java/com/fpvout/digiview/MainActivity.java +++ b/app/src/main/java/com/fpvout/digiview/MainActivity.java @@ -153,21 +153,13 @@ public void onScaleEnd(ScaleGestureDetector detector) { watermarkView.setVisibility(View.GONE); mUsbMaskConnection = new UsbMaskConnection(); - mVideoReader = new VideoReaderExoplayer(fpvView, overlayView, this); + mVideoReader = new VideoReaderExoplayer(fpvView, overlayView, this, recorder); - // Init DVR recorder - recorder = DVR.getInstance(this, mVideoReader, true); - findViewById(R.id.recordbt).setOnClickListener(view -> { - if (recorder.isRecording()) { - recorder.stop(); - } else { - try { - recorder.start(); - } catch (IOException e) { - e.printStackTrace(); - } - } - }); + try { + recorder.init(mVideoReader); + } catch (IOException e) { + e.printStackTrace(); + } if (!usbConnected) { if (searchDevice()) { @@ -176,13 +168,6 @@ public void onScaleEnd(ScaleGestureDetector detector) { overlayView.showOpaque(R.string.waiting_for_usb_device, OverlayStatus.Disconnected); } } - - - try { - recorder.init(); - } catch (IOException e) { - e.printStackTrace(); - } } @Override @@ -343,7 +328,16 @@ private boolean searchDevice() { private void connect() { usbConnected = true; - mUsbMaskConnection.setUsbDevice(usbManager.openDevice(usbDevice), usbDevice); + // Init DVR recorder + recorder = DVR.getInstance(this, true); + findViewById(R.id.recordbt).setOnClickListener(view -> { + if (recorder.isRecording()) { + recorder.stop(); + } else { + recorder.start(); + } + }); + mUsbMaskConnection.setUsbDevice(usbManager.openDevice(usbDevice), usbDevice, recorder); mVideoReader.setUsbMaskConnection(mUsbMaskConnection); overlayView.hide(); mVideoReader.start(); diff --git a/app/src/main/java/com/fpvout/digiview/UsbMaskConnection.java b/app/src/main/java/com/fpvout/digiview/UsbMaskConnection.java index 1d6d90e..bbe2a66 100644 --- a/app/src/main/java/com/fpvout/digiview/UsbMaskConnection.java +++ b/app/src/main/java/com/fpvout/digiview/UsbMaskConnection.java @@ -4,6 +4,8 @@ import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbInterface; +import com.fpvout.digiview.dvr.DVR; + import java.io.IOException; import usb.AndroidUSBInputStream; @@ -22,7 +24,7 @@ public class UsbMaskConnection { public UsbMaskConnection() { } - public void setUsbDevice(UsbDeviceConnection c, UsbDevice d) { + public void setUsbDevice(UsbDeviceConnection c, UsbDevice d, DVR dvr) { usbConnection = c; device = d; usbInterface = device.getInterface(3); @@ -30,7 +32,7 @@ public void setUsbDevice(UsbDeviceConnection c, UsbDevice d) { usbConnection.claimInterface(usbInterface,true); mOutputStream = new AndroidUSBOutputStream(usbInterface.getEndpoint(0), usbConnection); - mInputStream = new AndroidUSBInputStream(usbInterface.getEndpoint(1), usbInterface.getEndpoint(0), usbConnection); + mInputStream = new AndroidUSBInputStream(usbInterface.getEndpoint(1), usbInterface.getEndpoint(0), usbConnection, dvr); ready = true; } diff --git a/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java b/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java index e01e351..1686a13 100644 --- a/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java +++ b/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java @@ -11,6 +11,7 @@ import androidx.constraintlayout.widget.ConstraintLayout; import androidx.preference.PreferenceManager; +import com.fpvout.digiview.dvr.DVR; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.DefaultLoadControl; import com.google.android.exoplayer2.ExoPlaybackException; @@ -43,19 +44,27 @@ public class VideoReaderExoplayer { private PerformancePreset performancePreset = PerformancePreset.getPreset(PerformancePreset.PresetType.DEFAULT); static final String VideoZoomedIn = "VideoZoomedIn"; private final SharedPreferences sharedPreferences; + private DVR dvr; + private boolean streaming = false; VideoReaderExoplayer(SurfaceView videoSurface, Context c) { surfaceView = videoSurface; - context = c; - sharedPreferences = PreferenceManager.getDefaultSharedPreferences(c); - } + context = c; + sharedPreferences = PreferenceManager.getDefaultSharedPreferences(c); + this.dvr = dvr; + } VideoReaderExoplayer(SurfaceView videoSurface, Context c, Handler v) { this(videoSurface, c); videoReaderEventListener = v; } - public InputStreamDataSource getInputDataStream(){ + + public boolean isStreaming(){ + return streaming; + } + + private InputStreamDataSource getInputDataStream(){ DataSpec dataSpec = new DataSpec(Uri.EMPTY,0,C.LENGTH_UNSET); return new InputStreamDataSource(context, dataSpec, inputStream); } @@ -102,6 +111,7 @@ public void start() { public void onPlayerError(ExoPlaybackException error) { switch (error.type) { case ExoPlaybackException.TYPE_SOURCE: + streaming = false; Log.e(TAG, "PLAYER_SOURCE - TYPE_SOURCE: " + error.getSourceException().getMessage()); (new Handler(Looper.getMainLooper())).postDelayed(() -> restart(), 1000); break; @@ -123,11 +133,13 @@ public void onPlaybackStateChanged(@NonNullApi int state) { case Player.STATE_IDLE: case Player.STATE_READY: case Player.STATE_BUFFERING: + streaming = false; break; case Player.STATE_ENDED: Log.d(TAG, "PLAYER_STATE - ENDED"); sendEvent(VideoReaderEventMessageCode.WAITING_FOR_VIDEO); // let MainActivity know so it can hide watermark/show settings button (new Handler(Looper.getMainLooper())).postDelayed(() -> restart(), 1000); + streaming = false; break; } } diff --git a/app/src/main/java/com/fpvout/digiview/dvr/DVR.java b/app/src/main/java/com/fpvout/digiview/dvr/DVR.java index e86f4ec..35206b7 100644 --- a/app/src/main/java/com/fpvout/digiview/dvr/DVR.java +++ b/app/src/main/java/com/fpvout/digiview/dvr/DVR.java @@ -4,6 +4,7 @@ import android.app.Activity; import android.media.MediaRecorder; import android.os.Environment; +import android.os.Looper; import android.util.Log; import android.widget.ImageButton; import android.widget.Toast; @@ -11,15 +12,26 @@ import com.fpvout.digiview.InputStreamDataSource; import com.fpvout.digiview.R; import com.fpvout.digiview.VideoReaderExoplayer; - +import com.fpvout.digiview.helpers.Mp4Muxer; +import com.fpvout.digiview.helpers.ThreadPerTaskExecutor; + +import org.mp4parser.Container; +import org.mp4parser.muxer.DataSource; +import org.mp4parser.muxer.FileDataSourceImpl; +import org.mp4parser.muxer.Movie; +import org.mp4parser.muxer.builder.DefaultMp4Builder; +import org.mp4parser.muxer.tracks.AACTrackImpl; +import org.mp4parser.muxer.tracks.h264.H264TrackImpl; + +import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStream; import java.util.Date; import androidx.core.app.ActivityCompat; -import nl.bravobit.ffmpeg.FFcommandExecuteResponseHandler; -import nl.bravobit.ffmpeg.FFmpeg; -import nl.bravobit.ffmpeg.FFtask; public class DVR { private static final int WRITE_EXTERNAL_STORAGE = 0; @@ -30,112 +42,87 @@ public class DVR { private static DVR instance; private static final String DVR_LOG_TAG = "DVR"; private String defaultFolder = ""; - private FFmpeg _ffmpeg; - private FFtask _ffTask; private String _ambietAudio; private String _videoFile; private String _dvrFile; private VideoReaderExoplayer _mPlayer; + private File dvrTmpFile; + private String fileName; - DVR(Activity activity,VideoReaderExoplayer mPlayer, boolean recordAmbientAudio){ + DVR(Activity activity, boolean recordAmbientAudio){ _activity = activity; - _mPlayer = mPlayer; _recordAmbientAudio = recordAmbientAudio; defaultFolder = Environment.getExternalStorageDirectory().getAbsolutePath()+ "/" +_activity.getApplicationInfo().loadLabel(_activity.getPackageManager()).toString(); ActivityCompat.requestPermissions(_activity, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA }, WRITE_EXTERNAL_STORAGE); } - public static DVR getInstance(Activity context,VideoReaderExoplayer mPlayer, boolean recordAmbientAudio){ + public static DVR getInstance(Activity context, boolean recordAmbientAudio){ if (instance == null) { - instance = new DVR(context,mPlayer, recordAmbientAudio); + instance = new DVR(context, recordAmbientAudio); } return instance; } - public void init() throws IOException { - _ffmpeg = FFmpeg.getInstance(_activity); + public void init(VideoReaderExoplayer mPlayer) throws IOException { + this._mPlayer = mPlayer; _recorder = new MediaRecorder(); _recorder.setAudioSource(MediaRecorder.AudioSource.MIC); - _recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); - _recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); + _recorder.setOutputFormat(MediaRecorder.OutputFormat.AAC_ADTS); + _recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); } - public void start() throws IOException { - Log.d(DVR_LOG_TAG, "start recording ..."); - Toast.makeText(_activity, _activity.getText(R.string.recording_started), Toast.LENGTH_LONG).show(); - ((ImageButton)_activity.findViewById(R.id.recordbt)).setImageResource(R.drawable.stop); - this.recording = true; - String fileName = String.valueOf(new Date().getTime()); - _ambietAudio = defaultFolder + "tmp_" + fileName + ".mp3"; - _videoFile = defaultFolder + "/tmp_" + fileName + ".mp4"; - _dvrFile = defaultFolder + "/DVR_" + fileName + ".mp4"; - - File objFolder = new File(defaultFolder); - if(!objFolder.exists()){ - objFolder.mkdir(); - } - - if (_recordAmbientAudio) { - _recorder.setOutputFile(_ambietAudio); - _recorder.prepare(); - _recorder.start(); // Ambient Audio Recording is now started - } - InputStreamDataSource inputStreamDataSource = _mPlayer.getInputDataStream(); - if (inputStreamDataSource != null) { - + public void recordVideoDVR(byte[] buffer, int offset, int readLength) { + if (isRecording()) { + try { + if (dvrTmpFile != null) { + FileOutputStream dvrOutputStream = new FileOutputStream(dvrTmpFile); + dvrOutputStream.write(buffer, offset, readLength); + dvrOutputStream.flush(); + dvrOutputStream.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } } + } - if (_ffmpeg.isSupported()) { - - - String RTSP_URL = "rtsp://"; - String[] ffmpegCommand = new String[]{ "-i", RTSP_URL, "-acodec", "copy", "-vcodec", "copy", _videoFile}; - _ffTask = _ffmpeg.execute( ffmpegCommand, new FFcommandExecuteResponseHandler() { - @Override - public void onStart() {} - - @Override - public void onProgress(String message) {} - - @Override - public void onFailure(String message) {} - - @Override - public void onSuccess(String message) {} - - @Override - public void onFinish() { - if (new File(_videoFile).exists() && new File(_ambietAudio).exists()) { - Toast.makeText(_activity, _activity.getString(R.string.dvr_merge_audio_video), Toast.LENGTH_LONG).show(); - _ffmpeg.execute(new String[]{"-i", _videoFile, "-i", _ambietAudio, " -shortest", _dvrFile}, new FFcommandExecuteResponseHandler() { - @Override - public void onStart() { - } - - @Override - public void onProgress(String message) { - } - - @Override - public void onFailure(String message) { - } - - @Override - public void onSuccess(String message) { - } + public void start() { + if ( _mPlayer.isStreaming()) { + this.recording = true; + fileName = String.valueOf(new Date().getTime()); + _ambietAudio = defaultFolder + "/tmp_" + fileName + ".aac"; + _videoFile = defaultFolder + "/tmp_" + fileName + ".h264"; + _dvrFile = defaultFolder + "/DVR_" + fileName + ".mp4"; + + Log.d(DVR_LOG_TAG, "start recording ..."); + Toast.makeText(_activity, _activity.getText(R.string.recording_started), Toast.LENGTH_LONG).show(); + ((ImageButton)_activity.findViewById(R.id.recordbt)).setImageResource(R.drawable.stop); + + ThreadPerTaskExecutor threadPerTaskExecutor = new ThreadPerTaskExecutor(); + threadPerTaskExecutor.execute(() -> { + Log.d(DVR_LOG_TAG, "creating folder for dvr saving ..."); + File objFolder = new File(defaultFolder); + if(!objFolder.exists()){ + objFolder.mkdir(); + } - @Override - public void onFinish() { - Toast.makeText(_activity, _activity.getString(R.string.saved_dvr), Toast.LENGTH_LONG).show(); - } + dvrTmpFile = new File(_videoFile); - }); + if (_recordAmbientAudio) { + Log.d(DVR_LOG_TAG, "starting abient recording ..."); + _recorder.setOutputFile(_ambietAudio); + try { + _recorder.prepare(); + _recorder.start(); // Ambient Audio Recording is now started + } catch (IOException e) { + e.printStackTrace(); } } - - } ); + }); + } else { + Toast.makeText(_activity, "Stream not ready", Toast.LENGTH_LONG).show(); } } @@ -147,10 +134,16 @@ public void stop() { Log.d(DVR_LOG_TAG, "stop recording ..."); Toast.makeText(_activity, _activity.getText(R.string.recording_stopped), Toast.LENGTH_LONG).show(); ((ImageButton)_activity.findViewById(R.id.recordbt)).setImageResource(R.drawable.record); + if (_recordAmbientAudio) { _recorder.stop(); + + Toast.makeText(_activity, _activity.getString(R.string.dvr_merge_audio_video), Toast.LENGTH_LONG).show(); + Mp4Muxer muxer = new Mp4Muxer(new File(_videoFile), _ambietAudio, _dvrFile); + muxer.start(); + } else { + new File(_videoFile).renameTo( new File(_dvrFile)); // No Ambient recording just dvr } - _ffTask.sendQuitSignal(); this.recording = false; } diff --git a/app/src/main/java/com/fpvout/digiview/helpers/Mp4Muxer.java b/app/src/main/java/com/fpvout/digiview/helpers/Mp4Muxer.java new file mode 100644 index 0000000..753a41c --- /dev/null +++ b/app/src/main/java/com/fpvout/digiview/helpers/Mp4Muxer.java @@ -0,0 +1,142 @@ +package com.fpvout.digiview.helpers; + + +import org.jcodec.codecs.h264.BufferH264ES; +import org.jcodec.codecs.h264.H264Decoder; +import org.jcodec.common.Codec; +import org.jcodec.common.MuxerTrack; +import org.jcodec.common.VideoCodecMeta; +import org.jcodec.common.io.NIOUtils; +import org.jcodec.common.io.SeekableByteChannel; +import org.jcodec.common.model.Packet; +import org.jcodec.containers.mp4.muxer.MP4Muxer; +import org.mp4parser.Container; +import org.mp4parser.muxer.DataSource; +import org.mp4parser.muxer.FileDataSourceImpl; +import org.mp4parser.muxer.Movie; +import org.mp4parser.muxer.builder.DefaultMp4Builder; +import org.mp4parser.muxer.tracks.AACTrackImpl; +import org.mp4parser.muxer.tracks.h264.H264TrackImpl; + +import android.util.Log; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; + +public class Mp4Muxer extends Thread { + + private static final int TIMESCALE = 60; + private static final long DURATION = 1; + + private final File h264Dump; + private final File muxerfile; + private final String ambientAudioPath; + private final String finalDVROutputPath; + + SeekableByteChannel file; + MP4Muxer muxer; + BufferH264ES es; + + public Mp4Muxer(File h264Dump, String ambientAudioPath, String finalDVROutputPath) { + this.h264Dump = h264Dump; + this.muxerfile = new File(finalDVROutputPath + ".tmp"); + this.ambientAudioPath = ambientAudioPath; + this.finalDVROutputPath = finalDVROutputPath; + } + + private void init() throws IOException { + file = NIOUtils.writableChannel(muxerfile); + muxer = MP4Muxer.createMP4MuxerToChannel(file); + + es = new BufferH264ES(NIOUtils.mapFile(h264Dump)); + } + + + private MuxerTrack initVideoTrack(Packet frame){ + VideoCodecMeta md = new H264Decoder().getCodecMeta(frame.getData()); + return muxer.addVideoTrack(Codec.H264, md); + } + + private Packet skipToFirstValidFrame(){ + return nextValidFrame(null, null); + } + + /** + * Seek next valid frame. + * For every invalid frame, insert placeholder frame into track + */ + private Packet nextValidFrame(Packet placeholder, MuxerTrack track){ + Packet frame = null; + // drop invalid frames + while (frame == null) { + try{ + frame = es.nextFrame(); + if(frame == null){ + return null; // end of input + } + }catch (Exception ignore){ + try { + if(track != null){ + track.addFrame(placeholder); + } + } catch (IOException ignored) { } + // invalid frames can cause a variety of exceptions on read + // continue + } + } + return frame; + } + + @Override + public void run() { + try{ + + init(); + + Packet frame = skipToFirstValidFrame(); + + MuxerTrack track = null; + while (frame != null) { + if (track == null) { + track = initVideoTrack(frame); + } + + frame.setTimescale(TIMESCALE); + frame.setDuration(DURATION); + track.addFrame(frame); + + frame = nextValidFrame(frame, track); + } + muxer.finish(); + + file.close(); + // cleanup + h264Dump.delete(); + + try { + DataSource videoFile = new FileDataSourceImpl(muxerfile); + AACTrackImpl aacTrack = new AACTrackImpl(new FileDataSourceImpl(ambientAudioPath)); + + H264TrackImpl h264Track = new H264TrackImpl(videoFile); + Movie movie = new Movie(); + movie.addTrack(h264Track); + movie.addTrack(aacTrack); + + Container out = new DefaultMp4Builder().build(movie); + FileOutputStream fos = new FileOutputStream(new File(finalDVROutputPath)); + out.writeContainer(fos.getChannel()); + fos.close(); + + muxerfile.delete(); + new File(ambientAudioPath).delete(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } catch (IOException exception){ + Log.e("DIGIVIEW", "MUXER: " + exception.getMessage()); + } + } +} diff --git a/app/src/main/java/usb/AndroidUSBInputStream.java b/app/src/main/java/usb/AndroidUSBInputStream.java index 84fdd24..a8add90 100644 --- a/app/src/main/java/usb/AndroidUSBInputStream.java +++ b/app/src/main/java/usb/AndroidUSBInputStream.java @@ -22,6 +22,8 @@ import android.hardware.usb.UsbEndpoint; import android.util.Log; +import com.fpvout.digiview.dvr.DVR; + /** * This class acts as a wrapper to read data from the USB Interface in Android * behaving like an {@code InputputStream} class. @@ -42,6 +44,8 @@ public class AndroidUSBInputStream extends InputStream { private boolean working = false; + private DVR dvr; + /** * Class constructor. Instantiates a new {@code AndroidUSBInputStream} * object with the given parameters. @@ -52,9 +56,10 @@ public class AndroidUSBInputStream extends InputStream { * @see UsbDeviceConnection * @see UsbEndpoint */ - public AndroidUSBInputStream( UsbEndpoint readEndpoint, UsbEndpoint sendEndpoint, UsbDeviceConnection connection) { + public AndroidUSBInputStream( UsbEndpoint readEndpoint, UsbEndpoint sendEndpoint, UsbDeviceConnection connection, DVR dvr) { this.usbConnection = connection; this.receiveEndPoint = readEndpoint; + this.dvr = dvr; this.sendEndPoint = sendEndpoint; } From 879ddb18d5391c54c209224c49e4a463d2b93ba3 Mon Sep 17 00:00:00 2001 From: markusstadtmann Date: Thu, 20 May 2021 17:49:30 +0200 Subject: [PATCH 10/29] moved init --- .../main/java/com/fpvout/digiview/MainActivity.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/fpvout/digiview/MainActivity.java b/app/src/main/java/com/fpvout/digiview/MainActivity.java index 3ceec05..f20d4f2 100644 --- a/app/src/main/java/com/fpvout/digiview/MainActivity.java +++ b/app/src/main/java/com/fpvout/digiview/MainActivity.java @@ -155,12 +155,6 @@ public void onScaleEnd(ScaleGestureDetector detector) { mUsbMaskConnection = new UsbMaskConnection(); mVideoReader = new VideoReaderExoplayer(fpvView, overlayView, this, recorder); - try { - recorder.init(mVideoReader); - } catch (IOException e) { - e.printStackTrace(); - } - if (!usbConnected) { if (searchDevice()) { connect(); @@ -337,6 +331,12 @@ private void connect() { recorder.start(); } }); + + try { + recorder.init(mVideoReader); + } catch (IOException e) { + e.printStackTrace(); + } mUsbMaskConnection.setUsbDevice(usbManager.openDevice(usbDevice), usbDevice, recorder); mVideoReader.setUsbMaskConnection(mUsbMaskConnection); overlayView.hide(); From 2fb45ad76af48b5c237171c7c491acfbbfe51a49 Mon Sep 17 00:00:00 2001 From: markusstadtmann Date: Fri, 21 May 2021 23:16:26 +0200 Subject: [PATCH 11/29] updated dvr branch --- .../com/fpvout/digiview/MainActivity.java | 20 +++++++++++-------- .../java/com/fpvout/digiview/dvr/DVR.java | 8 +++----- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/fpvout/digiview/MainActivity.java b/app/src/main/java/com/fpvout/digiview/MainActivity.java index f20d4f2..fa8e5c2 100644 --- a/app/src/main/java/com/fpvout/digiview/MainActivity.java +++ b/app/src/main/java/com/fpvout/digiview/MainActivity.java @@ -77,6 +77,18 @@ protected void onCreate(Bundle savedInstanceState) { actionBar.hide(); } + findViewById(R.id.recordbt).setOnClickListener(view -> { + if (recorder != null) { + if (recorder.isRecording()) { + recorder.stop(); + } else { + recorder.start(); + } + } else { + + } + }); + // Prevent screen from sleeping getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); @@ -324,14 +336,6 @@ private void connect() { usbConnected = true; // Init DVR recorder recorder = DVR.getInstance(this, true); - findViewById(R.id.recordbt).setOnClickListener(view -> { - if (recorder.isRecording()) { - recorder.stop(); - } else { - recorder.start(); - } - }); - try { recorder.init(mVideoReader); } catch (IOException e) { diff --git a/app/src/main/java/com/fpvout/digiview/dvr/DVR.java b/app/src/main/java/com/fpvout/digiview/dvr/DVR.java index 35206b7..648ba2d 100644 --- a/app/src/main/java/com/fpvout/digiview/dvr/DVR.java +++ b/app/src/main/java/com/fpvout/digiview/dvr/DVR.java @@ -32,6 +32,7 @@ import java.util.Date; import androidx.core.app.ActivityCompat; +import usb.AndroidUSBOutputStream; public class DVR { private static final int WRITE_EXTERNAL_STORAGE = 0; @@ -48,6 +49,7 @@ public class DVR { private VideoReaderExoplayer _mPlayer; private File dvrTmpFile; private String fileName; + private AndroidUSBOutputStream dvrOutputStream; DVR(Activity activity, boolean recordAmbientAudio){ _activity = activity; @@ -77,12 +79,9 @@ public void recordVideoDVR(byte[] buffer, int offset, int readLength) { if (isRecording()) { try { if (dvrTmpFile != null) { - FileOutputStream dvrOutputStream = new FileOutputStream(dvrTmpFile); dvrOutputStream.write(buffer, offset, readLength); - dvrOutputStream.flush(); - dvrOutputStream.close(); } - } catch (IOException e) { + } catch (Exception e) { e.printStackTrace(); } } @@ -109,7 +108,6 @@ public void start() { } dvrTmpFile = new File(_videoFile); - if (_recordAmbientAudio) { Log.d(DVR_LOG_TAG, "starting abient recording ..."); _recorder.setOutputFile(_ambietAudio); From 5b29b5eb7729cf0c85dcae43c6ed37ed43ad8aa4 Mon Sep 17 00:00:00 2001 From: markusstadtmann Date: Fri, 21 May 2021 23:44:42 +0200 Subject: [PATCH 12/29] rebased to new dev branch --- .../com/fpvout/digiview/MainActivity.java | 38 ++++++++++++------- .../fpvout/digiview/VideoReaderExoplayer.java | 2 +- app/src/main/res/values/strings.xml | 2 - 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/fpvout/digiview/MainActivity.java b/app/src/main/java/com/fpvout/digiview/MainActivity.java index fa8e5c2..201b71f 100644 --- a/app/src/main/java/com/fpvout/digiview/MainActivity.java +++ b/app/src/main/java/com/fpvout/digiview/MainActivity.java @@ -11,6 +11,7 @@ import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; import android.os.Bundle; +import android.preference.PreferenceManager; import android.os.Handler; import android.util.Log; import android.view.GestureDetector; @@ -23,13 +24,13 @@ import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; -import androidx.preference.PreferenceManager; +import io.sentry.SentryLevel; +import io.sentry.android.core.SentryAndroid; + import com.fpvout.digiview.dvr.DVR; import java.io.IOException; import java.util.HashMap; -import io.sentry.SentryLevel; -import io.sentry.android.core.SentryAndroid; import static com.fpvout.digiview.VideoReaderExoplayer.VideoZoomedIn; @@ -41,6 +42,7 @@ public class MainActivity extends AppCompatActivity implements UsbDeviceListener private int shortAnimationDuration; private float buttonAlpha = 1; private View settingsButton; + private View recordButton; private View watermarkView; private OverlayView overlayView; PendingIntent permissionIntent; @@ -114,6 +116,7 @@ public void onClick(View v) { v.getContext().startActivity(intent); } }); + recordButton = findViewById(R.id.recordbt); sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); @@ -140,7 +143,7 @@ private void setupGestureDetectors() { gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() { @Override public boolean onSingleTapConfirmed(MotionEvent e) { - toggleSettingsButton(); + toggleButton(); return super.onSingleTapConfirmed(e); } @@ -165,7 +168,7 @@ public void onScaleEnd(ScaleGestureDetector detector) { watermarkView.setVisibility(View.GONE); mUsbMaskConnection = new UsbMaskConnection(); - mVideoReader = new VideoReaderExoplayer(fpvView, overlayView, this, recorder); + mVideoReader = new VideoReaderExoplayer(fpvView, overlayView, this); if (!usbConnected) { if (searchDevice()) { @@ -256,11 +259,11 @@ private void showSettingsButton() { } } - private void toggleSettingsButton() { + private void toggleButton() { if (buttonAlpha == 1 && overlayView.getVisibility() == View.VISIBLE) return; - // cancel any pending delayed animations first cancelButtonAnimation(); + recordButton.getHandler().removeCallbacksAndMessages(null); if (buttonAlpha == 1) { buttonAlpha = 0; @@ -277,6 +280,10 @@ public void onAnimationEnd(Animator animation) { autoHideSettingsButton(); } }); + + recordButton.animate() + .alpha(buttonAlpha) + .setDuration(shortAnimationDuration); } private void autoHideSettingsButton() { @@ -292,6 +299,16 @@ public void run() { .setDuration(shortAnimationDuration); } }, 3000); + + recordButton.postDelayed(new Runnable() { + @Override + public void run() { + buttonAlpha = 0; + recordButton.animate() + .alpha(0) + .setDuration(shortAnimationDuration); + } + }, 3000); } @Override @@ -377,15 +394,10 @@ public void onResume() { } settingsButton.setAlpha(1); + recordButton.setAlpha(1); autoHideSettingsButton(); updateWatermark(); updateVideoZoom(); - - try { - recorder.init(); - } catch (IOException e) { - e.printStackTrace(); - } } private boolean onVideoReaderEvent(VideoReaderExoplayer.VideoReaderEventMessageCode m) { diff --git a/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java b/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java index 1686a13..8922092 100644 --- a/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java +++ b/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java @@ -5,11 +5,11 @@ import android.net.Uri; import android.os.Handler; import android.os.Looper; +import android.preference.PreferenceManager; import android.os.Message; import android.util.Log; import android.view.SurfaceView; import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.preference.PreferenceManager; import com.fpvout.digiview.dvr.DVR; import com.google.android.exoplayer2.C; diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1c077bb..d927485 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -40,8 +40,6 @@ Copyright Open-Source License MIT License - - DigiView Connect recording started recording stopped From 42d04b924762633dca94a34c84afdd2ed735a21e Mon Sep 17 00:00:00 2001 From: markusstadtmann Date: Sat, 22 May 2021 02:55:39 +0200 Subject: [PATCH 13/29] first version of dvr implementation. work in progress --- .../com/fpvout/digiview/MainActivity.java | 64 ++++++-- .../java/com/fpvout/digiview/dvr/DVR.java | 145 ++++++++---------- .../com/fpvout/digiview/helpers/Mp4Muxer.java | 73 +++++---- .../fpvout/digiview/helpers/StreamDumper.java | 70 +++++++++ .../helpers/ThreadPerTaskExecutor.java | 9 -- .../main/java/usb/AndroidUSBInputStream.java | 3 + app/src/main/res/layout/activity_main.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- 8 files changed, 229 insertions(+), 139 deletions(-) create mode 100644 app/src/main/java/com/fpvout/digiview/helpers/StreamDumper.java delete mode 100644 app/src/main/java/com/fpvout/digiview/helpers/ThreadPerTaskExecutor.java diff --git a/app/src/main/java/com/fpvout/digiview/MainActivity.java b/app/src/main/java/com/fpvout/digiview/MainActivity.java index 201b71f..a547d3e 100644 --- a/app/src/main/java/com/fpvout/digiview/MainActivity.java +++ b/app/src/main/java/com/fpvout/digiview/MainActivity.java @@ -1,5 +1,6 @@ package com.fpvout.digiview; +import android.Manifest; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.LayoutTransition; @@ -8,8 +9,10 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; +import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; import android.os.Handler; @@ -21,9 +24,12 @@ import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; +import android.widget.Toast; +import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; import io.sentry.SentryLevel; import io.sentry.android.core.SentryAndroid; @@ -79,7 +85,8 @@ protected void onCreate(Bundle savedInstanceState) { actionBar.hide(); } - findViewById(R.id.recordbt).setOnClickListener(view -> { + recordButton = findViewById(R.id.recordbt); + recordButton.setOnClickListener(view -> { if (recorder != null) { if (recorder.isRecording()) { recorder.stop(); @@ -87,7 +94,7 @@ protected void onCreate(Bundle savedInstanceState) { recorder.start(); } } else { - + Toast.makeText(this, this.getText(R.string.no_dvr_video), Toast.LENGTH_LONG).show(); } }); @@ -116,7 +123,6 @@ public void onClick(View v) { v.getContext().startActivity(intent); } }); - recordButton = findViewById(R.id.recordbt); sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); @@ -384,6 +390,20 @@ public void onResume() { actionBar.hide(); } + + settingsButton.setAlpha(1); + recordButton.setAlpha(1); + autoHideSettingsButton(); + updateWatermark(); + updateVideoZoom(); + + if(checkStoragePermission()){ + // permission already granted + finishStartup(); + } + } + + private void finishStartup(){ if (!usbConnected) { if (searchDevice()) { Log.d(TAG, "APP - On Resume usbDevice device found"); @@ -392,12 +412,39 @@ public void onResume() { showOverlay(R.string.waiting_for_usb_device, OverlayStatus.Connected); } } + } - settingsButton.setAlpha(1); - recordButton.setAlpha(1); - autoHideSettingsButton(); - updateWatermark(); - updateVideoZoom(); + private boolean checkStoragePermission() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) + == PackageManager.PERMISSION_GRANTED && + checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) + == PackageManager.PERMISSION_GRANTED && + checkSelfPermission(Manifest.permission.RECORD_AUDIO) + == PackageManager.PERMISSION_GRANTED) { + return true; + + }else{ + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA }, 1); + return false; + } + } + return true; + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + + if(requestCode == 1){ + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + + finishStartup(); + } + else { + overlayView.showOpaque("Storage access is required.", OverlayStatus.Error); + } + } } private boolean onVideoReaderEvent(VideoReaderExoplayer.VideoReaderEventMessageCode m) { @@ -491,5 +538,4 @@ private void checkDataCollectionAgreement() { } } - } \ No newline at end of file diff --git a/app/src/main/java/com/fpvout/digiview/dvr/DVR.java b/app/src/main/java/com/fpvout/digiview/dvr/DVR.java index 648ba2d..99d19a2 100644 --- a/app/src/main/java/com/fpvout/digiview/dvr/DVR.java +++ b/app/src/main/java/com/fpvout/digiview/dvr/DVR.java @@ -1,62 +1,43 @@ package com.fpvout.digiview.dvr; -import android.Manifest; import android.app.Activity; import android.media.MediaRecorder; import android.os.Environment; -import android.os.Looper; import android.util.Log; import android.widget.ImageButton; import android.widget.Toast; -import com.fpvout.digiview.InputStreamDataSource; import com.fpvout.digiview.R; import com.fpvout.digiview.VideoReaderExoplayer; -import com.fpvout.digiview.helpers.Mp4Muxer; -import com.fpvout.digiview.helpers.ThreadPerTaskExecutor; - -import org.mp4parser.Container; -import org.mp4parser.muxer.DataSource; -import org.mp4parser.muxer.FileDataSourceImpl; -import org.mp4parser.muxer.Movie; -import org.mp4parser.muxer.builder.DefaultMp4Builder; -import org.mp4parser.muxer.tracks.AACTrackImpl; -import org.mp4parser.muxer.tracks.h264.H264TrackImpl; - -import java.io.ByteArrayOutputStream; +import com.fpvout.digiview.helpers.StreamDumper; + import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.OutputStream; -import java.util.Date; - -import androidx.core.app.ActivityCompat; -import usb.AndroidUSBOutputStream; +import java.text.SimpleDateFormat; +import java.util.Calendar; public class DVR { private static final int WRITE_EXTERNAL_STORAGE = 0; - private final Activity _activity; - private boolean _recordAmbientAudio; - private MediaRecorder _recorder; + private final Activity activity; + private boolean recordAmbientAudio; + private MediaRecorder recorder; private boolean recording = false; private static DVR instance; private static final String DVR_LOG_TAG = "DVR"; private String defaultFolder = ""; - private String _ambietAudio; - private String _videoFile; - private String _dvrFile; - private VideoReaderExoplayer _mPlayer; + private String ambietAudio; + private String videoFile; + private String dvrFile; + private VideoReaderExoplayer mPlayer; private File dvrTmpFile; private String fileName; - private AndroidUSBOutputStream dvrOutputStream; + private StreamDumper streamDumper; DVR(Activity activity, boolean recordAmbientAudio){ - _activity = activity; - _recordAmbientAudio = recordAmbientAudio; - defaultFolder = Environment.getExternalStorageDirectory().getAbsolutePath()+ "/" +_activity.getApplicationInfo().loadLabel(_activity.getPackageManager()).toString(); - ActivityCompat.requestPermissions(_activity, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA }, WRITE_EXTERNAL_STORAGE); - + this.activity = activity; + this.recordAmbientAudio = recordAmbientAudio; + defaultFolder = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + "/" + this.activity.getApplicationInfo().loadLabel(this.activity.getPackageManager()).toString(); + streamDumper = new StreamDumper(activity, defaultFolder); } public static DVR getInstance(Activity context, boolean recordAmbientAudio){ @@ -67,60 +48,55 @@ public static DVR getInstance(Activity context, boolean recordAmbientAudio){ } public void init(VideoReaderExoplayer mPlayer) throws IOException { - this._mPlayer = mPlayer; + this.mPlayer = mPlayer; - _recorder = new MediaRecorder(); - _recorder.setAudioSource(MediaRecorder.AudioSource.MIC); - _recorder.setOutputFormat(MediaRecorder.OutputFormat.AAC_ADTS); - _recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); + recorder = new MediaRecorder(); + recorder.setAudioSource(MediaRecorder.AudioSource.MIC); + recorder.setOutputFormat(MediaRecorder.OutputFormat.AAC_ADTS); + recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); } public void recordVideoDVR(byte[] buffer, int offset, int readLength) { if (isRecording()) { - try { - if (dvrTmpFile != null) { - dvrOutputStream.write(buffer, offset, readLength); - } - } catch (Exception e) { - e.printStackTrace(); - } + streamDumper.dump( buffer, offset, readLength); } } public void start() { - if ( _mPlayer.isStreaming()) { + if ( mPlayer.isStreaming()) { this.recording = true; - fileName = String.valueOf(new Date().getTime()); - _ambietAudio = defaultFolder + "/tmp_" + fileName + ".aac"; - _videoFile = defaultFolder + "/tmp_" + fileName + ".h264"; - _dvrFile = defaultFolder + "/DVR_" + fileName + ".mp4"; + fileName = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss") + .format(Calendar.getInstance().getTime()); + ambietAudio = "/DigiView_" + fileName + ".aac"; + videoFile ="/DigiView_"+fileName+".h264"; + dvrFile = "/DigiView_" + fileName + ".mp4"; + + Log.d(DVR_LOG_TAG, "creating folder for dvr saving ..."); + File objFolder = new File(defaultFolder); + if (!objFolder.exists()) + objFolder.mkdir(); + Log.d(DVR_LOG_TAG, "start recording ..."); - Toast.makeText(_activity, _activity.getText(R.string.recording_started), Toast.LENGTH_LONG).show(); - ((ImageButton)_activity.findViewById(R.id.recordbt)).setImageResource(R.drawable.stop); - - ThreadPerTaskExecutor threadPerTaskExecutor = new ThreadPerTaskExecutor(); - threadPerTaskExecutor.execute(() -> { - Log.d(DVR_LOG_TAG, "creating folder for dvr saving ..."); - File objFolder = new File(defaultFolder); - if(!objFolder.exists()){ - objFolder.mkdir(); - } + streamDumper.init(videoFile, ambietAudio, dvrFile); - dvrTmpFile = new File(_videoFile); - if (_recordAmbientAudio) { - Log.d(DVR_LOG_TAG, "starting abient recording ..."); - _recorder.setOutputFile(_ambietAudio); - try { - _recorder.prepare(); - _recorder.start(); // Ambient Audio Recording is now started - } catch (IOException e) { - e.printStackTrace(); - } + Toast.makeText(activity, activity.getText(R.string.recording_started), Toast.LENGTH_LONG).show(); + ((ImageButton) activity.findViewById(R.id.recordbt)).setImageResource(R.drawable.stop); + + + + if (recordAmbientAudio) { + Log.d(DVR_LOG_TAG, "starting ambient recording ..."); + recorder.setOutputFile(defaultFolder + ambietAudio); + try { + recorder.prepare(); + recorder.start(); // Ambient Audio Recording is now started + } catch (IOException e) { + e.printStackTrace(); } - }); + } } else { - Toast.makeText(_activity, "Stream not ready", Toast.LENGTH_LONG).show(); + Toast.makeText(activity, "Stream not ready", Toast.LENGTH_LONG).show(); } } @@ -130,19 +106,18 @@ public boolean isRecording(){ public void stop() { Log.d(DVR_LOG_TAG, "stop recording ..."); - Toast.makeText(_activity, _activity.getText(R.string.recording_stopped), Toast.LENGTH_LONG).show(); - ((ImageButton)_activity.findViewById(R.id.recordbt)).setImageResource(R.drawable.record); - - if (_recordAmbientAudio) { - _recorder.stop(); + Toast.makeText(activity, activity.getText(R.string.recording_stopped), Toast.LENGTH_LONG).show(); + ((ImageButton) activity.findViewById(R.id.recordbt)).setImageResource(R.drawable.record); - Toast.makeText(_activity, _activity.getString(R.string.dvr_merge_audio_video), Toast.LENGTH_LONG).show(); - Mp4Muxer muxer = new Mp4Muxer(new File(_videoFile), _ambietAudio, _dvrFile); - muxer.start(); - } else { - new File(_videoFile).renameTo( new File(_dvrFile)); // No Ambient recording just dvr + if (recordAmbientAudio) { + recorder.stop(); + try { + init(mPlayer); + } catch (IOException e) { + e.printStackTrace(); + } } - + streamDumper.stop(); this.recording = false; } } \ No newline at end of file diff --git a/app/src/main/java/com/fpvout/digiview/helpers/Mp4Muxer.java b/app/src/main/java/com/fpvout/digiview/helpers/Mp4Muxer.java index 753a41c..6f55ad8 100644 --- a/app/src/main/java/com/fpvout/digiview/helpers/Mp4Muxer.java +++ b/app/src/main/java/com/fpvout/digiview/helpers/Mp4Muxer.java @@ -11,18 +11,23 @@ import org.jcodec.common.model.Packet; import org.jcodec.containers.mp4.muxer.MP4Muxer; import org.mp4parser.Container; -import org.mp4parser.muxer.DataSource; import org.mp4parser.muxer.FileDataSourceImpl; import org.mp4parser.muxer.Movie; import org.mp4parser.muxer.builder.DefaultMp4Builder; +import org.mp4parser.muxer.container.mp4.MovieCreator; import org.mp4parser.muxer.tracks.AACTrackImpl; -import org.mp4parser.muxer.tracks.h264.H264TrackImpl; +import org.mp4parser.muxer.tracks.ClippedTrack; +import android.content.Context; +import android.media.MediaScannerConnection; import android.util.Log; + +import com.fpvout.digiview.MainActivity; + import java.io.File; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; +import java.nio.channels.FileChannel; public class Mp4Muxer extends Thread { @@ -30,23 +35,26 @@ public class Mp4Muxer extends Thread { private static final long DURATION = 1; private final File h264Dump; - private final File muxerfile; - private final String ambientAudioPath; - private final String finalDVROutputPath; + private final File ambientAudioFile; + private final File videoFile; + private final File output; + private final Context context; + SeekableByteChannel file; MP4Muxer muxer; BufferH264ES es; - public Mp4Muxer(File h264Dump, String ambientAudioPath, String finalDVROutputPath) { + public Mp4Muxer(Context context, File h264Dump, File ambientAudio, File output) { + this.context = context; this.h264Dump = h264Dump; - this.muxerfile = new File(finalDVROutputPath + ".tmp"); - this.ambientAudioPath = ambientAudioPath; - this.finalDVROutputPath = finalDVROutputPath; + this.ambientAudioFile = ambientAudio; + this.videoFile = new File(output.getAbsolutePath() + ".tmp"); + this.output = output; } private void init() throws IOException { - file = NIOUtils.writableChannel(muxerfile); + file = NIOUtils.writableChannel(videoFile); muxer = MP4Muxer.createMP4MuxerToChannel(file); es = new BufferH264ES(NIOUtils.mapFile(h264Dump)); @@ -109,32 +117,29 @@ public void run() { frame = nextValidFrame(frame, track); } muxer.finish(); - file.close(); + + Movie movie = MovieCreator.build(videoFile.getAbsolutePath()); + AACTrackImpl aacTrack = new AACTrackImpl(new FileDataSourceImpl(ambientAudioFile)); + ClippedTrack aacCroppedTrack = new ClippedTrack(aacTrack, 1, aacTrack.getSamples().size()); + movie.addTrack(aacCroppedTrack); + + Container mp4file = new DefaultMp4Builder().build(movie); + + FileOutputStream fileOutputStream = new FileOutputStream(output); + FileChannel fc = fileOutputStream.getChannel(); + mp4file.writeContainer(fc); + fileOutputStream.close(); + + // add mp4 to gallery + MediaScannerConnection.scanFile(context, + new String[]{output.toString()}, + null, null); + // cleanup h264Dump.delete(); - - try { - DataSource videoFile = new FileDataSourceImpl(muxerfile); - AACTrackImpl aacTrack = new AACTrackImpl(new FileDataSourceImpl(ambientAudioPath)); - - H264TrackImpl h264Track = new H264TrackImpl(videoFile); - Movie movie = new Movie(); - movie.addTrack(h264Track); - movie.addTrack(aacTrack); - - Container out = new DefaultMp4Builder().build(movie); - FileOutputStream fos = new FileOutputStream(new File(finalDVROutputPath)); - out.writeContainer(fos.getChannel()); - fos.close(); - - muxerfile.delete(); - new File(ambientAudioPath).delete(); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } + videoFile.delete(); + ambientAudioFile.delete(); } catch (IOException exception){ Log.e("DIGIVIEW", "MUXER: " + exception.getMessage()); } diff --git a/app/src/main/java/com/fpvout/digiview/helpers/StreamDumper.java b/app/src/main/java/com/fpvout/digiview/helpers/StreamDumper.java new file mode 100644 index 0000000..8c26dfe --- /dev/null +++ b/app/src/main/java/com/fpvout/digiview/helpers/StreamDumper.java @@ -0,0 +1,70 @@ +package com.fpvout.digiview.helpers; + +import android.content.Context; +import android.os.Environment; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Calendar; + +public class StreamDumper { + + private FileOutputStream fos; + private boolean bytesWritten = false; + + private final File dumpDir; + private File streamDump; + private File streamAmbient; + private File outFile; + private final Context context; + + public StreamDumper(Context context, String defaultPath){ + this.context = context; + dumpDir = new File(defaultPath); + + dumpDir.mkdirs(); + } + + public void dump(byte[] buffer, int offset, int receivedBytes) { + + try { + fos.write(buffer, offset, receivedBytes); + bytesWritten = true; + } catch (IOException exception) { + exception.printStackTrace(); + } + } + + public void init(String videoFileName, String ambientAudioFileName, String outFileFileName) { + try { + streamDump = new File(dumpDir, videoFileName); + streamAmbient = new File(dumpDir, ambientAudioFileName); + outFile = new File(dumpDir, outFileFileName); + fos = new FileOutputStream(streamDump); + bytesWritten = false; + } catch (IOException exception) { + exception.printStackTrace(); + } + } + + public void stop() { + try { + if(fos != null){ + fos.flush(); + fos.close(); + + if(bytesWritten) { + new Mp4Muxer(this.context, streamDump, streamAmbient,outFile).start(); + } + } + if(!bytesWritten){ + streamDump.delete(); + } + + } catch (IOException exception) { + exception.printStackTrace(); + } + } +} diff --git a/app/src/main/java/com/fpvout/digiview/helpers/ThreadPerTaskExecutor.java b/app/src/main/java/com/fpvout/digiview/helpers/ThreadPerTaskExecutor.java deleted file mode 100644 index 9e86b2c..0000000 --- a/app/src/main/java/com/fpvout/digiview/helpers/ThreadPerTaskExecutor.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.fpvout.digiview.helpers; - -import java.util.concurrent.Executor; - -public class ThreadPerTaskExecutor implements Executor { - public void execute(Runnable r) { - new Thread(r).start(); - } -} diff --git a/app/src/main/java/usb/AndroidUSBInputStream.java b/app/src/main/java/usb/AndroidUSBInputStream.java index a8add90..19069eb 100644 --- a/app/src/main/java/usb/AndroidUSBInputStream.java +++ b/app/src/main/java/usb/AndroidUSBInputStream.java @@ -77,7 +77,10 @@ public int read(byte[] buffer, int offset, int length) throws IOException { Log.d(TAG, "received buffer empty, sending magic packet again..."); usbConnection.bulkTransfer(sendEndPoint, "RMVT".getBytes(), "RMVT".getBytes().length, 2000); receivedBytes = usbConnection.bulkTransfer(receiveEndPoint, buffer, buffer.length, READ_TIMEOUT); + } else { + this.dvr.recordVideoDVR(buffer,offset , buffer.length); } + return receivedBytes; } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index a5bb79c..971b5fb 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -45,7 +45,7 @@ android:id="@+id/recordbt" android:layout_width="60dp" android:layout_height="60dp" - android:layout_marginEnd="16dp" + android:layout_marginEnd="25dp" android:background="@android:color/transparent" android:scaleType="fitXY" app:layout_constraintBottom_toTopOf="@+id/fpvView" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d927485..b7b80de 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -43,7 +43,7 @@ Connect recording started recording stopped - No stream recording could not be started + No stream! recording could not be started dvr has been saved merging audio and video of the dvr \ No newline at end of file From f2bc27e2de13ee2b391395f44507eb67471d99b3 Mon Sep 17 00:00:00 2001 From: markusstadtmann Date: Mon, 24 May 2021 07:20:51 +0200 Subject: [PATCH 14/29] updated style and minor changes --- .../com/fpvout/digiview/MainActivity.java | 72 +++++++++---------- .../java/com/fpvout/digiview/dvr/DVR.java | 5 ++ .../com/fpvout/digiview/helpers/Mp4Muxer.java | 17 ++++- .../fpvout/digiview/helpers/StreamDumper.java | 2 +- .../res/drawable-hdpi/rounded_corners.xml | 5 ++ app/src/main/res/layout/activity_main.xml | 71 ++++++++++++------ 6 files changed, 111 insertions(+), 61 deletions(-) create mode 100644 app/src/main/res/drawable-hdpi/rounded_corners.xml diff --git a/app/src/main/java/com/fpvout/digiview/MainActivity.java b/app/src/main/java/com/fpvout/digiview/MainActivity.java index a547d3e..8bf6691 100644 --- a/app/src/main/java/com/fpvout/digiview/MainActivity.java +++ b/app/src/main/java/com/fpvout/digiview/MainActivity.java @@ -24,6 +24,8 @@ import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; +import android.widget.ImageButton; +import android.widget.RelativeLayout; import android.widget.Toast; import androidx.annotation.NonNull; @@ -46,9 +48,11 @@ public class MainActivity extends AppCompatActivity implements UsbDeviceListener private static final int VENDOR_ID = 11427; private static final int PRODUCT_ID = 31; private int shortAnimationDuration; - private float buttonAlpha = 1; + private float toolbarAlpha = 0.9f; private View settingsButton; private View recordButton; + private ImageButton thumbnail; + private RelativeLayout toolbar; private View watermarkView; private OverlayView overlayView; PendingIntent permissionIntent; @@ -85,6 +89,9 @@ protected void onCreate(Bundle savedInstanceState) { actionBar.hide(); } + thumbnail = findViewById(R.id.thumbnail); + toolbar = findViewById(R.id.toolbar); + recordButton = findViewById(R.id.recordbt); recordButton.setOnClickListener(view -> { if (recorder != null) { @@ -178,7 +185,7 @@ public void onScaleEnd(ScaleGestureDetector detector) { if (!usbConnected) { if (searchDevice()) { - connect(); + //connect(); } else { overlayView.showOpaque(R.string.waiting_for_usb_device, OverlayStatus.Disconnected); } @@ -265,53 +272,49 @@ private void showSettingsButton() { } } + private void cancelButtonAnimation() { + Handler handler = toolbar.getHandler(); + if (handler != null) { + toolbar.getHandler().removeCallbacksAndMessages(null); + } + } + private void toggleButton() { - if (buttonAlpha == 1 && overlayView.getVisibility() == View.VISIBLE) return; + if (toolbarAlpha == 0.9 && overlayView.getVisibility() == View.VISIBLE) return; // cancel any pending delayed animations first cancelButtonAnimation(); - recordButton.getHandler().removeCallbacksAndMessages(null); - if (buttonAlpha == 1) { - buttonAlpha = 0; + int translation = 0; + if (toolbarAlpha == 0.9f) { + toolbarAlpha = 0; + translation = 60; } else { - buttonAlpha = 1; + toolbarAlpha = 0.9f; } - settingsButton.animate() - .alpha(buttonAlpha) + toolbar.animate() + .alpha(toolbarAlpha) + .translationX(translation) .setDuration(shortAnimationDuration) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - autoHideSettingsButton(); + autoHideToolbar(); } }); - - recordButton.animate() - .alpha(buttonAlpha) - .setDuration(shortAnimationDuration); } private void autoHideSettingsButton() { if (overlayView.getVisibility() == View.VISIBLE) return; - if (buttonAlpha == 0) return; + if (toolbarAlpha == 0) return; - settingsButton.postDelayed(new Runnable() { + toolbar.postDelayed(new Runnable() { @Override public void run() { - buttonAlpha = 0; - settingsButton.animate() - .alpha(0) - .setDuration(shortAnimationDuration); - } - }, 3000); - - recordButton.postDelayed(new Runnable() { - @Override - public void run() { - buttonAlpha = 0; - recordButton.animate() + toolbarAlpha = 0; + toolbar.animate() .alpha(0) + .translationX(60) .setDuration(shortAnimationDuration); } }, 3000); @@ -358,7 +361,7 @@ private boolean searchDevice() { private void connect() { usbConnected = true; // Init DVR recorder - recorder = DVR.getInstance(this, true); + recorder = DVR.getInstance(this, true, overlayView); try { recorder.init(mVideoReader); } catch (IOException e) { @@ -390,16 +393,12 @@ public void onResume() { actionBar.hide(); } - - settingsButton.setAlpha(1); - recordButton.setAlpha(1); - autoHideSettingsButton(); + autoHideToolbar(); updateWatermark(); updateVideoZoom(); - if(checkStoragePermission()){ - // permission already granted - finishStartup(); + if(checkStoragePermission()) { + connect(); } } @@ -438,7 +437,6 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permis if(requestCode == 1){ if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - finishStartup(); } else { diff --git a/app/src/main/java/com/fpvout/digiview/dvr/DVR.java b/app/src/main/java/com/fpvout/digiview/dvr/DVR.java index 99d19a2..580b1f0 100644 --- a/app/src/main/java/com/fpvout/digiview/dvr/DVR.java +++ b/app/src/main/java/com/fpvout/digiview/dvr/DVR.java @@ -32,6 +32,7 @@ public class DVR { private File dvrTmpFile; private String fileName; private StreamDumper streamDumper; + public static final String LATEST_THUMB_FILE = "latest.jpeg"; DVR(Activity activity, boolean recordAmbientAudio){ this.activity = activity; @@ -100,6 +101,10 @@ public void start() { } } + public String getLatestDThumbFile() { + return defaultFolder + "/" + LATEST_THUMB_FILE; + } + public boolean isRecording(){ return recording; } diff --git a/app/src/main/java/com/fpvout/digiview/helpers/Mp4Muxer.java b/app/src/main/java/com/fpvout/digiview/helpers/Mp4Muxer.java index 6f55ad8..e1cb45b 100644 --- a/app/src/main/java/com/fpvout/digiview/helpers/Mp4Muxer.java +++ b/app/src/main/java/com/fpvout/digiview/helpers/Mp4Muxer.java @@ -19,7 +19,10 @@ import org.mp4parser.muxer.tracks.ClippedTrack; import android.content.Context; +import android.graphics.Bitmap; import android.media.MediaScannerConnection; +import android.media.ThumbnailUtils; +import android.provider.MediaStore; import android.util.Log; import com.fpvout.digiview.MainActivity; @@ -29,6 +32,8 @@ import java.io.IOException; import java.nio.channels.FileChannel; +import static com.fpvout.digiview.dvr.DVR.LATEST_THUMB_FILE; + public class Mp4Muxer extends Thread { private static final int TIMESCALE = 60; @@ -39,14 +44,16 @@ public class Mp4Muxer extends Thread { private final File videoFile; private final File output; private final Context context; + private final File dumpDir; SeekableByteChannel file; MP4Muxer muxer; BufferH264ES es; - public Mp4Muxer(Context context, File h264Dump, File ambientAudio, File output) { + public Mp4Muxer(Context context, File dumpDir , File h264Dump, File ambientAudio, File output) { this.context = context; + this.dumpDir = dumpDir; this.h264Dump = h264Dump; this.ambientAudioFile = ambientAudio; this.videoFile = new File(output.getAbsolutePath() + ".tmp"); @@ -105,6 +112,8 @@ public void run() { Packet frame = skipToFirstValidFrame(); MuxerTrack track = null; + //save first frame as img (thumb) + while (frame != null) { if (track == null) { track = initVideoTrack(frame); @@ -131,6 +140,12 @@ public void run() { mp4file.writeContainer(fc); fileOutputStream.close(); + Bitmap thumb = ThumbnailUtils.createVideoThumbnail(output.getAbsolutePath() , MediaStore.Images.Thumbnails.MINI_KIND); + FileOutputStream thumbOutputStream = new FileOutputStream(new File(dumpDir.getAbsolutePath() + "/" + LATEST_THUMB_FILE)); + thumb.compress(Bitmap.CompressFormat.JPEG, 100, thumbOutputStream); + thumbOutputStream.flush(); + thumbOutputStream.close(); + // add mp4 to gallery MediaScannerConnection.scanFile(context, new String[]{output.toString()}, diff --git a/app/src/main/java/com/fpvout/digiview/helpers/StreamDumper.java b/app/src/main/java/com/fpvout/digiview/helpers/StreamDumper.java index 8c26dfe..cd1cc51 100644 --- a/app/src/main/java/com/fpvout/digiview/helpers/StreamDumper.java +++ b/app/src/main/java/com/fpvout/digiview/helpers/StreamDumper.java @@ -56,7 +56,7 @@ public void stop() { fos.close(); if(bytesWritten) { - new Mp4Muxer(this.context, streamDump, streamAmbient,outFile).start(); + new Mp4Muxer(this.context, dumpDir, streamDump, streamAmbient,outFile).start(); } } if(!bytesWritten){ diff --git a/app/src/main/res/drawable-hdpi/rounded_corners.xml b/app/src/main/res/drawable-hdpi/rounded_corners.xml new file mode 100644 index 0000000..9f9308b --- /dev/null +++ b/app/src/main/res/drawable-hdpi/rounded_corners.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 971b5fb..43ddcf2 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -40,29 +40,56 @@ app:layout_constraintBottom_toTopOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> - + app:layout_constraintTop_toTopOf="parent" > + + + + + + + + + + - \ No newline at end of file From e31693c3876232668f179ff967268d499a41bee8 Mon Sep 17 00:00:00 2001 From: markusstadtmann Date: Mon, 24 May 2021 15:15:20 +0200 Subject: [PATCH 15/29] rebased to dev --- .../com/fpvout/digiview/MainActivity.java | 83 ++++--------------- app/src/main/res/values/strings.xml | 1 + 2 files changed, 18 insertions(+), 66 deletions(-) diff --git a/app/src/main/java/com/fpvout/digiview/MainActivity.java b/app/src/main/java/com/fpvout/digiview/MainActivity.java index 8bf6691..3e03fa7 100644 --- a/app/src/main/java/com/fpvout/digiview/MainActivity.java +++ b/app/src/main/java/com/fpvout/digiview/MainActivity.java @@ -80,10 +80,12 @@ protected void onCreate(Bundle savedInstanceState) { // Hide top bar and status bar View decorView = getWindow().getDecorView(); - int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_FULLSCREEN; - decorView.setSystemUiVisibility(uiOptions); - decorView.setClickable(false); + decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + | View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_FULLSCREEN); ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { actionBar.hide(); @@ -177,54 +179,6 @@ public void onScaleEnd(ScaleGestureDetector detector) { } } }); - - watermarkView.setVisibility(View.GONE); - - mUsbMaskConnection = new UsbMaskConnection(); - mVideoReader = new VideoReaderExoplayer(fpvView, overlayView, this); - - if (!usbConnected) { - if (searchDevice()) { - //connect(); - } else { - overlayView.showOpaque(R.string.waiting_for_usb_device, OverlayStatus.Disconnected); - } - } - } - - @Override - public void onWindowFocusChanged(boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); - if (hasFocus) { - hideSystemUI(); - } - } - - private void hideSystemUI() { - // Enables regular immersive mode. - // For "lean back" mode, remove SYSTEM_UI_FLAG_IMMERSIVE. - // Or for "sticky immersive," replace it with SYSTEM_UI_FLAG_IMMERSIVE_STICKY - View decorView = getWindow().getDecorView(); - decorView.setSystemUiVisibility( - View.SYSTEM_UI_FLAG_IMMERSIVE - // Set the content to appear under the system bars so that the - // content doesn't resize when the system bars hide and show. - | View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - // Hide the nav bar and status bar - | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_FULLSCREEN); - } - - // Shows the system bars by removing all the flags -// except for the ones that make the content appear under the system bars. - private void showSystemUI() { - View decorView = getWindow().getDecorView(); - decorView.setSystemUiVisibility( - View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); } @Override @@ -256,18 +210,11 @@ private void updateVideoZoom() { } } - private void cancelButtonAnimation() { - Handler handler = settingsButton.getHandler(); - if (handler != null) { - settingsButton.getHandler().removeCallbacksAndMessages(null); - } - } - private void showSettingsButton() { cancelButtonAnimation(); if (overlayView.getVisibility() == View.VISIBLE) { - buttonAlpha = 1; + toolbarAlpha = 0.9f; settingsButton.setAlpha(1); } } @@ -304,7 +251,7 @@ public void onAnimationEnd(Animator animation) { }); } - private void autoHideSettingsButton() { + private void autoHideToolbar() { if (overlayView.getVisibility() == View.VISIBLE) return; if (toolbarAlpha == 0) return; @@ -361,7 +308,7 @@ private boolean searchDevice() { private void connect() { usbConnected = true; // Init DVR recorder - recorder = DVR.getInstance(this, true, overlayView); + recorder = DVR .getInstance(this, true); try { recorder.init(mVideoReader); } catch (IOException e) { @@ -372,7 +319,7 @@ private void connect() { overlayView.hide(); mVideoReader.start(); updateWatermark(); - autoHideSettingsButton(); + autoHideToolbar(); showOverlay(R.string.waiting_for_video, OverlayStatus.Connected); } @@ -398,7 +345,11 @@ public void onResume() { updateVideoZoom(); if(checkStoragePermission()) { - connect(); + if (searchDevice()) { + connect(); + } else { + showOverlay(R.string.waiting_for_usb_device, OverlayStatus.Connected); + } } } @@ -440,7 +391,7 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permis finishStartup(); } else { - overlayView.showOpaque("Storage access is required.", OverlayStatus.Error); + overlayView.show( R.string.storage_rights_required, OverlayStatus.Error); } } } @@ -466,7 +417,7 @@ private void hideOverlay() { overlayView.hide(); updateWatermark(); showSettingsButton(); - autoHideSettingsButton(); + autoHideToolbar(); } @Override diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b7b80de..986bfbb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -46,4 +46,5 @@ No stream! recording could not be started dvr has been saved merging audio and video of the dvr + Storage access is required. \ No newline at end of file From 45a38ccf15f5be42b36cf0f50fba1d9b6fcdf36f Mon Sep 17 00:00:00 2001 From: markusstadtmann Date: Mon, 24 May 2021 15:34:38 +0200 Subject: [PATCH 16/29] merged refactor branch --- .../com/fpvout/digiview/MainActivity.java | 22 +++++++++---------- .../fpvout/digiview/UsbMaskConnection.java | 7 ++++-- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/fpvout/digiview/MainActivity.java b/app/src/main/java/com/fpvout/digiview/MainActivity.java index d7629a1..b69ebdc 100644 --- a/app/src/main/java/com/fpvout/digiview/MainActivity.java +++ b/app/src/main/java/com/fpvout/digiview/MainActivity.java @@ -36,8 +36,6 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.core.view.WindowInsetsCompat; import androidx.core.view.WindowInsetsControllerCompat; -import androidx.preference.PreferenceManager; - import androidx.core.app.ActivityCompat; import io.sentry.SentryLevel; import io.sentry.android.core.SentryAndroid; @@ -67,7 +65,7 @@ public class MainActivity extends AppCompatActivity implements UsbDeviceListener VideoReaderExoplayer mVideoReader; boolean usbConnected = false; SurfaceView fpvView; - DVR recorder; + DVR dvr; private GestureDetector gestureDetector; private ScaleGestureDetector scaleGestureDetector; private SharedPreferences sharedPreferences; @@ -90,11 +88,11 @@ protected void onCreate(Bundle savedInstanceState) { recordButton = findViewById(R.id.recordbt); recordButton.setOnClickListener(view -> { - if (recorder != null) { - if (recorder.isRecording()) { - recorder.stop(); + if (dvr != null) { + if (dvr.isRecording()) { + dvr.stop(); } else { - recorder.start(); + dvr.start(); } } else { Toast.makeText(this, this.getText(R.string.no_dvr_video), Toast.LENGTH_LONG).show(); @@ -135,7 +133,6 @@ protected void onCreate(Bundle savedInstanceState) { mVideoReader.setVideoWaitingEventListener(() -> showOverlay(R.string.waiting_for_video, OverlayStatus.Connected)); mUsbMaskConnection = new UsbMaskConnection(); - if (!usbConnected) { usbDevice = UsbMaskConnection.searchDevice(usbManager, getApplicationContext()); if (usbDevice != null) { @@ -307,13 +304,13 @@ public void usbDeviceDetached() { private void connect() { usbConnected = true; // Init DVR recorder - recorder = DVR .getInstance(this, true); + dvr = DVR.getInstance(this, true); try { - recorder.init(mVideoReader); + dvr.init(mVideoReader); } catch (IOException e) { e.printStackTrace(); } - mUsbMaskConnection.setUsbDevice(usbManager, usbDevice, recorder); + mUsbMaskConnection.setUsbDevice(usbManager, usbDevice, dvr); mVideoReader.setUsbMaskConnection(mUsbMaskConnection); overlayView.hide(); mVideoReader.start(); @@ -334,7 +331,8 @@ public void onResume() { updateVideoZoom(); if(checkStoragePermission()) { - if (searchDevice()) { + usbDevice = UsbMaskConnection.searchDevice(usbManager, getApplicationContext()); + if (usbDevice != null) { connect(); } else { showOverlay(R.string.waiting_for_usb_device, OverlayStatus.Connected); diff --git a/app/src/main/java/com/fpvout/digiview/UsbMaskConnection.java b/app/src/main/java/com/fpvout/digiview/UsbMaskConnection.java index 5ff220c..23159c4 100644 --- a/app/src/main/java/com/fpvout/digiview/UsbMaskConnection.java +++ b/app/src/main/java/com/fpvout/digiview/UsbMaskConnection.java @@ -27,8 +27,10 @@ public class UsbMaskConnection { AndroidUSBInputStream mInputStream; AndroidUSBOutputStream mOutputStream; private boolean ready = false; + private DVR dvr; public UsbMaskConnection() { + } public static UsbDevice searchDevice(UsbManager usbManager, Context c) { @@ -76,14 +78,15 @@ public boolean isReady() { return ready; } - public void setUsbDevice(UsbManager usbManager, UsbDevice d) { + public void setUsbDevice(UsbManager usbManager, UsbDevice d, DVR _dvr) { + dvr = _dvr; usbConnection = usbManager.openDevice(d); usbInterface = d.getInterface(3); usbConnection.claimInterface(usbInterface, true); mOutputStream = new AndroidUSBOutputStream(usbInterface.getEndpoint(0), usbConnection); - mInputStream = new AndroidUSBInputStream(usbInterface.getEndpoint(1), usbInterface.getEndpoint(0), usbConnection); + mInputStream = new AndroidUSBInputStream(usbInterface.getEndpoint(1), usbInterface.getEndpoint(0), usbConnection, dvr); ready = true; } } From 4efb67c92418eb043e843eec3edbaa5321474562 Mon Sep 17 00:00:00 2001 From: markusstadtmann Date: Mon, 24 May 2021 20:55:54 +0200 Subject: [PATCH 17/29] added feature for gallery button and fixed toolbar display bug --- .../com/fpvout/digiview/MainActivity.java | 65 +++++++++++--- .../fpvout/digiview/VideoReaderExoplayer.java | 2 +- .../java/com/fpvout/digiview/dvr/DVR.java | 17 ++-- .../fpvout/digiview/helpers/StreamDumper.java | 5 +- app/src/main/res/layout/activity_main.xml | 88 +++++++++---------- 5 files changed, 112 insertions(+), 65 deletions(-) diff --git a/app/src/main/java/com/fpvout/digiview/MainActivity.java b/app/src/main/java/com/fpvout/digiview/MainActivity.java index b69ebdc..63e1a93 100644 --- a/app/src/main/java/com/fpvout/digiview/MainActivity.java +++ b/app/src/main/java/com/fpvout/digiview/MainActivity.java @@ -9,10 +9,15 @@ import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; +import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.os.Message; +import android.os.StrictMode; import android.preference.PreferenceManager; import android.os.Handler; import android.util.Log; @@ -42,7 +47,10 @@ import static com.fpvout.digiview.UsbMaskConnection.ACTION_USB_PERMISSION; import com.fpvout.digiview.dvr.DVR; + +import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.util.HashMap; @@ -51,7 +59,7 @@ public class MainActivity extends AppCompatActivity implements UsbDeviceListener { private static final String TAG = "DIGIVIEW"; private int shortAnimationDuration; - private float toolbarAlpha = 0.9f; + private float toolbarAlpha = 0.7f; private View settingsButton; private View recordButton; private ImageButton thumbnail; @@ -84,6 +92,19 @@ protected void onCreate(Bundle savedInstanceState) { setFullscreen(); thumbnail = findViewById(R.id.thumbnail); + thumbnail.setOnClickListener(view -> { + StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); + StrictMode.setVmPolicy(builder.build()); + Intent intent = new Intent(); + intent.setAction(android.content.Intent.ACTION_VIEW); + if (dvr != null) { + intent.setDataAndType(Uri.withAppendedPath(Uri.fromFile(dvr.getDefaultFolder()), ""), "video/*"); + } else { + intent.setType("image/*"); + } + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + }); toolbar = findViewById(R.id.toolbar); recordButton = findViewById(R.id.recordbt); @@ -99,6 +120,12 @@ protected void onCreate(Bundle savedInstanceState) { } }); + dvr = DVR.getInstance(this, true, new Handler(message -> { + updateDVRThumb(); + return true; + })); + updateDVRThumb(); + // Prevent screen from sleeping getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); @@ -215,15 +242,6 @@ private void updateVideoZoom() { } } - private void showSettingsButton() { - cancelToolbarAnimation(); - - if (overlayView.getVisibility() == View.VISIBLE) { - toolbarAlpha = 0.9f; - settingsButton.setAlpha(1); - } - } - private void cancelToolbarAnimation() { Handler handler = toolbar.getHandler(); if (handler != null) { @@ -232,16 +250,23 @@ private void cancelToolbarAnimation() { } private void toggleButton() { + if (dvr != null) { + if (dvr.isRecording()) { + toolbar.setX(0); + toolbar.setAlpha(0.7f); + return; + } + } if (toolbarAlpha == 0.9 && overlayView.getVisibility() == View.VISIBLE) return; // cancel any pending delayed animations first cancelToolbarAnimation(); int translation = 0; - if (toolbarAlpha == 0.9f) { + if (toolbarAlpha == 0.7f) { toolbarAlpha = 0; translation = 60; } else { - toolbarAlpha = 0.9f; + toolbarAlpha = 0.7f; } toolbar.animate() @@ -257,6 +282,11 @@ public void onAnimationEnd(Animator animation) { } private void autoHideToolbar() { + if (dvr != null) { + if (dvr.isRecording()) { + return; + } + } if (overlayView.getVisibility() == View.VISIBLE) return; if (toolbarAlpha == 0) return; @@ -300,11 +330,20 @@ public void usbDeviceDetached() { this.onStop(); } + private void updateDVRThumb() { + if (dvr != null) { + File file = new File(dvr.getLatestThumbFile()); + if (file.exists()) { + Bitmap bmp = BitmapFactory.decodeFile(dvr.getLatestThumbFile()); + thumbnail.setImageBitmap(bmp); + } + } + } + private void connect() { usbConnected = true; // Init DVR recorder - dvr = DVR.getInstance(this, true); try { dvr.init(mVideoReader); } catch (IOException e) { diff --git a/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java b/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java index 3473598..f058c3a 100644 --- a/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java +++ b/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java @@ -129,7 +129,7 @@ public void onPlaybackStateChanged(int state) { case Player.STATE_IDLE: case Player.STATE_READY: case Player.STATE_BUFFERING: - streaming = false; + streaming = true; break; case Player.STATE_ENDED: Log.d(TAG, "PLAYER_STATE - ENDED"); diff --git a/app/src/main/java/com/fpvout/digiview/dvr/DVR.java b/app/src/main/java/com/fpvout/digiview/dvr/DVR.java index 580b1f0..ba32f3e 100644 --- a/app/src/main/java/com/fpvout/digiview/dvr/DVR.java +++ b/app/src/main/java/com/fpvout/digiview/dvr/DVR.java @@ -3,6 +3,7 @@ import android.app.Activity; import android.media.MediaRecorder; import android.os.Environment; +import android.os.Handler; import android.util.Log; import android.widget.ImageButton; import android.widget.Toast; @@ -32,18 +33,20 @@ public class DVR { private File dvrTmpFile; private String fileName; private StreamDumper streamDumper; + private static Handler updateAfterRecord; public static final String LATEST_THUMB_FILE = "latest.jpeg"; - DVR(Activity activity, boolean recordAmbientAudio){ + DVR(Activity activity, boolean recordAmbientAudio, Handler updateAfterRecord){ this.activity = activity; this.recordAmbientAudio = recordAmbientAudio; + this.updateAfterRecord = updateAfterRecord; defaultFolder = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + "/" + this.activity.getApplicationInfo().loadLabel(this.activity.getPackageManager()).toString(); streamDumper = new StreamDumper(activity, defaultFolder); } - public static DVR getInstance(Activity context, boolean recordAmbientAudio){ + public static DVR getInstance(Activity context, boolean recordAmbientAudio, Handler updateAfterRecord){ if (instance == null) { - instance = new DVR(context, recordAmbientAudio); + instance = new DVR(context, recordAmbientAudio, updateAfterRecord); } return instance; } @@ -101,7 +104,7 @@ public void start() { } } - public String getLatestDThumbFile() { + public String getLatestThumbFile() { return defaultFolder + "/" + LATEST_THUMB_FILE; } @@ -122,7 +125,11 @@ public void stop() { e.printStackTrace(); } } - streamDumper.stop(); + streamDumper.stop(updateAfterRecord); this.recording = false; } + + public File getDefaultFolder() { + return new File(defaultFolder); + } } \ No newline at end of file diff --git a/app/src/main/java/com/fpvout/digiview/helpers/StreamDumper.java b/app/src/main/java/com/fpvout/digiview/helpers/StreamDumper.java index cd1cc51..8afa9fd 100644 --- a/app/src/main/java/com/fpvout/digiview/helpers/StreamDumper.java +++ b/app/src/main/java/com/fpvout/digiview/helpers/StreamDumper.java @@ -2,6 +2,7 @@ import android.content.Context; import android.os.Environment; +import android.os.Handler; import java.io.File; import java.io.FileOutputStream; @@ -49,7 +50,7 @@ public void init(String videoFileName, String ambientAudioFileName, String outFi } } - public void stop() { + public void stop(Handler completeHandler) { try { if(fos != null){ fos.flush(); @@ -62,7 +63,7 @@ public void stop() { if(!bytesWritten){ streamDump.delete(); } - + completeHandler.sendEmptyMessage(0); } catch (IOException exception) { exception.printStackTrace(); } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 43ddcf2..3bc2c41 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -41,55 +41,55 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" > + - + - + - + - + - - + \ No newline at end of file From e0b6c7fef8c6191403a24e95b52f320fc21d9c95 Mon Sep 17 00:00:00 2001 From: markusstadtmann Date: Mon, 24 May 2021 20:58:01 +0200 Subject: [PATCH 18/29] removed isRecording check from MainActivity --- .../main/java/com/fpvout/digiview/MainActivity.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/app/src/main/java/com/fpvout/digiview/MainActivity.java b/app/src/main/java/com/fpvout/digiview/MainActivity.java index 63e1a93..6874b3f 100644 --- a/app/src/main/java/com/fpvout/digiview/MainActivity.java +++ b/app/src/main/java/com/fpvout/digiview/MainActivity.java @@ -250,13 +250,6 @@ private void cancelToolbarAnimation() { } private void toggleButton() { - if (dvr != null) { - if (dvr.isRecording()) { - toolbar.setX(0); - toolbar.setAlpha(0.7f); - return; - } - } if (toolbarAlpha == 0.9 && overlayView.getVisibility() == View.VISIBLE) return; // cancel any pending delayed animations first cancelToolbarAnimation(); @@ -282,11 +275,6 @@ public void onAnimationEnd(Animator animation) { } private void autoHideToolbar() { - if (dvr != null) { - if (dvr.isRecording()) { - return; - } - } if (overlayView.getVisibility() == View.VISIBLE) return; if (toolbarAlpha == 0) return; From 60cb09b8d4eab7f9615f5a7b025ba037b818d573 Mon Sep 17 00:00:00 2001 From: markusstadtmann Date: Mon, 24 May 2021 21:01:36 +0200 Subject: [PATCH 19/29] added thumb update to different locations --- app/src/main/java/com/fpvout/digiview/MainActivity.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/fpvout/digiview/MainActivity.java b/app/src/main/java/com/fpvout/digiview/MainActivity.java index 6874b3f..9f7e0fe 100644 --- a/app/src/main/java/com/fpvout/digiview/MainActivity.java +++ b/app/src/main/java/com/fpvout/digiview/MainActivity.java @@ -110,6 +110,7 @@ protected void onCreate(Bundle savedInstanceState) { recordButton = findViewById(R.id.recordbt); recordButton.setOnClickListener(view -> { if (dvr != null) { + updateDVRThumb(); if (dvr.isRecording()) { dvr.stop(); } else { @@ -261,7 +262,7 @@ private void toggleButton() { } else { toolbarAlpha = 0.7f; } - + updateDVRThumb(); toolbar.animate() .alpha(toolbarAlpha) .translationX(translation) @@ -270,6 +271,7 @@ private void toggleButton() { @Override public void onAnimationEnd(Animator animation) { autoHideToolbar(); + updateDVRThumb(); } }); } @@ -341,6 +343,7 @@ private void connect() { mVideoReader.setUsbMaskConnection(mUsbMaskConnection); overlayView.hide(); mVideoReader.start(); + updateDVRThumb(); updateWatermark(); autoHideToolbar(); showOverlay(R.string.waiting_for_video, OverlayStatus.Connected); From c99dd7ca0fbfac974cf7d8b8f9fbeb2bbc9008ed Mon Sep 17 00:00:00 2001 From: markusstadtmann Date: Tue, 25 May 2021 08:32:45 +0200 Subject: [PATCH 20/29] minor updates --- app/src/main/AndroidManifest.xml | 1 - .../fpvout/digiview/InputStreamDataSource.java | 2 -- .../java/com/fpvout/digiview/MainActivity.java | 2 +- .../com/fpvout/digiview/VideoReaderExoplayer.java | 11 ++--------- .../main/java/com/fpvout/digiview/dvr/DVR.java | 15 +++++++++------ app/src/main/java/usb/AndroidUSBInputStream.java | 4 +++- 6 files changed, 15 insertions(+), 20 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c2f8a43..515ff8d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,7 +6,6 @@ - diff --git a/app/src/main/java/com/fpvout/digiview/InputStreamDataSource.java b/app/src/main/java/com/fpvout/digiview/InputStreamDataSource.java index 98d43d0..0bfc5dd 100644 --- a/app/src/main/java/com/fpvout/digiview/InputStreamDataSource.java +++ b/app/src/main/java/com/fpvout/digiview/InputStreamDataSource.java @@ -1,8 +1,6 @@ package com.fpvout.digiview; import android.net.Uri; - -import com.fpvout.digiview.dvr.DVR; import androidx.annotation.NonNull; import com.google.android.exoplayer2.C; diff --git a/app/src/main/java/com/fpvout/digiview/MainActivity.java b/app/src/main/java/com/fpvout/digiview/MainActivity.java index 9f7e0fe..4d050ba 100644 --- a/app/src/main/java/com/fpvout/digiview/MainActivity.java +++ b/app/src/main/java/com/fpvout/digiview/MainActivity.java @@ -335,7 +335,7 @@ private void connect() { usbConnected = true; // Init DVR recorder try { - dvr.init(mVideoReader); + dvr.init(); } catch (IOException e) { e.printStackTrace(); } diff --git a/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java b/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java index f058c3a..1a1e40d 100644 --- a/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java +++ b/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java @@ -13,7 +13,6 @@ import androidx.annotation.NonNull; import androidx.constraintlayout.widget.ConstraintLayout; -import com.fpvout.digiview.dvr.DVR; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.DefaultLoadControl; import com.google.android.exoplayer2.ExoPlaybackException; @@ -58,12 +57,8 @@ public void setVideoWaitingEventListener(VideoWaitingListener listener) { VideoReaderExoplayer(SurfaceView videoSurface, Context c) { surfaceView = videoSurface; - context = c; - sharedPreferences = PreferenceManager.getDefaultSharedPreferences(c); - } - - public boolean isStreaming(){ - return streaming; + context = c; + sharedPreferences = PreferenceManager.getDefaultSharedPreferences(c); } public void setUsbMaskConnection(UsbMaskConnection connection) { @@ -129,14 +124,12 @@ public void onPlaybackStateChanged(int state) { case Player.STATE_IDLE: case Player.STATE_READY: case Player.STATE_BUFFERING: - streaming = true; break; case Player.STATE_ENDED: Log.d(TAG, "PLAYER_STATE - ENDED"); if (videoWaitingListener != null) videoWaitingListener.onVideoWaiting(); // let MainActivity know so it can hide watermark/show settings button (new Handler(Looper.getMainLooper())).postDelayed(() -> restart(), 1000); - streaming = false; break; } } diff --git a/app/src/main/java/com/fpvout/digiview/dvr/DVR.java b/app/src/main/java/com/fpvout/digiview/dvr/DVR.java index ba32f3e..07bb03f 100644 --- a/app/src/main/java/com/fpvout/digiview/dvr/DVR.java +++ b/app/src/main/java/com/fpvout/digiview/dvr/DVR.java @@ -29,12 +29,12 @@ public class DVR { private String ambietAudio; private String videoFile; private String dvrFile; - private VideoReaderExoplayer mPlayer; private File dvrTmpFile; private String fileName; private StreamDumper streamDumper; private static Handler updateAfterRecord; public static final String LATEST_THUMB_FILE = "latest.jpeg"; + private boolean streaming = false; DVR(Activity activity, boolean recordAmbientAudio, Handler updateAfterRecord){ this.activity = activity; @@ -51,9 +51,7 @@ public static DVR getInstance(Activity context, boolean recordAmbientAudio, Hand return instance; } - public void init(VideoReaderExoplayer mPlayer) throws IOException { - this.mPlayer = mPlayer; - + public void init() throws IOException { recorder = new MediaRecorder(); recorder.setAudioSource(MediaRecorder.AudioSource.MIC); recorder.setOutputFormat(MediaRecorder.OutputFormat.AAC_ADTS); @@ -61,13 +59,18 @@ public void init(VideoReaderExoplayer mPlayer) throws IOException { } public void recordVideoDVR(byte[] buffer, int offset, int readLength) { + if (buffer != null){ + streaming = true; + } else { + streaming = false; + } if (isRecording()) { streamDumper.dump( buffer, offset, readLength); } } public void start() { - if ( mPlayer.isStreaming()) { + if (streaming) { this.recording = true; fileName = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss") .format(Calendar.getInstance().getTime()); @@ -120,7 +123,7 @@ public void stop() { if (recordAmbientAudio) { recorder.stop(); try { - init(mPlayer); + init(); } catch (IOException e) { e.printStackTrace(); } diff --git a/app/src/main/java/usb/AndroidUSBInputStream.java b/app/src/main/java/usb/AndroidUSBInputStream.java index e15b336..0da3e28 100644 --- a/app/src/main/java/usb/AndroidUSBInputStream.java +++ b/app/src/main/java/usb/AndroidUSBInputStream.java @@ -11,6 +11,7 @@ */ import java.io.IOException; import java.io.InputStream; +import java.util.Arrays; public class AndroidUSBInputStream extends InputStream { @@ -57,7 +58,8 @@ public int read(byte[] buffer, int offset, int length) { usbConnection.bulkTransfer(sendEndPoint, "RMVT".getBytes(), "RMVT".getBytes().length, 2000); receivedBytes = usbConnection.bulkTransfer(receiveEndPoint, buffer, buffer.length, READ_TIMEOUT); } else { - this.dvr.recordVideoDVR(buffer,offset , buffer.length); + byte[] copiedBuffer = Arrays.copyOf(buffer, buffer.length); + this.dvr.recordVideoDVR(copiedBuffer,offset , copiedBuffer.length); } return receivedBytes; From b04bc43dcd56c6638888db9f934e0364ba3ed597 Mon Sep 17 00:00:00 2001 From: markusstadtmann Date: Tue, 25 May 2021 11:51:32 +0200 Subject: [PATCH 21/29] fixed some perfomance issues and recording lags on the dvr --- .../com/fpvout/digiview/MainActivity.java | 14 ++-- .../java/com/fpvout/digiview/dvr/DVR.java | 75 +++++++++++-------- .../helpers/ThreadPerTaskExecutor.java | 9 +++ 3 files changed, 60 insertions(+), 38 deletions(-) create mode 100644 app/src/main/java/com/fpvout/digiview/helpers/ThreadPerTaskExecutor.java diff --git a/app/src/main/java/com/fpvout/digiview/MainActivity.java b/app/src/main/java/com/fpvout/digiview/MainActivity.java index 4d050ba..99f6388 100644 --- a/app/src/main/java/com/fpvout/digiview/MainActivity.java +++ b/app/src/main/java/com/fpvout/digiview/MainActivity.java @@ -125,6 +125,12 @@ protected void onCreate(Bundle savedInstanceState) { updateDVRThumb(); return true; })); + // Init DVR recorder + try { + dvr.init(); + } catch (IOException e) { + e.printStackTrace(); + } updateDVRThumb(); // Prevent screen from sleeping @@ -326,6 +332,8 @@ private void updateDVRThumb() { if (file.exists()) { Bitmap bmp = BitmapFactory.decodeFile(dvr.getLatestThumbFile()); thumbnail.setImageBitmap(bmp); + } else { + thumbnail.setImageBitmap(null); } } } @@ -333,12 +341,6 @@ private void updateDVRThumb() { private void connect() { usbConnected = true; - // Init DVR recorder - try { - dvr.init(); - } catch (IOException e) { - e.printStackTrace(); - } mUsbMaskConnection.setUsbDevice(usbManager, usbDevice, dvr); mVideoReader.setUsbMaskConnection(mUsbMaskConnection); overlayView.hide(); diff --git a/app/src/main/java/com/fpvout/digiview/dvr/DVR.java b/app/src/main/java/com/fpvout/digiview/dvr/DVR.java index 07bb03f..542b704 100644 --- a/app/src/main/java/com/fpvout/digiview/dvr/DVR.java +++ b/app/src/main/java/com/fpvout/digiview/dvr/DVR.java @@ -11,6 +11,7 @@ import com.fpvout.digiview.R; import com.fpvout.digiview.VideoReaderExoplayer; import com.fpvout.digiview.helpers.StreamDumper; +import com.fpvout.digiview.helpers.ThreadPerTaskExecutor; import java.io.File; import java.io.IOException; @@ -52,12 +53,17 @@ public static DVR getInstance(Activity context, boolean recordAmbientAudio, Hand } public void init() throws IOException { + repairNotFinishedDVR(); recorder = new MediaRecorder(); recorder.setAudioSource(MediaRecorder.AudioSource.MIC); recorder.setOutputFormat(MediaRecorder.OutputFormat.AAC_ADTS); recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); } + private void repairNotFinishedDVR(){ + + } + public void recordVideoDVR(byte[] buffer, int offset, int readLength) { if (buffer != null){ streaming = true; @@ -71,37 +77,38 @@ public void recordVideoDVR(byte[] buffer, int offset, int readLength) { public void start() { if (streaming) { - this.recording = true; - fileName = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss") - .format(Calendar.getInstance().getTime()); - ambietAudio = "/DigiView_" + fileName + ".aac"; - videoFile ="/DigiView_"+fileName+".h264"; - dvrFile = "/DigiView_" + fileName + ".mp4"; - - Log.d(DVR_LOG_TAG, "creating folder for dvr saving ..."); - File objFolder = new File(defaultFolder); - if (!objFolder.exists()) - objFolder.mkdir(); - - - Log.d(DVR_LOG_TAG, "start recording ..."); - streamDumper.init(videoFile, ambietAudio, dvrFile); - Toast.makeText(activity, activity.getText(R.string.recording_started), Toast.LENGTH_LONG).show(); ((ImageButton) activity.findViewById(R.id.recordbt)).setImageResource(R.drawable.stop); - - - if (recordAmbientAudio) { - Log.d(DVR_LOG_TAG, "starting ambient recording ..."); - recorder.setOutputFile(defaultFolder + ambietAudio); - try { - recorder.prepare(); - recorder.start(); // Ambient Audio Recording is now started - } catch (IOException e) { - e.printStackTrace(); + ThreadPerTaskExecutor executor = new ThreadPerTaskExecutor(); + executor.execute(() -> { + fileName = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss") + .format(Calendar.getInstance().getTime()); + ambietAudio = "/DigiView_" + fileName + ".aac"; + videoFile ="/DigiView_"+fileName+".h264"; + dvrFile = "/DigiView_" + fileName + ".mp4"; + + Log.d(DVR_LOG_TAG, "creating folder for dvr saving ..."); + File objFolder = new File(defaultFolder); + if (!objFolder.exists()) + objFolder.mkdir(); + + Log.d(DVR_LOG_TAG, "start recording ..."); + streamDumper.init(videoFile, ambietAudio, dvrFile); + if (recordAmbientAudio) { + Log.d(DVR_LOG_TAG, "starting ambient recording ..."); + recorder.setOutputFile(defaultFolder + ambietAudio); + try { + recorder.prepare(); + recorder.start(); // Ambient Audio Recording is now started + } catch (IOException e) { + e.printStackTrace(); + } } - } + + //start recording (input stream starts collecting data + this.recording = true; + }); } else { Toast.makeText(activity, "Stream not ready", Toast.LENGTH_LONG).show(); } @@ -117,19 +124,23 @@ public boolean isRecording(){ public void stop() { Log.d(DVR_LOG_TAG, "stop recording ..."); + this.recording = false; Toast.makeText(activity, activity.getText(R.string.recording_stopped), Toast.LENGTH_LONG).show(); ((ImageButton) activity.findViewById(R.id.recordbt)).setImageResource(R.drawable.record); - if (recordAmbientAudio) { - recorder.stop(); + ThreadPerTaskExecutor executor = new ThreadPerTaskExecutor(); + executor.execute(() -> { + streamDumper.stop(updateAfterRecord); + if (recordAmbientAudio) { + recorder.stop(); + } + try { init(); } catch (IOException e) { e.printStackTrace(); } - } - streamDumper.stop(updateAfterRecord); - this.recording = false; + }); } public File getDefaultFolder() { diff --git a/app/src/main/java/com/fpvout/digiview/helpers/ThreadPerTaskExecutor.java b/app/src/main/java/com/fpvout/digiview/helpers/ThreadPerTaskExecutor.java new file mode 100644 index 0000000..9e86b2c --- /dev/null +++ b/app/src/main/java/com/fpvout/digiview/helpers/ThreadPerTaskExecutor.java @@ -0,0 +1,9 @@ +package com.fpvout.digiview.helpers; + +import java.util.concurrent.Executor; + +public class ThreadPerTaskExecutor implements Executor { + public void execute(Runnable r) { + new Thread(r).start(); + } +} From f44645b839ab97b7f0e278d1a27fa859661ff547 Mon Sep 17 00:00:00 2001 From: markusstadtmann Date: Tue, 25 May 2021 15:35:08 +0200 Subject: [PATCH 22/29] changed to a different access to InputStream --- .../com/fpvout/digiview/MainActivity.java | 33 +++++++------- .../fpvout/digiview/UsbMaskConnection.java | 6 ++- .../java/com/fpvout/digiview/dvr/DVR.java | 44 +++++++++++-------- .../main/java/usb/AndroidUSBInputStream.java | 21 ++++++--- 4 files changed, 60 insertions(+), 44 deletions(-) diff --git a/app/src/main/java/com/fpvout/digiview/MainActivity.java b/app/src/main/java/com/fpvout/digiview/MainActivity.java index 99f6388..3457319 100644 --- a/app/src/main/java/com/fpvout/digiview/MainActivity.java +++ b/app/src/main/java/com/fpvout/digiview/MainActivity.java @@ -121,24 +121,13 @@ protected void onCreate(Bundle savedInstanceState) { } }); - dvr = DVR.getInstance(this, true, new Handler(message -> { - updateDVRThumb(); - return true; - })); - // Init DVR recorder - try { - dvr.init(); - } catch (IOException e) { - e.printStackTrace(); - } - updateDVRThumb(); - // Prevent screen from sleeping getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); // Register app for auto launch usbDeviceBroadcastReceiver = new UsbDeviceBroadcastReceiver(this); + IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION); registerReceiver(usbDeviceBroadcastReceiver, filter); IntentFilter filterDetached = new IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED); @@ -167,6 +156,13 @@ protected void onCreate(Bundle savedInstanceState) { mVideoReader.setVideoWaitingEventListener(() -> showOverlay(R.string.waiting_for_video, OverlayStatus.Connected)); mUsbMaskConnection = new UsbMaskConnection(); + + dvr = DVR.getInstance(this, true, new Handler(message -> { + updateDVRThumb(); + return true; + }), mUsbMaskConnection); + updateDVRThumb(); + if (!usbConnected) { usbDevice = UsbMaskConnection.searchDevice(usbManager, getApplicationContext()); if (usbDevice != null) { @@ -363,16 +359,17 @@ public void onResume() { updateVideoZoom(); if(checkStoragePermission()) { - usbDevice = UsbMaskConnection.searchDevice(usbManager, getApplicationContext()); - if (usbDevice != null) { - connect(); - } else { - showOverlay(R.string.waiting_for_usb_device, OverlayStatus.Connected); - } + finishStartup(); } } private void finishStartup(){ + // Init DVR recorder + try { + dvr.init(); + } catch (IOException e) { + Log.i(TAG, "DVR - init failed"); + } if (!usbConnected) { usbDevice = UsbMaskConnection.searchDevice(usbManager, getApplicationContext()); if (usbDevice != null) { diff --git a/app/src/main/java/com/fpvout/digiview/UsbMaskConnection.java b/app/src/main/java/com/fpvout/digiview/UsbMaskConnection.java index 23159c4..5985c1b 100644 --- a/app/src/main/java/com/fpvout/digiview/UsbMaskConnection.java +++ b/app/src/main/java/com/fpvout/digiview/UsbMaskConnection.java @@ -33,6 +33,10 @@ public UsbMaskConnection() { } + public AndroidUSBInputStream getInputStream(){ + return mInputStream; + } + public static UsbDevice searchDevice(UsbManager usbManager, Context c) { PendingIntent permissionIntent = PendingIntent.getBroadcast(c, 0, new Intent(ACTION_USB_PERMISSION), 0); @@ -86,7 +90,7 @@ public void setUsbDevice(UsbManager usbManager, UsbDevice d, DVR _dvr) { usbConnection.claimInterface(usbInterface, true); mOutputStream = new AndroidUSBOutputStream(usbInterface.getEndpoint(0), usbConnection); - mInputStream = new AndroidUSBInputStream(usbInterface.getEndpoint(1), usbInterface.getEndpoint(0), usbConnection, dvr); + mInputStream = new AndroidUSBInputStream(usbInterface.getEndpoint(1), usbInterface.getEndpoint(0), usbConnection); ready = true; } } diff --git a/app/src/main/java/com/fpvout/digiview/dvr/DVR.java b/app/src/main/java/com/fpvout/digiview/dvr/DVR.java index 542b704..f5b01ae 100644 --- a/app/src/main/java/com/fpvout/digiview/dvr/DVR.java +++ b/app/src/main/java/com/fpvout/digiview/dvr/DVR.java @@ -4,22 +4,28 @@ import android.media.MediaRecorder; import android.os.Environment; import android.os.Handler; +import android.os.Looper; +import android.os.Message; import android.util.Log; import android.widget.ImageButton; import android.widget.Toast; import com.fpvout.digiview.R; +import com.fpvout.digiview.UsbMaskConnection; import com.fpvout.digiview.VideoReaderExoplayer; import com.fpvout.digiview.helpers.StreamDumper; import com.fpvout.digiview.helpers.ThreadPerTaskExecutor; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.text.SimpleDateFormat; import java.util.Calendar; +import androidx.annotation.NonNull; +import usb.AndroidUSBInputStream; + public class DVR { - private static final int WRITE_EXTERNAL_STORAGE = 0; private final Activity activity; private boolean recordAmbientAudio; private MediaRecorder recorder; @@ -30,24 +36,24 @@ public class DVR { private String ambietAudio; private String videoFile; private String dvrFile; - private File dvrTmpFile; private String fileName; private StreamDumper streamDumper; + private UsbMaskConnection connection; private static Handler updateAfterRecord; public static final String LATEST_THUMB_FILE = "latest.jpeg"; - private boolean streaming = false; - DVR(Activity activity, boolean recordAmbientAudio, Handler updateAfterRecord){ + DVR(Activity activity, boolean recordAmbientAudio, Handler updateAfterRecord, UsbMaskConnection connection){ this.activity = activity; + this.connection = connection; this.recordAmbientAudio = recordAmbientAudio; this.updateAfterRecord = updateAfterRecord; defaultFolder = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + "/" + this.activity.getApplicationInfo().loadLabel(this.activity.getPackageManager()).toString(); streamDumper = new StreamDumper(activity, defaultFolder); } - public static DVR getInstance(Activity context, boolean recordAmbientAudio, Handler updateAfterRecord){ + public static DVR getInstance(Activity context, boolean recordAmbientAudio, Handler updateAfterRecord, UsbMaskConnection connection){ if (instance == null) { - instance = new DVR(context, recordAmbientAudio, updateAfterRecord); + instance = new DVR(context, recordAmbientAudio, updateAfterRecord, connection); } return instance; } @@ -64,22 +70,23 @@ private void repairNotFinishedDVR(){ } - public void recordVideoDVR(byte[] buffer, int offset, int readLength) { - if (buffer != null){ - streaming = true; - } else { - streaming = false; - } - if (isRecording()) { - streamDumper.dump( buffer, offset, readLength); - } - } - public void start() { - if (streaming) { + if (connection.getInputStream() != null) { Toast.makeText(activity, activity.getText(R.string.recording_started), Toast.LENGTH_LONG).show(); ((ImageButton) activity.findViewById(R.id.recordbt)).setImageResource(R.drawable.stop); + connection.getInputStream().setInputStreamListener(new Handler(message -> { + if (streamDumper != null) { + if (isRecording()) { + byte[] buffer = (byte[]) message.obj; + if (buffer != null) { + streamDumper.dump(buffer, message.what, buffer.length); + } + } + } + return true; + })); + ThreadPerTaskExecutor executor = new ThreadPerTaskExecutor(); executor.execute(() -> { fileName = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss") @@ -130,6 +137,7 @@ public void stop() { ThreadPerTaskExecutor executor = new ThreadPerTaskExecutor(); executor.execute(() -> { + connection.getInputStream().setInputStreamListener(null); //remove listener from raw streamDumper.stop(updateAfterRecord); if (recordAmbientAudio) { recorder.stop(); diff --git a/app/src/main/java/usb/AndroidUSBInputStream.java b/app/src/main/java/usb/AndroidUSBInputStream.java index 0da3e28..2d61159 100644 --- a/app/src/main/java/usb/AndroidUSBInputStream.java +++ b/app/src/main/java/usb/AndroidUSBInputStream.java @@ -2,6 +2,8 @@ import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbEndpoint; +import android.os.Handler; +import android.os.Message; import com.fpvout.digiview.dvr.DVR; @@ -22,9 +24,7 @@ public class AndroidUSBInputStream extends InputStream { private final UsbEndpoint sendEndPoint; private boolean working = false; - - - private DVR dvr; + private Handler inputHandler = null; /** * Class constructor. Instantiates a new {@code AndroidUSBInputStream} @@ -36,10 +36,9 @@ public class AndroidUSBInputStream extends InputStream { * @see UsbDeviceConnection * @see UsbEndpoint */ - public AndroidUSBInputStream( UsbEndpoint readEndpoint, UsbEndpoint sendEndpoint, UsbDeviceConnection connection, DVR dvr) { + public AndroidUSBInputStream( UsbEndpoint readEndpoint, UsbEndpoint sendEndpoint, UsbDeviceConnection connection) { this.usbConnection = connection; this.receiveEndPoint = readEndpoint; - this.dvr = dvr; this.sendEndPoint = sendEndpoint; } @@ -58,13 +57,21 @@ public int read(byte[] buffer, int offset, int length) { usbConnection.bulkTransfer(sendEndPoint, "RMVT".getBytes(), "RMVT".getBytes().length, 2000); receivedBytes = usbConnection.bulkTransfer(receiveEndPoint, buffer, buffer.length, READ_TIMEOUT); } else { - byte[] copiedBuffer = Arrays.copyOf(buffer, buffer.length); - this.dvr.recordVideoDVR(copiedBuffer,offset , copiedBuffer.length); + if (inputHandler != null) { + Message msg = new Message(); + msg.what = offset; //OFFSET + msg.obj = Arrays.copyOf(buffer, buffer.length); + this.inputHandler.sendMessage(msg); + } } return receivedBytes; } + public void setInputStreamListener(Handler inputHandler) { + this.inputHandler = inputHandler; + } + @Override public void close() throws IOException { From 6f17c81f3d1a0aa804c68a3e44543f1836a0a864 Mon Sep 17 00:00:00 2001 From: markusstadtmann Date: Tue, 25 May 2021 15:56:22 +0200 Subject: [PATCH 23/29] changed from handler to Listener --- .../java/com/fpvout/digiview/dvr/DVR.java | 25 ++++++++++--------- .../fpvout/digiview/helpers/DataListener.java | 5 ++++ .../main/java/usb/AndroidUSBInputStream.java | 18 ++++++------- 3 files changed, 25 insertions(+), 23 deletions(-) create mode 100644 app/src/main/java/com/fpvout/digiview/helpers/DataListener.java diff --git a/app/src/main/java/com/fpvout/digiview/dvr/DVR.java b/app/src/main/java/com/fpvout/digiview/dvr/DVR.java index f5b01ae..5012d9f 100644 --- a/app/src/main/java/com/fpvout/digiview/dvr/DVR.java +++ b/app/src/main/java/com/fpvout/digiview/dvr/DVR.java @@ -13,6 +13,7 @@ import com.fpvout.digiview.R; import com.fpvout.digiview.UsbMaskConnection; import com.fpvout.digiview.VideoReaderExoplayer; +import com.fpvout.digiview.helpers.DataListener; import com.fpvout.digiview.helpers.StreamDumper; import com.fpvout.digiview.helpers.ThreadPerTaskExecutor; @@ -75,20 +76,21 @@ public void start() { Toast.makeText(activity, activity.getText(R.string.recording_started), Toast.LENGTH_LONG).show(); ((ImageButton) activity.findViewById(R.id.recordbt)).setImageResource(R.drawable.stop); - connection.getInputStream().setInputStreamListener(new Handler(message -> { - if (streamDumper != null) { - if (isRecording()) { - byte[] buffer = (byte[]) message.obj; - if (buffer != null) { - streamDumper.dump(buffer, message.what, buffer.length); + ThreadPerTaskExecutor executor = new ThreadPerTaskExecutor(); + executor.execute(() -> { + connection.getInputStream().setInputStreamListener(new DataListener() { + @Override + public void calllback(byte[] buffer, int offset, int length) { + if (streamDumper != null) { + if (isRecording()) { + if (buffer != null) { + streamDumper.dump(buffer, offset, length); + } + } } } - } - return true; - })); + }); - ThreadPerTaskExecutor executor = new ThreadPerTaskExecutor(); - executor.execute(() -> { fileName = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss") .format(Calendar.getInstance().getTime()); ambietAudio = "/DigiView_" + fileName + ".aac"; @@ -112,7 +114,6 @@ public void start() { e.printStackTrace(); } } - //start recording (input stream starts collecting data this.recording = true; }); diff --git a/app/src/main/java/com/fpvout/digiview/helpers/DataListener.java b/app/src/main/java/com/fpvout/digiview/helpers/DataListener.java new file mode 100644 index 0000000..84f8336 --- /dev/null +++ b/app/src/main/java/com/fpvout/digiview/helpers/DataListener.java @@ -0,0 +1,5 @@ +package com.fpvout.digiview.helpers; + +public interface DataListener{ + public void calllback(byte[] buffer, int offset, int length); +} diff --git a/app/src/main/java/usb/AndroidUSBInputStream.java b/app/src/main/java/usb/AndroidUSBInputStream.java index 2d61159..5ffe1b3 100644 --- a/app/src/main/java/usb/AndroidUSBInputStream.java +++ b/app/src/main/java/usb/AndroidUSBInputStream.java @@ -2,10 +2,8 @@ import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbEndpoint; -import android.os.Handler; import android.os.Message; - -import com.fpvout.digiview.dvr.DVR; +import com.fpvout.digiview.helpers.DataListener; /** * This class acts as a wrapper to read data from the USB Interface in Android @@ -24,7 +22,7 @@ public class AndroidUSBInputStream extends InputStream { private final UsbEndpoint sendEndPoint; private boolean working = false; - private Handler inputHandler = null; + private DataListener inputListener = null; /** * Class constructor. Instantiates a new {@code AndroidUSBInputStream} @@ -57,19 +55,17 @@ public int read(byte[] buffer, int offset, int length) { usbConnection.bulkTransfer(sendEndPoint, "RMVT".getBytes(), "RMVT".getBytes().length, 2000); receivedBytes = usbConnection.bulkTransfer(receiveEndPoint, buffer, buffer.length, READ_TIMEOUT); } else { - if (inputHandler != null) { - Message msg = new Message(); - msg.what = offset; //OFFSET - msg.obj = Arrays.copyOf(buffer, buffer.length); - this.inputHandler.sendMessage(msg); + if (inputListener != null) { + byte[] bufferCopy = Arrays.copyOf(buffer, buffer.length); + this.inputListener.calllback(bufferCopy, offset, bufferCopy.length); } } return receivedBytes; } - public void setInputStreamListener(Handler inputHandler) { - this.inputHandler = inputHandler; + public void setInputStreamListener(DataListener inputListener) { + this.inputListener = inputListener; } From 6710312150b200333594c23e7b6ae4602b3385a0 Mon Sep 17 00:00:00 2001 From: markusstadtmann Date: Wed, 26 May 2021 05:46:48 +0200 Subject: [PATCH 24/29] added dvr repair function --- .../java/com/fpvout/digiview/dvr/DVR.java | 24 +++++++---- .../com/fpvout/digiview/helpers/Mp4Muxer.java | 43 +++++++++++-------- .../fpvout/digiview/helpers/StreamDumper.java | 2 +- app/src/main/res/values/strings.xml | 1 + 4 files changed, 44 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/com/fpvout/digiview/dvr/DVR.java b/app/src/main/java/com/fpvout/digiview/dvr/DVR.java index 5012d9f..6aebd53 100644 --- a/app/src/main/java/com/fpvout/digiview/dvr/DVR.java +++ b/app/src/main/java/com/fpvout/digiview/dvr/DVR.java @@ -4,28 +4,22 @@ import android.media.MediaRecorder; import android.os.Environment; import android.os.Handler; -import android.os.Looper; -import android.os.Message; import android.util.Log; import android.widget.ImageButton; import android.widget.Toast; import com.fpvout.digiview.R; import com.fpvout.digiview.UsbMaskConnection; -import com.fpvout.digiview.VideoReaderExoplayer; import com.fpvout.digiview.helpers.DataListener; +import com.fpvout.digiview.helpers.Mp4Muxer; import com.fpvout.digiview.helpers.StreamDumper; import com.fpvout.digiview.helpers.ThreadPerTaskExecutor; import java.io.File; import java.io.IOException; -import java.io.InputStream; import java.text.SimpleDateFormat; import java.util.Calendar; -import androidx.annotation.NonNull; -import usb.AndroidUSBInputStream; - public class DVR { private final Activity activity; private boolean recordAmbientAudio; @@ -68,7 +62,21 @@ public void init() throws IOException { } private void repairNotFinishedDVR(){ - + File dvrFolder = new File(defaultFolder); + if (dvrFolder.exists()) { + File[] files = dvrFolder.listFiles(); + for (int i = 0; i < files.length; ++i) { + File file = files[i]; + if (file.getAbsolutePath().endsWith(".h264")) { + File ambientAudio = new File(file.getAbsolutePath().replace(".h264", ".aac")); + if (ambientAudio.exists()) { + Toast.makeText(activity, activity.getString(R.string.repair_dvr), Toast.LENGTH_LONG).show(); + File output = new File(file.getAbsolutePath().replace(".h264", ".mp4")); + new Mp4Muxer(activity, dvrFolder , file, ambientAudio,output, false).start(); + } + } + } + } } public void start() { diff --git a/app/src/main/java/com/fpvout/digiview/helpers/Mp4Muxer.java b/app/src/main/java/com/fpvout/digiview/helpers/Mp4Muxer.java index e1cb45b..8b2f5c9 100644 --- a/app/src/main/java/com/fpvout/digiview/helpers/Mp4Muxer.java +++ b/app/src/main/java/com/fpvout/digiview/helpers/Mp4Muxer.java @@ -28,6 +28,7 @@ import com.fpvout.digiview.MainActivity; import java.io.File; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.channels.FileChannel; @@ -45,19 +46,21 @@ public class Mp4Muxer extends Thread { private final File output; private final Context context; private final File dumpDir; + private boolean thumbnail = false; SeekableByteChannel file; MP4Muxer muxer; BufferH264ES es; - public Mp4Muxer(Context context, File dumpDir , File h264Dump, File ambientAudio, File output) { + public Mp4Muxer(Context context, File dumpDir , File h264Dump, File ambientAudio, File output, boolean thumbnail) { this.context = context; this.dumpDir = dumpDir; this.h264Dump = h264Dump; this.ambientAudioFile = ambientAudio; this.videoFile = new File(output.getAbsolutePath() + ".tmp"); this.output = output; + this.thumbnail = thumbnail; } private void init() throws IOException { @@ -128,23 +131,15 @@ public void run() { muxer.finish(); file.close(); - Movie movie = MovieCreator.build(videoFile.getAbsolutePath()); - AACTrackImpl aacTrack = new AACTrackImpl(new FileDataSourceImpl(ambientAudioFile)); - ClippedTrack aacCroppedTrack = new ClippedTrack(aacTrack, 1, aacTrack.getSamples().size()); - movie.addTrack(aacCroppedTrack); + mergeAudioVideoFiles(videoFile, ambientAudioFile, output); - Container mp4file = new DefaultMp4Builder().build(movie); - - FileOutputStream fileOutputStream = new FileOutputStream(output); - FileChannel fc = fileOutputStream.getChannel(); - mp4file.writeContainer(fc); - fileOutputStream.close(); - - Bitmap thumb = ThumbnailUtils.createVideoThumbnail(output.getAbsolutePath() , MediaStore.Images.Thumbnails.MINI_KIND); - FileOutputStream thumbOutputStream = new FileOutputStream(new File(dumpDir.getAbsolutePath() + "/" + LATEST_THUMB_FILE)); - thumb.compress(Bitmap.CompressFormat.JPEG, 100, thumbOutputStream); - thumbOutputStream.flush(); - thumbOutputStream.close(); + if (this.thumbnail) { + Bitmap thumb = ThumbnailUtils.createVideoThumbnail(output.getAbsolutePath(), MediaStore.Images.Thumbnails.MINI_KIND); + FileOutputStream thumbOutputStream = new FileOutputStream(new File(dumpDir.getAbsolutePath() + "/" + LATEST_THUMB_FILE)); + thumb.compress(Bitmap.CompressFormat.JPEG, 100, thumbOutputStream); + thumbOutputStream.flush(); + thumbOutputStream.close(); + } // add mp4 to gallery MediaScannerConnection.scanFile(context, @@ -159,4 +154,18 @@ public void run() { Log.e("DIGIVIEW", "MUXER: " + exception.getMessage()); } } + + public void mergeAudioVideoFiles(File _videoFile, File _ambientFile, File _output) throws IOException { + Movie movie = MovieCreator.build(_videoFile.getAbsolutePath()); + AACTrackImpl aacTrack = new AACTrackImpl(new FileDataSourceImpl(_ambientFile)); + ClippedTrack aacCroppedTrack = new ClippedTrack(aacTrack, 1, aacTrack.getSamples().size()); + movie.addTrack(aacCroppedTrack); + + Container mp4file = new DefaultMp4Builder().build(movie); + + FileOutputStream fileOutputStream = new FileOutputStream(_output); + FileChannel fc = fileOutputStream.getChannel(); + mp4file.writeContainer(fc); + fileOutputStream.close(); + } } diff --git a/app/src/main/java/com/fpvout/digiview/helpers/StreamDumper.java b/app/src/main/java/com/fpvout/digiview/helpers/StreamDumper.java index 8afa9fd..f015898 100644 --- a/app/src/main/java/com/fpvout/digiview/helpers/StreamDumper.java +++ b/app/src/main/java/com/fpvout/digiview/helpers/StreamDumper.java @@ -57,7 +57,7 @@ public void stop(Handler completeHandler) { fos.close(); if(bytesWritten) { - new Mp4Muxer(this.context, dumpDir, streamDump, streamAmbient,outFile).start(); + new Mp4Muxer(this.context, dumpDir, streamDump, streamAmbient,outFile, true).start(); } } if(!bytesWritten){ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 986bfbb..cd4e64f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -47,4 +47,5 @@ dvr has been saved merging audio and video of the dvr Storage access is required. + repairing dvrs ... \ No newline at end of file From 46280c6fb2100f058c19703a5786887035707819 Mon Sep 17 00:00:00 2001 From: markusstadtmann Date: Wed, 26 May 2021 16:02:05 +0200 Subject: [PATCH 25/29] fixed crash on dvr repair --- app/src/main/java/com/fpvout/digiview/dvr/DVR.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/com/fpvout/digiview/dvr/DVR.java b/app/src/main/java/com/fpvout/digiview/dvr/DVR.java index 6aebd53..9965e33 100644 --- a/app/src/main/java/com/fpvout/digiview/dvr/DVR.java +++ b/app/src/main/java/com/fpvout/digiview/dvr/DVR.java @@ -4,6 +4,7 @@ import android.media.MediaRecorder; import android.os.Environment; import android.os.Handler; +import android.os.Looper; import android.util.Log; import android.widget.ImageButton; import android.widget.Toast; @@ -70,6 +71,7 @@ private void repairNotFinishedDVR(){ if (file.getAbsolutePath().endsWith(".h264")) { File ambientAudio = new File(file.getAbsolutePath().replace(".h264", ".aac")); if (ambientAudio.exists()) { + Looper.prepare(); Toast.makeText(activity, activity.getString(R.string.repair_dvr), Toast.LENGTH_LONG).show(); File output = new File(file.getAbsolutePath().replace(".h264", ".mp4")); new Mp4Muxer(activity, dvrFolder , file, ambientAudio,output, false).start(); From ff5dc681e6fb9c43298e3215b43f36f8514c72de Mon Sep 17 00:00:00 2001 From: markusstadtmann Date: Wed, 26 May 2021 16:03:35 +0200 Subject: [PATCH 26/29] removed toast causes crashes ... --- app/src/main/java/com/fpvout/digiview/MainActivity.java | 4 +++- app/src/main/java/com/fpvout/digiview/dvr/DVR.java | 2 -- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/fpvout/digiview/MainActivity.java b/app/src/main/java/com/fpvout/digiview/MainActivity.java index 3457319..e97f839 100644 --- a/app/src/main/java/com/fpvout/digiview/MainActivity.java +++ b/app/src/main/java/com/fpvout/digiview/MainActivity.java @@ -253,7 +253,7 @@ private void cancelToolbarAnimation() { } private void toggleButton() { - if (toolbarAlpha == 0.9 && overlayView.getVisibility() == View.VISIBLE) return; + if (overlayView.getVisibility() == View.VISIBLE) return; // cancel any pending delayed animations first cancelToolbarAnimation(); @@ -293,6 +293,8 @@ private void autoHideToolbar() { private void showOverlay(int textId, OverlayStatus connected) { overlayView.show(textId, connected); + toolbar.setAlpha(0.7f); + toolbar.setTranslationY(0); updateWatermark(); autoHideToolbar(); updateVideoZoom(); diff --git a/app/src/main/java/com/fpvout/digiview/dvr/DVR.java b/app/src/main/java/com/fpvout/digiview/dvr/DVR.java index 9965e33..675e756 100644 --- a/app/src/main/java/com/fpvout/digiview/dvr/DVR.java +++ b/app/src/main/java/com/fpvout/digiview/dvr/DVR.java @@ -71,8 +71,6 @@ private void repairNotFinishedDVR(){ if (file.getAbsolutePath().endsWith(".h264")) { File ambientAudio = new File(file.getAbsolutePath().replace(".h264", ".aac")); if (ambientAudio.exists()) { - Looper.prepare(); - Toast.makeText(activity, activity.getString(R.string.repair_dvr), Toast.LENGTH_LONG).show(); File output = new File(file.getAbsolutePath().replace(".h264", ".mp4")); new Mp4Muxer(activity, dvrFolder , file, ambientAudio,output, false).start(); } From 4f6d55cc2ad6e75b27f7330696524baaa167a134 Mon Sep 17 00:00:00 2001 From: markusstadtmann Date: Wed, 26 May 2021 16:30:07 +0200 Subject: [PATCH 27/29] on overlay visible show toolbar as well --- app/src/main/java/com/fpvout/digiview/MainActivity.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/fpvout/digiview/MainActivity.java b/app/src/main/java/com/fpvout/digiview/MainActivity.java index e97f839..841de7e 100644 --- a/app/src/main/java/com/fpvout/digiview/MainActivity.java +++ b/app/src/main/java/com/fpvout/digiview/MainActivity.java @@ -78,6 +78,7 @@ public class MainActivity extends AppCompatActivity implements UsbDeviceListener private ScaleGestureDetector scaleGestureDetector; private SharedPreferences sharedPreferences; private static final String ShowWatermark = "ShowWatermark"; + private boolean overlayIsShown = false; @Override protected void onCreate(Bundle savedInstanceState) { @@ -280,7 +281,7 @@ public void onAnimationEnd(Animator animation) { private void autoHideToolbar() { if (overlayView.getVisibility() == View.VISIBLE) return; - if (toolbarAlpha == 0) return; + if (overlayIsShown) return; toolbar.postDelayed(() -> { toolbarAlpha = 0; @@ -293,8 +294,9 @@ private void autoHideToolbar() { private void showOverlay(int textId, OverlayStatus connected) { overlayView.show(textId, connected); - toolbar.setAlpha(0.7f); - toolbar.setTranslationY(0); + overlayIsShown = true; + toolbar.setTranslationX(0); + toolbar.setAlpha(1); updateWatermark(); autoHideToolbar(); updateVideoZoom(); @@ -303,6 +305,7 @@ private void showOverlay(int textId, OverlayStatus connected) { private void hideOverlay() { overlayView.hide(); + overlayIsShown = false; updateWatermark(); autoHideToolbar(); updateVideoZoom(); From c5ef2378a19e7c9c9cb99987aab11356c329fba0 Mon Sep 17 00:00:00 2001 From: Jimmy Lucidarme Date: Mon, 22 Nov 2021 19:31:44 +0100 Subject: [PATCH 28/29] reorganize mainActivity to facilitate merge --- .../com/fpvout/digiview/MainActivity.java | 242 +++++++++--------- 1 file changed, 120 insertions(+), 122 deletions(-) diff --git a/app/src/main/java/com/fpvout/digiview/MainActivity.java b/app/src/main/java/com/fpvout/digiview/MainActivity.java index 841de7e..d9ab813 100644 --- a/app/src/main/java/com/fpvout/digiview/MainActivity.java +++ b/app/src/main/java/com/fpvout/digiview/MainActivity.java @@ -80,6 +80,95 @@ public class MainActivity extends AppCompatActivity implements UsbDeviceListener private static final String ShowWatermark = "ShowWatermark"; private boolean overlayIsShown = false; + private void setupGestureDetectors() { + gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + toggleButton(); + return super.onSingleTapConfirmed(e); + } + + @Override + public boolean onDoubleTap(MotionEvent e) { + mVideoReader.toggleZoom(); + return super.onDoubleTap(e); + } + }); + + scaleGestureDetector = new ScaleGestureDetector(this, new ScaleGestureDetector.SimpleOnScaleGestureListener() { + @Override + public void onScaleEnd(ScaleGestureDetector detector) { + if (detector.getScaleFactor() < 1) { + mVideoReader.zoomOut(); + } else { + mVideoReader.zoomIn(); + } + } + }); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + gestureDetector.onTouchEvent(event); + scaleGestureDetector.onTouchEvent(event); + + return super.onTouchEvent(event); + } + + private void updateWatermark() { + if (overlayView.getVisibility() == View.VISIBLE) { + watermarkView.setAlpha(0); + return; + } + + if (sharedPreferences.getBoolean(ShowWatermark, true)) { + watermarkView.setAlpha(0.3F); + } else { + watermarkView.setAlpha(0F); + } + } + + private void updateVideoZoom() { + if (sharedPreferences.getBoolean(VideoZoomedIn, true)) { + mVideoReader.zoomIn(); + } else { + mVideoReader.zoomOut(); + } + } + + private void cancelToolbarAnimation() { + Handler handler = toolbar.getHandler(); + if (handler != null) { + toolbar.getHandler().removeCallbacksAndMessages(null); + } + } + + private void toggleButton() { + if (overlayView.getVisibility() == View.VISIBLE) return; + // cancel any pending delayed animations first + cancelToolbarAnimation(); + + int translation = 0; + if (toolbarAlpha == 0.7f) { + toolbarAlpha = 0; + translation = 60; + } else { + toolbarAlpha = 0.7f; + } + updateDVRThumb(); + toolbar.animate() + .alpha(toolbarAlpha) + .translationX(translation) + .setDuration(shortAnimationDuration) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + autoHideToolbar(); + updateDVRThumb(); + } + }); + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -190,128 +279,6 @@ private void setFullscreen() { } } - private void setupGestureDetectors() { - gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() { - @Override - public boolean onSingleTapConfirmed(MotionEvent e) { - toggleButton(); - return super.onSingleTapConfirmed(e); - } - - @Override - public boolean onDoubleTap(MotionEvent e) { - mVideoReader.toggleZoom(); - return super.onDoubleTap(e); - } - }); - - scaleGestureDetector = new ScaleGestureDetector(this, new ScaleGestureDetector.SimpleOnScaleGestureListener() { - @Override - public void onScaleEnd(ScaleGestureDetector detector) { - if (detector.getScaleFactor() < 1) { - mVideoReader.zoomOut(); - } else { - mVideoReader.zoomIn(); - } - } - }); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - gestureDetector.onTouchEvent(event); - scaleGestureDetector.onTouchEvent(event); - - return super.onTouchEvent(event); - } - - private void updateWatermark() { - if (overlayView.getVisibility() == View.VISIBLE) { - watermarkView.setAlpha(0); - return; - } - - if (sharedPreferences.getBoolean(ShowWatermark, true)) { - watermarkView.setAlpha(0.3F); - } else { - watermarkView.setAlpha(0F); - } - } - - private void updateVideoZoom() { - if (sharedPreferences.getBoolean(VideoZoomedIn, true)) { - mVideoReader.zoomIn(); - } else { - mVideoReader.zoomOut(); - } - } - - private void cancelToolbarAnimation() { - Handler handler = toolbar.getHandler(); - if (handler != null) { - toolbar.getHandler().removeCallbacksAndMessages(null); - } - } - - private void toggleButton() { - if (overlayView.getVisibility() == View.VISIBLE) return; - // cancel any pending delayed animations first - cancelToolbarAnimation(); - - int translation = 0; - if (toolbarAlpha == 0.7f) { - toolbarAlpha = 0; - translation = 60; - } else { - toolbarAlpha = 0.7f; - } - updateDVRThumb(); - toolbar.animate() - .alpha(toolbarAlpha) - .translationX(translation) - .setDuration(shortAnimationDuration) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - autoHideToolbar(); - updateDVRThumb(); - } - }); - } - - private void autoHideToolbar() { - if (overlayView.getVisibility() == View.VISIBLE) return; - if (overlayIsShown) return; - - toolbar.postDelayed(() -> { - toolbarAlpha = 0; - toolbar.animate() - .alpha(0) - .translationX(60) - .setDuration(shortAnimationDuration); - }, 3000); - } - - private void showOverlay(int textId, OverlayStatus connected) { - overlayView.show(textId, connected); - overlayIsShown = true; - toolbar.setTranslationX(0); - toolbar.setAlpha(1); - updateWatermark(); - autoHideToolbar(); - updateVideoZoom(); - - } - - private void hideOverlay() { - overlayView.hide(); - overlayIsShown = false; - updateWatermark(); - autoHideToolbar(); - updateVideoZoom(); - } - - @Override public void usbDeviceApproved(UsbDevice device) { Log.i(TAG, "USB - usbDevice approved"); @@ -419,6 +386,24 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permis } } + private void showOverlay(int textId, OverlayStatus connected) { + overlayView.show(textId, connected); + overlayIsShown = true; + toolbar.setTranslationX(0); + toolbar.setAlpha(1); + updateWatermark(); + autoHideToolbar(); + updateVideoZoom(); + + } + + private void hideOverlay() { + overlayView.hide(); + overlayIsShown = false; + updateWatermark(); + autoHideToolbar(); + updateVideoZoom(); + } @Override protected void onStop() { @@ -450,6 +435,19 @@ protected void onDestroy() { usbConnected = false; } + private void autoHideToolbar() { + if (overlayView.getVisibility() == View.VISIBLE) return; + if (overlayIsShown) return; + + toolbar.postDelayed(() -> { + toolbarAlpha = 0; + toolbar.animate() + .alpha(0) + .translationX(60) + .setDuration(shortAnimationDuration); + }, 3000); + } + private void checkDataCollectionAgreement() { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); boolean dataCollectionAccepted = preferences.getBoolean("dataCollectionAccepted", false); From e4af1f5f857c232075d629ca83a63492c46b067b Mon Sep 17 00:00:00 2001 From: Jimmy Lucidarme Date: Wed, 1 Dec 2021 20:36:00 +0100 Subject: [PATCH 29/29] minor tweaking of the DVR toolbar look / fix typo --- .../com/fpvout/digiview/MainActivity.java | 55 ++++++++----------- .../java/com/fpvout/digiview/dvr/DVR.java | 23 ++++---- app/src/main/res/layout/activity_main.xml | 39 ++++++------- 3 files changed, 54 insertions(+), 63 deletions(-) diff --git a/app/src/main/java/com/fpvout/digiview/MainActivity.java b/app/src/main/java/com/fpvout/digiview/MainActivity.java index 3fbd08b..d45f336 100644 --- a/app/src/main/java/com/fpvout/digiview/MainActivity.java +++ b/app/src/main/java/com/fpvout/digiview/MainActivity.java @@ -18,10 +18,9 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.os.Message; +import android.os.Handler; import android.os.StrictMode; import android.preference.PreferenceManager; -import android.os.Handler; import android.util.Log; import android.view.GestureDetector; import android.view.MotionEvent; @@ -34,28 +33,21 @@ import android.widget.RelativeLayout; import android.widget.Toast; -import androidx.activity.result.ActivityResult; -import androidx.activity.result.ActivityResultCallback; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; -import androidx.core.view.WindowInsetsCompat; -import androidx.core.view.WindowInsetsControllerCompat; -import androidx.core.app.ActivityCompat; -import io.sentry.SentryLevel; -import io.sentry.android.core.SentryAndroid; -import static com.fpvout.digiview.UsbMaskConnection.ACTION_USB_PERMISSION; import com.fpvout.digiview.dvr.DVR; import java.io.File; import java.io.IOException; -import java.nio.file.Files; import java.util.HashMap; +import io.sentry.SentryLevel; +import io.sentry.android.core.SentryAndroid; import static com.fpvout.digiview.VideoReaderExoplayer.VideoZoomedIn; @@ -93,7 +85,6 @@ public class MainActivity extends AppCompatActivity implements UsbDeviceListener boolean dataCollectionAccepted = preferences.getBoolean("dataCollectionAccepted", false); if (result.getResultCode() == Activity.RESULT_OK && dataCollectionAccepted) { - Log.d(TAG, "launchDataCollectionActivity: " + dataCollectionAccepted); SentryAndroid.init(getApplicationContext(), options -> options.setBeforeSend((event, hint) -> { if (SentryLevel.DEBUG.equals(event.getLevel())) return null; @@ -107,7 +98,7 @@ private void setupGestureDetectors() { gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() { @Override public boolean onSingleTapConfirmed(MotionEvent e) { - toggleSettingsButton(); + toggleToolbar(); return super.onSingleTapConfirmed(e); } @@ -160,22 +151,22 @@ private void updateVideoZoom() { } private void cancelButtonAnimation() { - Handler handler = settingsButton.getHandler(); + Handler handler = toolbar.getHandler(); if (handler != null) { - settingsButton.getHandler().removeCallbacksAndMessages(null); + toolbar.getHandler().removeCallbacksAndMessages(null); } } - private void showSettingsButton() { + private void showToolbar() { cancelButtonAnimation(); if (overlayView.getVisibility() == View.VISIBLE) { buttonAlpha = 1; - settingsButton.setAlpha(1); + toolbar.setAlpha(1); } } - private void toggleSettingsButton() { + private void toggleToolbar() { if (buttonAlpha == 1 && overlayView.getVisibility() == View.VISIBLE) return; // cancel any pending delayed animations first @@ -189,14 +180,14 @@ private void toggleSettingsButton() { buttonAlpha = 1; } updateDVRThumb(); - settingsButton.animate() + toolbar.animate() .alpha(buttonAlpha) .translationX(translation) .setDuration(shortAnimationDuration) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - autoHideSettingsButton(); + autoHideToolbar(); updateDVRThumb(); } }); @@ -302,7 +293,7 @@ protected void onCreate(Bundle savedInstanceState) { } else { showOverlay(R.string.waiting_for_usb_device, OverlayStatus.Disconnected); } - }; + } } @Override @@ -363,7 +354,7 @@ private void connect() { mVideoReader.start(); updateDVRThumb(); updateWatermark(); - autoHideSettingsButton(); + autoHideToolbar(); showOverlay(R.string.waiting_for_video, OverlayStatus.Connected); } @@ -385,8 +376,8 @@ public void onResume() { } - settingsButton.setAlpha(1); - autoHideSettingsButton(); + toolbar.setAlpha(1); + autoHideToolbar(); updateWatermark(); updateVideoZoom(); @@ -460,18 +451,18 @@ private boolean onVideoReaderEvent(VideoReaderExoplayer.VideoReaderEventMessageC private void showOverlay(int textId, OverlayStatus connected) { overlayView.show(textId, connected); overlayIsShown = true; - settingsButton.setTranslationX(0); - settingsButton.setAlpha(1); + toolbar.setTranslationX(0); + toolbar.setAlpha(1); updateWatermark(); - showSettingsButton(); + showToolbar(); } private void hideOverlay() { overlayView.hide(); overlayIsShown = false; updateWatermark(); - showSettingsButton(); - autoHideSettingsButton(); + showToolbar(); + autoHideToolbar(); } private void disconnect() { @@ -507,13 +498,13 @@ protected void onDestroy() { usbConnected = false; } - private void autoHideSettingsButton() { + private void autoHideToolbar() { if (overlayView.getVisibility() == View.VISIBLE) return; if (buttonAlpha == 0) return; - settingsButton.postDelayed(() -> { + toolbar.postDelayed(() -> { buttonAlpha = 0; - settingsButton.animate() + toolbar.animate() .alpha(0) .translationX(60) .setDuration(shortAnimationDuration); diff --git a/app/src/main/java/com/fpvout/digiview/dvr/DVR.java b/app/src/main/java/com/fpvout/digiview/dvr/DVR.java index 675e756..7317d9d 100644 --- a/app/src/main/java/com/fpvout/digiview/dvr/DVR.java +++ b/app/src/main/java/com/fpvout/digiview/dvr/DVR.java @@ -4,7 +4,6 @@ import android.media.MediaRecorder; import android.os.Environment; import android.os.Handler; -import android.os.Looper; import android.util.Log; import android.widget.ImageButton; import android.widget.Toast; @@ -23,27 +22,27 @@ public class DVR { private final Activity activity; - private boolean recordAmbientAudio; + private final boolean recordAmbientAudio; private MediaRecorder recorder; private boolean recording = false; private static DVR instance; private static final String DVR_LOG_TAG = "DVR"; private String defaultFolder = ""; - private String ambietAudio; + private final StreamDumper streamDumper; private String videoFile; private String dvrFile; private String fileName; - private StreamDumper streamDumper; - private UsbMaskConnection connection; + private final UsbMaskConnection connection; + private String ambientAudio; private static Handler updateAfterRecord; public static final String LATEST_THUMB_FILE = "latest.jpeg"; - DVR(Activity activity, boolean recordAmbientAudio, Handler updateAfterRecord, UsbMaskConnection connection){ + DVR(Activity activity, boolean recordAmbientAudio, Handler updateAfterRecord, UsbMaskConnection connection) { this.activity = activity; this.connection = connection; this.recordAmbientAudio = recordAmbientAudio; - this.updateAfterRecord = updateAfterRecord; - defaultFolder = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + "/" + this.activity.getApplicationInfo().loadLabel(this.activity.getPackageManager()).toString(); + DVR.updateAfterRecord = updateAfterRecord; + defaultFolder = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + "/" + this.activity.getApplicationInfo().loadLabel(this.activity.getPackageManager()).toString(); streamDumper = new StreamDumper(activity, defaultFolder); } @@ -101,8 +100,8 @@ public void calllback(byte[] buffer, int offset, int length) { fileName = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss") .format(Calendar.getInstance().getTime()); - ambietAudio = "/DigiView_" + fileName + ".aac"; - videoFile ="/DigiView_"+fileName+".h264"; + ambientAudio = "/DigiView_" + fileName + ".aac"; + videoFile = "/DigiView_" + fileName + ".h264"; dvrFile = "/DigiView_" + fileName + ".mp4"; Log.d(DVR_LOG_TAG, "creating folder for dvr saving ..."); @@ -111,10 +110,10 @@ public void calllback(byte[] buffer, int offset, int length) { objFolder.mkdir(); Log.d(DVR_LOG_TAG, "start recording ..."); - streamDumper.init(videoFile, ambietAudio, dvrFile); + streamDumper.init(videoFile, ambientAudio, dvrFile); if (recordAmbientAudio) { Log.d(DVR_LOG_TAG, "starting ambient recording ..."); - recorder.setOutputFile(defaultFolder + ambietAudio); + recorder.setOutputFile(defaultFolder + ambientAudio); try { recorder.prepare(); recorder.start(); // Ambient Audio Recording is now started diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 3bc2c41..363ee98 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -47,22 +47,25 @@ android:id="@+id/toolbar" android:layout_width="80dp" android:layout_height="0dp" - android:background="@color/black" + android:background="@android:color/transparent" android:orientation="vertical" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent"> + android:adjustViewBounds="false" + android:background="@android:color/transparent" + android:scaleType="fitXY" + app:srcCompat="@drawable/exo_ic_settings" /> + android:background="@drawable/rounded_corners" + android:scaleType="fitXY" /> +