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 7f4c7a4..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);
+ mVideoReader.setVideoPlayingEventListener(this::hideOverlay);
+ mVideoReader.setVideoWaitingEventListener(() -> showOverlay(R.string.waiting_for_video, OverlayStatus.Connected));
- 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.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");
@@ -249,32 +266,10 @@ public void usbDeviceDetached() {
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,29 +302,6 @@ 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();
@@ -372,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 23c795e..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.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;
- }
+ 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/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/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