From 3e77f69743393c5ea9e3743e88d585b7caef2949 Mon Sep 17 00:00:00 2001 From: Daniele Date: Wed, 13 Oct 2021 19:29:04 +0200 Subject: [PATCH] v0.01 --- .gitignore | 98 ++++++ .idea/.gitignore | 3 + .idea/.name | 1 + .idea/compiler.xml | 6 + .idea/jarRepositories.xml | 30 ++ .idea/misc.xml | 19 ++ app/.gitignore | 1 + app/build.gradle | 46 +++ app/proguard-rules.pro | 21 ++ .../ojo/ExampleInstrumentedTest.java | 26 ++ app/src/main/AndroidManifest.xml | 27 ++ app/src/main/ic_launcher-playstore.png | Bin 0 -> 19467 bytes .../java/it/danieleverducci/ojo/Settings.java | 75 +++++ .../danieleverducci/ojo/entities/Camera.java | 21 ++ .../ojo/ui/AddStreamFragment.java | 71 +++++ .../danieleverducci/ojo/ui/MainActivity.java | 49 +++ .../ojo/ui/SurveillanceFragment.java | 278 ++++++++++++++++++ .../danieleverducci/ojo/utils/DpiUtils.java | 16 + app/src/main/res/drawable/ic_add_camera.xml | 10 + .../res/drawable/ic_launcher_foreground.xml | 15 + app/src/main/res/layout/activity_main.xml | 20 ++ app/src/main/res/layout/content_main.xml | 19 ++ .../main/res/layout/fragment_add_stream.xml | 88 ++++++ .../main/res/layout/fragment_surveillance.xml | 12 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + app/src/main/res/navigation/nav_graph.xml | 28 ++ app/src/main/res/values-en/strings.xml | 4 + app/src/main/res/values-it/strings.xml | 20 ++ app/src/main/res/values-night/themes.xml | 15 + app/src/main/res/values-v27/themes.xml | 19 ++ app/src/main/res/values/colors.xml | 8 + app/src/main/res/values/dimens.xml | 3 + .../res/values/ic_launcher_background.xml | 4 + app/src/main/res/values/strings.xml | 19 ++ app/src/main/res/values/themes.xml | 17 ++ .../danieleverducci/ojo/ExampleUnitTest.java | 17 ++ build.gradle | 25 ++ .../metadata/android/en-US/changelogs/1.txt | 1 + .../android/en-US/full_description.txt | 6 + .../android/en-US/images/featureGraphic.png | Bin 0 -> 817293 bytes .../metadata/android/en-US/images/icon.png | Bin 0 -> 19467 bytes .../en-US/images/phoneScreenshots/1.png | Bin 0 -> 63043 bytes .../en-US/images/phoneScreenshots/2.png | Bin 0 -> 2829873 bytes .../en-US/images/phoneScreenshots/3.png | Bin 0 -> 1641568 bytes .../android/en-US/short_description.txt | 1 + fastlane/metadata/android/en-US/title.txt | 1 + .../metadata/android/it-IT/changelogs/1.txt | 1 + .../android/it-IT/full_description.txt | 6 + .../android/it-IT/images/featureGraphic.png | Bin 0 -> 817293 bytes .../metadata/android/it-IT/images/icon.png | Bin 0 -> 19467 bytes .../it-IT/images/phoneScreenshots/1.png | Bin 0 -> 63043 bytes .../it-IT/images/phoneScreenshots/2.png | Bin 0 -> 2829873 bytes .../it-IT/images/phoneScreenshots/3.png | Bin 0 -> 1641568 bytes .../android/it-IT/short_description.txt | 1 + fastlane/metadata/android/it-IT/title.txt | 1 + gradle.properties | 17 ++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54329 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 172 +++++++++++ gradlew.bat | 84 ++++++ settings.gradle | 2 + 62 files changed, 1440 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/.name create mode 100644 .idea/compiler.xml create mode 100644 .idea/jarRepositories.xml create mode 100644 .idea/misc.xml create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/src/androidTest/java/it/danieleverducci/ojo/ExampleInstrumentedTest.java create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/ic_launcher-playstore.png create mode 100644 app/src/main/java/it/danieleverducci/ojo/Settings.java create mode 100644 app/src/main/java/it/danieleverducci/ojo/entities/Camera.java create mode 100644 app/src/main/java/it/danieleverducci/ojo/ui/AddStreamFragment.java create mode 100644 app/src/main/java/it/danieleverducci/ojo/ui/MainActivity.java create mode 100644 app/src/main/java/it/danieleverducci/ojo/ui/SurveillanceFragment.java create mode 100644 app/src/main/java/it/danieleverducci/ojo/utils/DpiUtils.java create mode 100644 app/src/main/res/drawable/ic_add_camera.xml create mode 100644 app/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/layout/content_main.xml create mode 100644 app/src/main/res/layout/fragment_add_stream.xml create mode 100644 app/src/main/res/layout/fragment_surveillance.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/navigation/nav_graph.xml create mode 100644 app/src/main/res/values-en/strings.xml create mode 100644 app/src/main/res/values-it/strings.xml create mode 100644 app/src/main/res/values-night/themes.xml create mode 100644 app/src/main/res/values-v27/themes.xml create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/dimens.xml create mode 100644 app/src/main/res/values/ic_launcher_background.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/themes.xml create mode 100644 app/src/test/java/it/danieleverducci/ojo/ExampleUnitTest.java create mode 100644 build.gradle create mode 100644 fastlane/metadata/android/en-US/changelogs/1.txt create mode 100644 fastlane/metadata/android/en-US/full_description.txt create mode 100644 fastlane/metadata/android/en-US/images/featureGraphic.png create mode 100644 fastlane/metadata/android/en-US/images/icon.png create mode 100644 fastlane/metadata/android/en-US/images/phoneScreenshots/1.png create mode 100644 fastlane/metadata/android/en-US/images/phoneScreenshots/2.png create mode 100644 fastlane/metadata/android/en-US/images/phoneScreenshots/3.png create mode 100644 fastlane/metadata/android/en-US/short_description.txt create mode 100644 fastlane/metadata/android/en-US/title.txt create mode 100644 fastlane/metadata/android/it-IT/changelogs/1.txt create mode 100644 fastlane/metadata/android/it-IT/full_description.txt create mode 100644 fastlane/metadata/android/it-IT/images/featureGraphic.png create mode 100644 fastlane/metadata/android/it-IT/images/icon.png create mode 100644 fastlane/metadata/android/it-IT/images/phoneScreenshots/1.png create mode 100644 fastlane/metadata/android/it-IT/images/phoneScreenshots/2.png create mode 100644 fastlane/metadata/android/it-IT/images/phoneScreenshots/3.png create mode 100644 fastlane/metadata/android/it-IT/short_description.txt create mode 100644 fastlane/metadata/android/it-IT/title.txt create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..16271bb --- /dev/null +++ b/.gitignore @@ -0,0 +1,98 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +# Built application files +*.apk +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ +# Uncomment the following line in case you need and you don't have the release build type files in your app +# release/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# IntelliJ +*.iml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +# Android Studio 3 in .gitignore file. +.idea/caches +.idea/modules.xml +# Comment next line if keeping position of elements in Navigation Editor is relevant for you +.idea/navEditor.xml + +# Keystore files +# Uncomment the following lines if you do not want to check your keystore files in. +#*.jks +#*.keystore + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild +.cxx/ + +# Google Services (e.g. APIs or Firebase) +# google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md + +# Version control +vcs.xml + +# lint +lint/intermediates/ +lint/generated/ +lint/outputs/ +lint/tmp/ +# lint/reports/ diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..b6e5b6a --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +Ojo \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..fb7f4a8 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..0380d8d --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..a72e892 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..e33b942 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,46 @@ +plugins { + id 'com.android.application' +} + +android { + compileSdkVersion 30 + buildToolsVersion "30.0.2" + + defaultConfig { + applicationId "it.danieleverducci.ojo" + minSdkVersion 17 + targetSdkVersion 30 + versionCode 1 + versionName "0.0.1" + + vectorDrawables.useSupportLibrary = true + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + buildFeatures { + viewBinding true + } +} + +dependencies { + + implementation 'androidx.appcompat:appcompat:1.3.1' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.navigation:navigation-fragment:2.3.5' + implementation 'androidx.navigation:navigation-ui:2.3.5' + implementation 'org.videolan.android:libvlc-all:3.4.1' + testImplementation 'junit:junit:4.+' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/it/danieleverducci/ojo/ExampleInstrumentedTest.java b/app/src/androidTest/java/it/danieleverducci/ojo/ExampleInstrumentedTest.java new file mode 100644 index 0000000..f337601 --- /dev/null +++ b/app/src/androidTest/java/it/danieleverducci/ojo/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package it.danieleverducci.ojo; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("it.danieleverducci.ojo", appContext.getPackageName()); + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..cfae129 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000000000000000000000000000000000000..2e9b1d710aed102445068778f88fe9f9de328e47 GIT binary patch literal 19467 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 literal 0 HcmV?d00001 diff --git a/app/src/main/java/it/danieleverducci/ojo/Settings.java b/app/src/main/java/it/danieleverducci/ojo/Settings.java new file mode 100644 index 0000000..84dcad3 --- /dev/null +++ b/app/src/main/java/it/danieleverducci/ojo/Settings.java @@ -0,0 +1,75 @@ +package it.danieleverducci.ojo; + +import android.content.Context; +import android.util.Log; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import it.danieleverducci.ojo.entities.Camera; + +/** + * Manages the settings persistence + */ +public class Settings implements Serializable { + private static final String FILENAME = "settings.bin"; + private static final String TAG = "Settings"; + + private volatile String settingsFilePath; + private List cameras = new ArrayList<>(); + + public static Settings fromDisk(Context context) { + String filePath = context.getFilesDir() + File.separator + FILENAME; + Settings s = new Settings(); + try { + FileInputStream fin = new FileInputStream(filePath); + ObjectInputStream ois = new ObjectInputStream(fin); + s = (Settings) ois.readObject(); + } catch (FileNotFoundException e) { + Log.d(TAG, "No saved settings found, will create a new one"); + } catch (IOException e) { + Log.e(TAG, "Unable to load settings from disk: " + e.toString()); + } catch (ClassNotFoundException e) { + Log.e(TAG, e.toString()); + } + s.settingsFilePath = filePath; + return s; + } + + public List getCameras() { + return cameras; + } + + public void setCameras(List cameras) { + this.cameras = cameras; + } + + public void addCamera(Camera camera) { + this.cameras.add(camera); + } + + public boolean save() { + try { + FileOutputStream fout = new FileOutputStream(settingsFilePath); + ObjectOutputStream oos = new ObjectOutputStream(fout); + oos.writeObject(this); + fout.close(); + oos.close(); + return true; + } catch (FileNotFoundException e) { + Log.e(TAG, "Unable to create file " + settingsFilePath + ": " + e.toString()); + } catch (IOException e) { + Log.e(TAG, "Unable to save settings: " + e.toString()); + } + return false; + } +} diff --git a/app/src/main/java/it/danieleverducci/ojo/entities/Camera.java b/app/src/main/java/it/danieleverducci/ojo/entities/Camera.java new file mode 100644 index 0000000..3a1512f --- /dev/null +++ b/app/src/main/java/it/danieleverducci/ojo/entities/Camera.java @@ -0,0 +1,21 @@ +package it.danieleverducci.ojo.entities; + +import java.io.Serializable; + +public class Camera implements Serializable { + private String name; + private String rtspUrl; + + public Camera(String name, String rtspUrl) { + this.name = name; + this.rtspUrl = rtspUrl; + } + + public String getName() { + return name; + } + + public String getRtspUrl() { + return rtspUrl; + } +} diff --git a/app/src/main/java/it/danieleverducci/ojo/ui/AddStreamFragment.java b/app/src/main/java/it/danieleverducci/ojo/ui/AddStreamFragment.java new file mode 100644 index 0000000..13481a1 --- /dev/null +++ b/app/src/main/java/it/danieleverducci/ojo/ui/AddStreamFragment.java @@ -0,0 +1,71 @@ +package it.danieleverducci.ojo.ui; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.navigation.fragment.NavHostFragment; + +import com.google.android.material.snackbar.Snackbar; + +import it.danieleverducci.ojo.R; +import it.danieleverducci.ojo.Settings; +import it.danieleverducci.ojo.databinding.FragmentAddStreamBinding; +import it.danieleverducci.ojo.entities.Camera; + +public class AddStreamFragment extends Fragment { + + private FragmentAddStreamBinding binding; + + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState + ) { + + binding = FragmentAddStreamBinding.inflate(inflater, container, false); + return binding.getRoot(); + + } + + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + binding.save.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + // Check the field is filled + String url = binding.streamUrl.getText().toString(); + if (!(url.startsWith("rtsp://") || url.startsWith("http://"))) { + Snackbar.make(view, R.string.add_stream_invalid_url, Snackbar.LENGTH_LONG) + .setAction(R.string.add_stream_invalid_url_dismiss, null).show(); + return; + } + + // Load existing settings (if any) + Settings settings = Settings.fromDisk(getContext()); + // Add stream to list + settings.addCamera(new Camera("", url)); + // Save + if (!settings.save()) { + Snackbar.make(view, R.string.add_stream_error_saving, Snackbar.LENGTH_LONG).show(); + return; + } + + // Back to first fragment + NavHostFragment.findNavController(AddStreamFragment.this) + .navigate(R.id.action_SecondFragment_to_FirstFragment); + } + }); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + binding = null; + } + +} \ No newline at end of file diff --git a/app/src/main/java/it/danieleverducci/ojo/ui/MainActivity.java b/app/src/main/java/it/danieleverducci/ojo/ui/MainActivity.java new file mode 100644 index 0000000..1542681 --- /dev/null +++ b/app/src/main/java/it/danieleverducci/ojo/ui/MainActivity.java @@ -0,0 +1,49 @@ +package it.danieleverducci.ojo.ui; + +import android.os.Bundle; + +import com.google.android.material.snackbar.Snackbar; + +import androidx.appcompat.app.AppCompatActivity; + +import android.view.View; + +import androidx.navigation.NavController; +import androidx.navigation.Navigation; +import androidx.navigation.ui.AppBarConfiguration; +import androidx.navigation.ui.NavigationUI; + +import it.danieleverducci.ojo.R; +import it.danieleverducci.ojo.databinding.ActivityMainBinding; + +import android.view.Menu; +import android.view.MenuItem; + +public class MainActivity extends AppCompatActivity { + + private ActivityMainBinding binding; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + binding = ActivityMainBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + + // Show FAB only on first fragment + NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main); + navController.addOnDestinationChangedListener((controller, destination, arguments) -> { + if (destination.getId() == R.id.FirstFragment) + binding.fab.show(); + else + binding.fab.hide(); + }); + + binding.fab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + navController.navigate(R.id.action_FirstFragment_to_SecondFragment); + } + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/it/danieleverducci/ojo/ui/SurveillanceFragment.java b/app/src/main/java/it/danieleverducci/ojo/ui/SurveillanceFragment.java new file mode 100644 index 0000000..e57585f --- /dev/null +++ b/app/src/main/java/it/danieleverducci/ojo/ui/SurveillanceFragment.java @@ -0,0 +1,278 @@ +package it.danieleverducci.ojo.ui; + +import android.content.Context; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.Window; +import android.view.WindowInsets; +import android.view.WindowInsetsController; +import android.widget.LinearLayout; + +import androidx.fragment.app.Fragment; + +import org.videolan.libvlc.interfaces.IVLCVout; +import org.videolan.libvlc.LibVLC; +import org.videolan.libvlc.Media; +import org.videolan.libvlc.MediaPlayer; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import it.danieleverducci.ojo.R; +import it.danieleverducci.ojo.Settings; +import it.danieleverducci.ojo.databinding.FragmentSurveillanceBinding; +import it.danieleverducci.ojo.entities.Camera; +import it.danieleverducci.ojo.utils.DpiUtils; + +/** + * Some streams to test: + * rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov + * rtsp://demo:demo@ipvmdemo.dyndns.org:5541/onvif-media/media.amp?profile=profile_1_h264&sessiontimeout=60&streamtype=unicast + */ +public class SurveillanceFragment extends Fragment { + + final static private String TAG = "SurveillanceFragment"; + final static private String[] VLC_OPTIONS = new String[]{ + "--aout=opensles", + //"--audio-time-stretch", // time stretching + //"-vvv", // verbosity + "--avcodec-codec=h264", + //"--file-logging", + //"--logfile=vlc-log.txt" + }; + + private FragmentSurveillanceBinding binding; + private List cameraViews = new ArrayList<>(); + int viewMargin; + + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState + ) { + viewMargin = DpiUtils.DpToPixels(container.getContext(), 2); + + binding = FragmentSurveillanceBinding.inflate(inflater, container, false); + return binding.getRoot(); + + } + + @Override + public void onResume() { + super.onResume(); + + // Leanback mode (fullscreen) + Window window = getActivity().getWindow(); + if (window != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + final WindowInsetsController controller = window.getInsetsController(); + + if (controller != null) + controller.hide(WindowInsets.Type.statusBars()); + } else { + window.getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_IMMERSIVE + | View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); + } + } + + addAllCameras(); + + // Start playback for all streams + for (CameraView cv : cameraViews) { + cv.startPlayback(); + } + } + + @Override + public void onPause() { + super.onPause(); + + disposeAllCameras(); + } + + + private void addAllCameras() { + Settings settings = Settings.fromDisk(getContext()); + List cc = settings.getCameras(); + + int elemsPerSide = calcGridSideElements(cc.size()); + int camIdx = 0; + for (int r = 0; r < elemsPerSide; r++) { + // Create row and add to row container + LinearLayout row = new LinearLayout(getContext()); + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + 0, + 1.0f + ); + binding.gridRowContainer.addView(row, params); + // Add camera viewers to the row + for (int c = 0; c < elemsPerSide; c++) { + if ( camIdx < cc.size() ) { + Camera cam = cc.get(camIdx); + CameraView cv = addCameraView(cam, row); + cv.startPlayback(); + } else { + // Cameras are less than the maximum number of cells in grid: fill remaining cells with empty views + View ev = new View(getContext()); + ev.setBackgroundColor(getResources().getColor(R.color.purple_700)); + LinearLayout.LayoutParams evParams = new LinearLayout.LayoutParams( + 0, + LinearLayout.LayoutParams.MATCH_PARENT, + 1.0f + ); + evParams.setMargins(viewMargin,viewMargin,viewMargin,viewMargin); + row.addView(ev, evParams); + } + camIdx++; + } + } + } + + private void disposeAllCameras() { + // Destroy players, libs etc + for (CameraView cv : cameraViews) { + cv.destroy(); + } + cameraViews.clear(); + // Remove views + binding.gridRowContainer.removeAllViews(); + } + + protected void hideAllCameraViewsButNot(View cameraView) { + for (int i = 0; i < binding.gridRowContainer.getChildCount(); i++) { + LinearLayout row = (LinearLayout) binding.gridRowContainer.getChildAt(i); + for (int j = 0; j < row.getChildCount(); j++) { + View cam = row.getChildAt(j); + if (cameraView.getId() == cam.getId()) + cam.setVisibility(View.VISIBLE); + else + cam.setVisibility(View.GONE); + } + } + } + + protected void showAllCameras() { + for (int i = 0; i < binding.gridRowContainer.getChildCount(); i++) { + LinearLayout row = (LinearLayout) binding.gridRowContainer.getChildAt(i); + for (int j = 0; j < row.getChildCount(); j++) { + View cam = row.getChildAt(j); + cam.setVisibility(View.VISIBLE); + } + } + } + + private CameraView addCameraView(Camera camera, LinearLayout rowContainer) { + CameraView cv = new CameraView( + getContext(), + camera + ); + + // Add to layout + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( + 0, + LinearLayout.LayoutParams.MATCH_PARENT, + 1.0f + ); + params.setMargins(viewMargin,viewMargin,viewMargin,viewMargin); + rowContainer.addView(cv.surfaceView, params); + + cameraViews.add(cv); + return cv; + } + + /** + * Returns the number of elements per side needed to create a grid that can contain the provided elements number. + * Es: to display 3 elements is needed a 4-element grid, with 2 elements per side (a 2x2 grid) + * Es: to display 7 elements is needed a 9-element grid, with 3 elements per side (a 3x3 grid) + * @param elements + */ + private int calcGridSideElements(int elements) { + return (int)(Math.ceil(Math.sqrt(elements))); + } + + /** + * Contains all entities (views and java entities) related to a camera stream viewer + */ + private class CameraView { + protected SurfaceView surfaceView; + protected MediaPlayer mediaPlayer; + protected IVLCVout ivlcVout; + protected Camera camera; + protected LibVLC libvlc; + + public CameraView(Context context, Camera camera) { + this.camera = camera; + this.libvlc = new LibVLC(context, new ArrayList<>(Arrays.asList(VLC_OPTIONS))); + + surfaceView = new SurfaceView(context); + surfaceView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + SurveillanceFragment.this.hideAllCameraViewsButNot(v); + } + }); + SurfaceHolder holder = surfaceView.getHolder(); + + holder.setKeepScreenOn(true); + + // Create media player + mediaPlayer = new MediaPlayer(libvlc); + + // Set up video output + ivlcVout = mediaPlayer.getVLCVout(); + ivlcVout.setVideoView(surfaceView); + ivlcVout.attachViews(); + + // Load media and start playing + Media m = new Media(libvlc, Uri.parse(camera.getRtspUrl())); + mediaPlayer.setMedia(m); + + // Register for view resize events + final ViewTreeObserver observer= surfaceView.getViewTreeObserver(); + observer.addOnGlobalLayoutListener(() -> { + // Set rendering size + ivlcVout.setWindowSize(surfaceView.getWidth(), surfaceView.getHeight()); + }); + } + + /** + * Starts the playback. + */ + public void startPlayback() { + mediaPlayer.play(); + } + + /** + * Destroys the object and frees the memory + */ + public void destroy() { + if (libvlc == null) { + Log.e(TAG, this.toString() + " already destroyed"); + return; + } + + mediaPlayer.stop(); + final IVLCVout vout = mediaPlayer.getVLCVout(); + vout.detachViews(); + libvlc.release(); + libvlc = null; + mediaPlayer.release(); + mediaPlayer = null; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/it/danieleverducci/ojo/utils/DpiUtils.java b/app/src/main/java/it/danieleverducci/ojo/utils/DpiUtils.java new file mode 100644 index 0000000..b2efbdb --- /dev/null +++ b/app/src/main/java/it/danieleverducci/ojo/utils/DpiUtils.java @@ -0,0 +1,16 @@ +package it.danieleverducci.ojo.utils; + +import android.content.Context; +import android.content.res.Resources; +import android.util.TypedValue; + +public class DpiUtils { + public static int DpToPixels(Context context, int dp) { + Resources r = context.getResources(); + return (int) TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + dp, + r.getDisplayMetrics() + ); + } +} diff --git a/app/src/main/res/drawable/ic_add_camera.xml b/app/src/main/res/drawable/ic_add_camera.xml new file mode 100644 index 0000000..b4f90ff --- /dev/null +++ b/app/src/main/res/drawable/ic_add_camera.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..8c276c2 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..ba4c96f --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,20 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml new file mode 100644 index 0000000..4f68632 --- /dev/null +++ b/app/src/main/res/layout/content_main.xml @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_add_stream.xml b/app/src/main/res/layout/fragment_add_stream.xml new file mode 100644 index 0000000..dae7bf8 --- /dev/null +++ b/app/src/main/res/layout/fragment_add_stream.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + +