diff --git a/.gitignore b/.gitignore
index 293d9ae..a722239 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,7 @@
.externalNativeBuild
.cxx
local.properties
+app/debug
+app/alpha
+app/beta
+app/release
diff --git a/app/build.gradle b/app/build.gradle
index de58b67..91434fd 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,5 +1,6 @@
plugins {
id 'com.android.application'
+ id "com.starter.easylauncher" version "4.0.0"
}
android {
@@ -12,7 +13,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 +66,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.1'
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/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 7df7ca4..fd85baf 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -12,21 +12,20 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
- android:theme="@style/Theme.Digiview"
- >
+ android:theme="@style/Theme.Digiview">
+ android:theme="@style/Theme.AppCompat.Dialog" />
+ android:label="@string/title_activity_settings" />
+ android:configChanges="orientation|keyboardHidden"
+ android:launchMode="singleTask"
+ android:screenOrientation="sensorLandscape">
@@ -38,14 +37,22 @@
-
-
-
-
+
+
+
+
new Extractor[] {new H264Extractor()};
-
private static int MAX_SYNC_FRAME_SIZE = 131072;
-
- private long firstSampleTimestampUs;
private static long sampleTime = 10000; // todo: try to lower this. it directly infer on speed and latency. this should be equal to 16666 to reach 60fps but works better with lower value
private final H264Reader reader;
private final ParsableByteArray sampleData;
-
+ private long firstSampleTimestampUs;
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 +66,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..d6bc5b6 100644
--- a/app/src/main/java/com/fpvout/digiview/InputStreamBufferedDataSource.java
+++ b/app/src/main/java/com/fpvout/digiview/InputStreamBufferedDataSource.java
@@ -1,8 +1,9 @@
package com.fpvout.digiview;
-import android.content.Context;
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;
@@ -19,10 +20,8 @@ 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;
private CircularByteBuffer readBuffer;
@@ -30,20 +29,20 @@ 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();
}
@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)
@@ -63,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);
@@ -71,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 7b10606..fe1634c 100644
--- a/app/src/main/java/com/fpvout/digiview/InputStreamDataSource.java
+++ b/app/src/main/java/com/fpvout/digiview/InputStreamDataSource.java
@@ -1,8 +1,9 @@
package com.fpvout.digiview;
-import android.content.Context;
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;
@@ -13,25 +14,23 @@
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;
}
@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)
@@ -51,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 af99d9e..6643d65 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;
@@ -21,28 +20,25 @@
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 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.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 float buttonAlpha = 1;
- private View settingsButton;
- private View watermarkView;
- private OverlayView overlayView;
- PendingIntent permissionIntent;
+ private static final String ShowWatermark = "ShowWatermark";
UsbDeviceBroadcastReceiver usbDeviceBroadcastReceiver;
UsbManager usbManager;
UsbDevice usbDevice;
@@ -50,10 +46,14 @@ public class MainActivity extends AppCompatActivity implements UsbDeviceListener
VideoReaderExoplayer mVideoReader;
boolean usbConnected = false;
SurfaceView fpvView;
+ private int shortAnimationDuration;
+ private float buttonAlpha = 1;
+ private View settingsButton;
+ private View watermarkView;
+ private OverlayView overlayView;
private GestureDetector gestureDetector;
private ScaleGestureDetector scaleGestureDetector;
private SharedPreferences sharedPreferences;
- private static final String ShowWatermark = "ShowWatermark";
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -65,58 +65,48 @@ 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);
+ shortAnimationDuration = getResources().getInteger(android.R.integer.config_shortAnimTime);
// Enable resizing animations
((ViewGroup) findViewById(R.id.mainLayout)).getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
setupGestureDetectors();
- mUsbMaskConnection = new UsbMaskConnection();
- Handler videoReaderEventListener = new Handler(this.getMainLooper(), msg -> onVideoReaderEvent((VideoReaderExoplayer.VideoReaderEventMessageCode) msg.obj));
+ settingsButton.setOnClickListener(v -> {
+ Intent intent = new Intent(v.getContext(), SettingsActivity.class);
+ v.getContext().startActivity(intent);
+ });
- 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();
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.Disconnected);
@@ -124,6 +114,20 @@ public void onClick(View v) {
}
}
+ private void setFullscreen() {
+ 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();
+ }
+ }
+
private void setupGestureDetectors() {
gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {
@Override
@@ -183,18 +187,18 @@ private void updateVideoZoom() {
private void cancelButtonAnimation() {
Handler handler = settingsButton.getHandler();
if (handler != null) {
- settingsButton.getHandler().removeCallbacksAndMessages(null);
+ handler.removeCallbacksAndMessages(null);
}
}
- 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;
@@ -223,17 +227,30 @@ 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 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");
@@ -246,35 +263,13 @@ public void usbDeviceApproved(UsbDevice device) {
public void usbDeviceDetached() {
Log.i(TAG, "USB - usbDevice detached");
showOverlay(R.string.usb_device_detached_waiting, OverlayStatus.Disconnected);
- this.onStop();
+ disconnect();
}
- 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();
@@ -288,24 +283,16 @@ 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);
}
}
@@ -315,38 +302,18 @@ public void onResume() {
updateVideoZoom();
}
- private boolean onVideoReaderEvent(VideoReaderExoplayer.VideoReaderEventMessageCode m) {
- if (VideoReaderExoplayer.VideoReaderEventMessageCode.WAITING_FOR_VIDEO.equals(m)) {
- Log.d(TAG, "event: WAITING_FOR_VIDEO");
- showOverlay(R.string.waiting_for_video, OverlayStatus.Connected);
- } else if (VideoReaderExoplayer.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();
- showSettingsButton();
- }
-
- private void hideOverlay() {
- overlayView.hide();
- updateWatermark();
- showSettingsButton();
- autoHideSettingsButton();
+ private void disconnect() {
+ mUsbMaskConnection.stop();
+ mVideoReader.stop();
+ usbConnected = false;
}
@Override
protected void onStop() {
super.onStop();
Log.d(TAG, "APP - On Stop");
-
- mUsbMaskConnection.stop();
- mVideoReader.stop();
- usbConnected = false;
+ disconnect();
}
@Override
@@ -369,33 +336,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;
- }));
- }
-
- }
- } //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/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 89e4965..9dc7067 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;
@@ -33,17 +31,14 @@ static PerformancePreset getPreset(PresetType p) {
return new PerformancePreset(30720, 200, 32768, 65536, 0, 0, DataSourceType.BUFFERED_INPUT_STREAM);
case LEGACY_BUFFERED:
return new PerformancePreset(30720, 300, 32768, 65536, 34, 34, DataSourceType.BUFFERED_INPUT_STREAM);
+ case VIDEO_STREAM_SERVICE:
+ return new PerformancePreset(131072, 10000, 500, 2000, 17, 17, DataSourceType.VIDEO_STREAM_SERVICE);
case DEFAULT:
default:
return new PerformancePreset(131072, 10000, 500, 2000, 17, 17, DataSourceType.INPUT_STREAM);
}
}
- public enum DataSourceType {
- INPUT_STREAM,
- BUFFERED_INPUT_STREAM
- }
-
static PerformancePreset getPreset(String p) {
switch (p) {
case "conservative":
@@ -54,21 +49,16 @@ static PerformancePreset getPreset(String p) {
return getPreset(PresetType.LEGACY);
case "new_legacy":
return getPreset(PresetType.LEGACY_BUFFERED);
+ case "video_stream_service":
+ return getPreset(PresetType.VIDEO_STREAM_SERVICE);
case "default":
default:
return getPreset(PresetType.DEFAULT);
}
}
- public enum PresetType {
- DEFAULT,
- CONSERVATIVE,
- AGGRESSIVE,
- LEGACY,
- LEGACY_BUFFERED
- }
-
@Override
+ @NonNull
public String toString() {
return "PerformancePreset{" +
"h264ReaderMaxSyncFrameSize=" + h264ReaderMaxSyncFrameSize +
@@ -80,4 +70,20 @@ public String toString() {
", dataSourceType=" + dataSourceType +
'}';
}
+
+ public enum PresetType {
+ DEFAULT,
+ CONSERVATIVE,
+ AGGRESSIVE,
+ LEGACY,
+ LEGACY_BUFFERED,
+ VIDEO_STREAM_SERVICE
+ }
+
+
+ public enum DataSourceType {
+ INPUT_STREAM,
+ BUFFERED_INPUT_STREAM,
+ VIDEO_STREAM_SERVICE
+ }
}
diff --git a/app/src/main/java/com/fpvout/digiview/UsbDeviceBroadcastReceiver.java b/app/src/main/java/com/fpvout/digiview/UsbDeviceBroadcastReceiver.java
index db64b09..a2f75b9 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;
}
@@ -21,7 +22,7 @@ public void onReceive(Context context, Intent intent) {
UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
- if(device != null){
+ if (device != null) {
listener.usbDeviceApproved(device);
}
}
diff --git a/app/src/main/java/com/fpvout/digiview/UsbMaskConnection.java b/app/src/main/java/com/fpvout/digiview/UsbMaskConnection.java
index 1d6d90e..57e6245 100644
--- a/app/src/main/java/com/fpvout/digiview/UsbMaskConnection.java
+++ b/app/src/main/java/com/fpvout/digiview/UsbMaskConnection.java
@@ -1,52 +1,69 @@
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 {
- private final byte[] magicPacket = "RMVT".getBytes();
- private UsbDeviceConnection usbConnection;
- private UsbDevice device;
- private UsbInterface usbInterface;
+ 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;
AndroidUSBInputStream mInputStream;
AndroidUSBOutputStream mOutputStream;
+ private UsbDeviceConnection usbConnection;
+ private UsbInterface usbInterface;
private boolean ready = false;
+ private VideoStreamService videoStreamService;
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(){
- mOutputStream.write(magicPacket);
+ videoStreamService.start();
}
public void stop() {
ready = false;
try {
+ if (videoStreamService != null)
+ videoStreamService.stop();
+
if (mInputStream != null)
mInputStream.close();
if (mOutputStream != null)
mOutputStream.close();
- } catch (IOException e) {
+ } catch (IOException | InterruptedException e) {
e.printStackTrace();
}
@@ -59,4 +76,20 @@ public void stop() {
public boolean isReady() {
return ready;
}
+
+ public VideoStreamService getVideoStreamService() {
+ return videoStreamService;
+ }
+
+ 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);
+ videoStreamService = new VideoStreamService(mInputStream, mOutputStream);
+ 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 7c18bd0..7146039 100644
--- a/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java
+++ b/app/src/main/java/com/fpvout/digiview/VideoReaderExoplayer.java
@@ -5,17 +5,16 @@
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
-import android.os.Message;
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;
@@ -27,23 +26,24 @@
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";
+ static final String VideoZoomedIn = "VideoZoomedIn";
+ private static final String TAG = "DIGIVIEW";
private final SurfaceView surfaceView;
+ private final Context context;
+ private final SharedPreferences sharedPreferences;
+ private SimpleExoPlayer mPlayer;
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;
VideoReaderExoplayer(SurfaceView videoSurface, Context c) {
surfaceView = videoSurface;
@@ -51,9 +51,12 @@ public class VideoReaderExoplayer {
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(c);
}
- VideoReaderExoplayer(SurfaceView videoSurface, Context c, Handler v) {
- this(videoSurface, c);
- videoReaderEventListener = v;
+ public void setVideoPlayingEventListener(VideoPlayingListener listener) {
+ this.videoPlayingListener = listener;
+ }
+
+ public void setVideoWaitingEventListener(VideoWaitingListener listener) {
+ this.videoWaitingListener = listener;
}
public void setUsbMaskConnection(UsbMaskConnection connection) {
@@ -65,95 +68,93 @@ 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);
-
- DataSpec dataSpec = new DataSpec(Uri.EMPTY, 0, C.LENGTH_UNSET);
-
- Log.d(TAG, "preset: " + performancePreset);
-
- DataSource.Factory dataSourceFactory = () -> {
- switch (performancePreset.dataSourceType){
- case INPUT_STREAM:
- return (DataSource) new InputStreamDataSource(context, dataSpec, inputStream);
- case BUFFERED_INPUT_STREAM:
- default:
- return (DataSource) new InputStreamBufferedDataSource(context, dataSpec, inputStream);
- }
- };
-
- ExtractorsFactory extractorsFactory = () ->new Extractor[] {new H264Extractor(performancePreset.h264ReaderMaxSyncFrameSize, performancePreset.h264ReaderSampleTime)};
- MediaSource mediaSource = new ProgressiveMediaSource.Factory(dataSourceFactory, extractorsFactory).createMediaSource(MediaItem.fromUri(Uri.EMPTY));
- mPlayer.setMediaSource(mediaSource);
-
- 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());
- break;
- case ExoPlaybackException.TYPE_UNEXPECTED:
- Log.e(TAG, "PLAYER_SOURCE - TYPE_UNEXPECTED: " + error.getSourceException().getMessage());
- break;
- }
+ 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);
+
+ Log.d(TAG, "preset: " + performancePreset);
+
+ DataSource.Factory dataSourceFactory = () -> {
+ switch (performancePreset.dataSourceType) {
+ case INPUT_STREAM:
+ return (DataSource) new InputStreamDataSource(dataSpec, inputStream);
+ case BUFFERED_INPUT_STREAM:
+ return (DataSource) new InputStreamBufferedDataSource(dataSpec, inputStream);
+ case VIDEO_STREAM_SERVICE:
+ default:
+ VideoStreamServiceDataSource v = new VideoStreamServiceDataSource(dataSpec, mUsbMaskConnection.getVideoStreamService());
+ mUsbMaskConnection.start();
+ return v;
+ }
+ };
+
+ ExtractorsFactory extractorsFactory = () -> new Extractor[]{new H264Extractor(performancePreset.h264ReaderMaxSyncFrameSize, performancePreset.h264ReaderSampleTime)};
+ MediaSource mediaSource = new ProgressiveMediaSource.Factory(dataSourceFactory, extractorsFactory).createMediaSource(MediaItem.fromUri(Uri.EMPTY));
+ mPlayer.setMediaSource(mediaSource);
+
+ mPlayer.prepare();
+ mPlayer.play();
+ mPlayer.addListener(new Player.Listener() {
+ @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.getMessage());
+ break;
+ case ExoPlaybackException.TYPE_RENDERER:
+ Log.e(TAG, "PLAYER_SOURCE - TYPE_RENDERER: " + error.getRendererException().getMessage());
+ break;
+ case ExoPlaybackException.TYPE_UNEXPECTED:
+ Log.e(TAG, "PLAYER_SOURCE - TYPE_UNEXPECTED: " + error.getUnexpectedException().getMessage());
+ break;
}
+ }
- @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");
- sendEvent(VideoReaderEventMessageCode.WAITING_FOR_VIDEO); // 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");
- sendEvent(VideoReaderEventMessageCode.VIDEO_PLAYING); // 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);
}
+ }
});
}
- 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;
@@ -163,12 +164,12 @@ public void toggleZoom() {
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;
}
@@ -202,5 +203,11 @@ public void stop() {
mPlayer.release();
}
- public enum VideoReaderEventMessageCode {WAITING_FOR_VIDEO, VIDEO_PLAYING}
+ public interface VideoPlayingListener {
+ void onVideoPlaying();
+ }
+
+ public interface VideoWaitingListener {
+ void onVideoWaiting();
+ }
}
diff --git a/app/src/main/java/com/fpvout/digiview/VideoStreamService.java b/app/src/main/java/com/fpvout/digiview/VideoStreamService.java
new file mode 100644
index 0000000..49795a3
--- /dev/null
+++ b/app/src/main/java/com/fpvout/digiview/VideoStreamService.java
@@ -0,0 +1,82 @@
+package com.fpvout.digiview;
+
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+
+/**
+ * Consume an inputStream and make it available to all the listeners
+ */
+public class VideoStreamService {
+
+ private static final int READ_BUFFER_SIZE = 131072;
+ private static final int MAX_VIDEO_STREAM_LISTENER = 10;
+ private static final String TAG = "VideoStreamService";
+ private final ArrayList videoStreamListeners;
+ InputStream videoStream;
+ private final byte[] magicPacket = "RMVT".getBytes();
+ private boolean isRunning = false;
+ private Thread streamServiceThread;
+ OutputStream outStream;
+
+ public VideoStreamService(InputStream inputStream, OutputStream outputStream) {
+ videoStream = inputStream;
+ outStream = outputStream;
+ videoStreamListeners = new ArrayList<>();
+ }
+
+ public void start() {
+ Log.d(TAG, "streamServiceThread started");
+ if (!isRunning) {
+ isRunning = true;
+ streamServiceThread = new Thread(() -> {
+ while (isRunning) {
+ try {
+ outStream.write(magicPacket);
+ byte[] buffer = new byte[READ_BUFFER_SIZE];
+ int bytesReceived = videoStream.read(buffer, 0, READ_BUFFER_SIZE);
+ if (bytesReceived >= 0) {
+ Log.d(TAG, "bytesReceived : " + bytesReceived);
+ for (VideoStreamListener v : videoStreamListeners) {
+ v.onVideoStreamData(buffer, bytesReceived);
+ }
+ } else {
+ outStream.write(magicPacket);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ streamServiceThread.start();
+ }
+
+ }
+
+ public void stop() throws InterruptedException {
+ Log.d(TAG, "streamServiceThread stopped");
+ isRunning = false;
+ streamServiceThread.join();
+ }
+
+ public void addVideoStreamListener(VideoStreamListener listener) {
+ if (videoStreamListeners.size() < MAX_VIDEO_STREAM_LISTENER) {
+ Log.d(TAG, "addVideoStreamListener");
+ videoStreamListeners.add(listener);
+ } else {
+ Log.d(TAG, "addVideoStreamListener: Limit reached !");
+ }
+ }
+
+ public void removeVideoStreamListener(VideoStreamListener listener) {
+ Log.d(TAG, "removeVideoStreamListener");
+ videoStreamListeners.remove(listener);
+ }
+
+ public interface VideoStreamListener {
+ void onVideoStreamData(byte[] buffer, int bytesReceived);
+ }
+}
diff --git a/app/src/main/java/com/fpvout/digiview/VideoStreamServiceDataSource.java b/app/src/main/java/com/fpvout/digiview/VideoStreamServiceDataSource.java
new file mode 100644
index 0000000..150026b
--- /dev/null
+++ b/app/src/main/java/com/fpvout/digiview/VideoStreamServiceDataSource.java
@@ -0,0 +1,81 @@
+package com.fpvout.digiview;
+
+import android.net.Uri;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.upstream.DataSource;
+import com.google.android.exoplayer2.upstream.DataSpec;
+import com.google.android.exoplayer2.upstream.TransferListener;
+
+import usb.CircularByteBuffer;
+
+public class VideoStreamServiceDataSource implements DataSource {
+ private static final int READ_BUFFER_SIZE = 50 * 1024 * 1024;
+ private static final long READ_TIMEOUT = 200;
+
+ private final DataSpec dataSpec;
+ private final CircularByteBuffer readBuffer;
+ private boolean opened;
+
+ public VideoStreamServiceDataSource(DataSpec dataSpec) {
+ this.dataSpec = dataSpec;
+ readBuffer = new CircularByteBuffer(READ_BUFFER_SIZE);
+ }
+
+ public VideoStreamServiceDataSource(DataSpec dataSpec, VideoStreamService v) {
+ this.dataSpec = dataSpec;
+ readBuffer = new CircularByteBuffer(READ_BUFFER_SIZE);
+ v.addVideoStreamListener(this::onVideoStreamData);
+ }
+
+ @Override
+ public void addTransferListener(@NonNull TransferListener transferListener) {
+
+ }
+
+ @Override
+ public long open(DataSpec dataSpec) {
+ long bytesRemaining;
+
+ if (dataSpec.length != C.LENGTH_UNSET) {
+ bytesRemaining = dataSpec.length;
+ } else {
+ bytesRemaining = C.LENGTH_UNSET;
+ }
+
+ opened = true;
+ return bytesRemaining;
+ }
+
+ @Override
+ public int read(@NonNull byte[] buffer, int offset, int readLength) {
+ long deadLine = System.currentTimeMillis() + READ_TIMEOUT;
+ int readBytes = 0;
+ while (System.currentTimeMillis() < deadLine && readBytes <= 0)
+ readBytes = readBuffer.read(buffer, offset, readLength);
+ return readBytes;
+ }
+
+ @Override
+ public Uri getUri() {
+ return dataSpec.uri;
+ }
+
+ @Override
+ public void close() {
+ if (opened) {
+ opened = false;
+ }
+
+ }
+
+ public void onVideoStreamData(byte[] buffer, int receivedBytes) {
+ if (receivedBytes > 0) {
+ readBuffer.write(buffer, 0, receivedBytes);
+ Log.d("onVideoStream", "onVideoStreamData: " + receivedBytes);
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/usb/AndroidUSBInputStream.java b/app/src/main/java/usb/AndroidUSBInputStream.java
index 84fdd24..571531d 100644
--- a/app/src/main/java/usb/AndroidUSBInputStream.java
+++ b/app/src/main/java/usb/AndroidUSBInputStream.java
@@ -1,54 +1,26 @@
-/*
- * 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.IOException;
+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,17 +31,17 @@ 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...
- 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);
}
@@ -78,6 +50,8 @@ public int read(byte[] buffer, int offset, int length) throws IOException {
@Override
- public void close() throws IOException {}
+ public void close() throws IOException {
+ super.close();
+ }
}
diff --git a/app/src/main/java/usb/AndroidUSBOutputStream.java b/app/src/main/java/usb/AndroidUSBOutputStream.java
index 3225bda..da7f042 100644
--- a/app/src/main/java/usb/AndroidUSBOutputStream.java
+++ b/app/src/main/java/usb/AndroidUSBOutputStream.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;
import android.hardware.usb.UsbDeviceConnection;
@@ -20,7 +5,6 @@
import java.io.IOException;
import java.io.OutputStream;
-import java.util.concurrent.LinkedBlockingQueue;
/**
* This class acts as a wrapper to write data to the USB Interface in Android
@@ -31,15 +15,9 @@ public class AndroidUSBOutputStream extends OutputStream {
// Constants.
private static final int WRITE_TIMEOUT = 2000;
- // Variables.
private final UsbDeviceConnection usbConnection;
-
private final UsbEndpoint sendEndPoint;
- private LinkedBlockingQueue writeQueue;
-
- private final boolean streamOpen = true;
-
/**
* Class constructor. Instantiates a new {@code AndroidUSBOutputStream}
* object with the given parameters.
@@ -80,10 +58,8 @@ public void write(byte[] buffer) {
@Override
public void write(byte[] buffer, int offset, int count) {
usbConnection.bulkTransfer(sendEndPoint, buffer, count, WRITE_TIMEOUT);
-
}
-
@Override
public void close() throws IOException {
super.close();
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
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index ccb60bf..e1b4fbb 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -1,39 +1,39 @@
DigiView\nfeed
- Attente de périphérique USB…
+ En attente du périphérique USB…
Périphérique USB approuvé
- Périphérique USB détaché\n\nAttente de périphérique USB…
+ Périphérique USB déconnecté\nEn attente du périphérique USB…
Périphérique USB trouvé.
En attente de la vidéo…
- Collection des données
- Partager les données de crashs avec l\'équipe de DigiView ?
- Cette application crashera sûrement moins que votre drone. Mais si jamais l\'application plante, On aimerait bien le savoir ! L\'équipe utilise Sentry pour tracker les bugs et rendre cette application meilleur.
+ Collecte de données
+ Partager mes données et diagnostics de crashs avec l\'équipe de DigiView ?
+ Cette application crashera sûrement moins que votre drone. Mais si jamais elle plante, nous aimerions bien le savoir ! Nous utilisons Sentry pour tracker les erreurs et crashs de l\'application, contribuez à son amélioration en acceptant de partager ces données avec nous.
Accepter
Refuser
Paramètres
Performance
- Vie Privée
- Preset Vidéo
+ Vie privée
+ Preset vidéo
Default
Conservative
Aggressive
Legacy
- Activer les Analytics
- politique de confidentialité
+ Collecte de données
+ Politique de confidentialité
Voyez quelles données nous recoltons et comment nous les utilisons.
- Lecteur Vidéo
- Plein Ecran
- peut aussi être activer en double tapant ou en pincant le lecteur vidéo.
+ Lecteur vidéo
+ Plein écran
+ Peut aussi être activé en double tapant ou en pinçant l\'écran.
Afficher le logo DigiView en filigrane
Liens
Notre site internet
Venez discuter avec nous sur Discord
L\'application web DigiView
Fonctionne sur Chrome et les navigateurs basés sur Chrome, même sur Android !
- Tout notre code est open-source
- À Propos
- Version de l\'application
+ Tout notre code est Open-Source
+ À propos
+ Version
Copyright
License Open-Source
License MIT
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
new file mode 100644
index 0000000..c0640c6
--- /dev/null
+++ b/app/src/main/res/values-nl/strings.xml
@@ -0,0 +1,44 @@
+
+
+ DigiView
+ DigiView\nfeed
+ Verbind De Goggles
+ USB Apparaat Goedgekeurd
+ Goggles Verbinding Verbroken
+ Goggles Verbonden
+ Wachten Op Video…
+ Data Collection Agreement
+ Nee, bedankt
+ Ja hoor!
+ Deel crash-informatie met het DigiView team?
+ Deze app crashed waarschijnlijk minder vaak dan je drone, maar als het gebeurt willen we het graag weten!
+ Instellingen
+
+
+ Prestatie
+ Privacy
+ Video Preset
+ Standaard
+ Behouden
+ Aggresief
+ Legacy
+ Legacy Buffered
+ Analyse Inschakelen
+ Privacy Policy
+ Bekijk welke data we verzamelen en hoe we deze gebruiken
+ Video Speler
+ Volledig Scherm
+ Kan ook ingeschakeld worden door dubbel-tap of het pinchen van de videospeler.
+ DigiView watermerk weergeven
+ Links
+ Onze Website
+ Chat met ons en andere DigiView gebruikers
+ DigiView Web App
+ Werkt in Chrome en Chrome-based browsers, zelfs op Android!
+ Al onze code is open-source
+ Over
+ App Versie
+ Copyright
+ Open-Source License
+ MIT License
+
\ No newline at end of file
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index fd59273..68eefdd 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -6,6 +6,8 @@
- @string/video_preset_aggressive
- @string/video_preset_legacy
- @string/video_preset_legacy_buffered
+ - @string/video_preset_video_stream_service
+
@@ -14,5 +16,6 @@
- aggressive
- legacy
- legacy_buffered
+ - video_stream_service
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index dba2270..2ee0237 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -40,5 +40,6 @@
Copyright
Open-Source License
MIT License
+ Video Stream Service
\ No newline at end of file
diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml
index aafa7b5..828f778 100644
--- a/app/src/main/res/xml/root_preferences.xml
+++ b/app/src/main/res/xml/root_preferences.xml
@@ -51,7 +51,7 @@
+ app:summary="https://fpvout.com">
diff --git a/build.gradle b/build.gradle
index 92f3684..8df7cb6 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,10 +2,10 @@
buildscript {
repositories {
google()
- jcenter()
+ mavenCentral()
}
dependencies {
- classpath "com.android.tools.build:gradle:4.1.3"
+ classpath 'com.android.tools.build:gradle:4.2.1'
// NOTE: Do not place your application dependencies here; they belong
@@ -16,7 +16,7 @@ buildscript {
allprojects {
repositories {
google()
- jcenter()
+ mavenCentral()
}
}
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index e3b2a6d..a143d9b 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip