Compare commits

..

No commits in common. "stable" and "v0.0.2" have entirely different histories.

100 changed files with 185 additions and 1248 deletions

2
.idea/compiler.xml generated
View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="17" />
<bytecodeTargetLevel target="11" />
</component>
</project>

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<value>
<entry key="app">
<State />
</entry>
</value>
</component>
</project>

10
.idea/migrations.xml generated
View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>

11
.idea/misc.xml generated
View File

@ -3,21 +3,14 @@
<component name="DesignSurface">
<option name="filePathToZoomLevelMap">
<map>
<entry key="../../../../layout/custom_preview.xml" value="0.36974358974358973" />
<entry key="app/src/main/res/drawable/ic_add.xml" value="0.1565" />
<entry key="app/src/main/res/drawable/ic_add_camera.xml" value="0.1565" />
<entry key="app/src/main/res/layout/activity_main.xml" value="0.45" />
<entry key="app/src/main/res/layout/dialog_info.xml" value="0.7119565217391305" />
<entry key="app/src/main/res/layout/fragment_add_stream.xml" value="0.524901185770751" />
<entry key="app/src/main/res/layout/fragment_settings_item.xml" value="0.536" />
<entry key="app/src/main/res/layout/fragment_settings_item_list.xml" value="0.4" />
<entry key="app/src/main/res/layout/fragment_add_stream.xml" value="0.536" />
<entry key="app/src/main/res/layout/fragment_surveillance.xml" value="0.16354166666666667" />
<entry key="app/src/main/res/menu/settings_menu.xml" value="0.3458333333333333" />
</map>
</option>
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View File

@ -2,8 +2,9 @@
# Ojo: the FLOSS RTSP Surveillance camera viewer for Android
[<img src="https://raw.githubusercontent.com/andOTP/andOTP/master/assets/badges/get-it-on-github.png" height="80">](https://github.com/penguin86/ojo/releases/latest)
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" alt="Get it on F-Droid" height="80">](https://f-droid.org/it/packages/it.danieleverducci.ojo)
[<img src="https://raw.githubusercontent.com/andOTP/andOTP/master/assets/badges/get-it-on-github.png" height="80">](https://github.com/penguin86/ojo/releases/latest)
F-droid build **coming soon!**
(Always prefer [F-Droid](https://f-droid.org) build, when possible).
@ -14,21 +15,5 @@ The maximum number of cameras is determined by the device's capabilities.
The stream decoding and rendering is demanded to [VLC's library](https://code.videolan.org/videolan/vlc-android): without their effort this app wouldn't be possible.
This app was specifically developed for F-Droid, as I couldn't find any open source RTSP vievers in the main repository.
The app can be opened deeplinking to url ojo://view.
To open the app with focus on a specific camera, you can use an intent (it.danieleverducci.ojo.OPEN_CAMERA) to specify which camera you want to view.
The extra argument it.danieleverducci.ojo.CAMERA_NAME will open the app with the camera with the name you specified while adding the camera.
The extra argument it.danieleverducci.ojo.CAMERA_NUMBER starting at 1 could be used as well, if you have multiple cameras with the same name.
See belows example how to use the intent. The flag (-f 268468224) could be useful if you want to switch to an other camera while the app is running.
```shell
adb -s <YOUR_DEVICE> shell am start -a it.danieleverducci.ojo.OPEN_CAMERA -f 268468224 --es it.danieleverducci.ojo.CAMERA_NAME <YOUR_CAMERA_NAME>
adb -s <YOUR_DEVICE> shell am start -a it.danieleverducci.ojo.OPEN_CAMERA -f 268468224 --es it.danieleverducci.ojo.CAMERA_NUMBER <YOUR_CAMERA_NUMBER>
```
![Screenshot 1](media/screenshots/1.png) ![Screenshot 2](media/screenshots/2.png) ![Screenshot 3](media/screenshots/3.png)
## Contributors
- Thanks to [brenard](https://github.com/brenard) for the new grid sizing method
- Thanks to [davquar](https://github.com/davquar) for the fullscreen compatibility fix on Android 11
- Thanks to [jayfan0](https://github.com/jayfan0) for the first deep link implementation
- Thanks to [free-bots](https://github.com/free-bots) for the selection border on Android TV, intents for direct camera access and leanback support

View File

@ -3,14 +3,15 @@ plugins {
}
android {
compileSdkVersion 33
compileSdkVersion 30
buildToolsVersion "30.0.2"
defaultConfig {
applicationId "it.danieleverducci.ojo"
minSdkVersion 15
targetSdkVersion 33
versionCode 9
versionName "0.1.4"
minSdkVersion 17
targetSdkVersion 30
versionCode 2
versionName "0.0.2"
vectorDrawables.useSupportLibrary = true
@ -30,19 +31,16 @@ android {
buildFeatures {
viewBinding true
}
namespace 'it.danieleverducci.ojo'
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.8.0'
implementation 'androidx.navigation:navigation-fragment:2.5.3'
implementation 'androidx.navigation:navigation-ui:2.5.3'
implementation 'de.mrmaffen:libvlc-android:2.1.12@aar'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
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.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

View File

@ -1,20 +1,18 @@
{
"version": 3,
"version": 2,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "it.danieleverducci.ojo.googleplay",
"applicationId": "it.danieleverducci.ojo",
"variantName": "release",
"elements": [
{
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 6,
"versionName": "0.1.1",
"versionCode": 1,
"versionName": "0.0.1",
"outputFile": "app-release.apk"
}
],
"elementType": "File"
]
}

View File

@ -1,11 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="it.danieleverducci.ojo">
<uses-permission android:name="android.permission.INTERNET" />
<uses-feature
android:name="android.software.leanback"
android:required="false" />
<application
android:allowBackup="true"
@ -14,32 +11,15 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Ojo">
<activity
android:name=".ui.SettingsActivity"
android:banner="@mipmap/ic_launcher"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.Ojo">
</activity>
<activity
android:name=".ui.MainActivity"
android:configChanges="orientation|screenSize"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.Ojo">
android:theme="@style/Theme.Ojo"
android:screenOrientation="landscape">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="ojo" android:host="view"/>
</intent-filter>
<intent-filter>
<action android:name="it.danieleverducci.ojo.OPEN_CAMERA"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
</application>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -13,6 +13,7 @@ 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;
@ -20,7 +21,6 @@ import it.danieleverducci.ojo.entities.Camera;
* Manages the settings persistence
*/
public class Settings implements Serializable {
private static final long serialVersionUID = 1081285022445419696L;
private static final String FILENAME = "settings.bin";
private static final String TAG = "Settings";

View File

@ -1,22 +0,0 @@
package it.danieleverducci.ojo;
import android.content.Context;
import android.content.SharedPreferences;
public class SharedPreferencesManager {
private static final String SP_ROTATION_ENABLED = "rot_en";
public static void saveRotationEnabled(Context ctx, boolean enabled) {
SharedPreferences sharedPref = ctx.getSharedPreferences(SP_ROTATION_ENABLED, Context.MODE_PRIVATE);
sharedPref.edit().putBoolean(SP_ROTATION_ENABLED, enabled).apply();
}
public static boolean loadRotationEnabled(Context ctx) {
SharedPreferences sharedPref = ctx.getSharedPreferences(SP_ROTATION_ENABLED, Context.MODE_PRIVATE);
return sharedPref.getBoolean(SP_ROTATION_ENABLED, false);
}
public static void toggleRotationEnabled(Context ctx) {
saveRotationEnabled(ctx, ! loadRotationEnabled(ctx));
}
}

View File

@ -3,7 +3,6 @@ package it.danieleverducci.ojo.entities;
import java.io.Serializable;
public class Camera implements Serializable {
private static final long serialVersionUID = -3837361587400158910L;
private String name;
private String rtspUrl;
@ -12,14 +11,6 @@ public class Camera implements Serializable {
this.rtspUrl = rtspUrl;
}
public void setName(String name) {
this.name = name;
}
public void setRtspUrl(String rtspUrl) {
this.rtspUrl = rtspUrl;
}
public String getName() {
return name;
}

View File

@ -6,7 +6,6 @@ import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.navigation.fragment.NavHostFragment;
@ -17,20 +16,9 @@ import it.danieleverducci.ojo.Settings;
import it.danieleverducci.ojo.databinding.FragmentAddStreamBinding;
import it.danieleverducci.ojo.entities.Camera;
public class StreamUrlFragment extends Fragment {
public static final String ARG_CAMERA = "arg_camera";
public class AddStreamFragment extends Fragment {
private FragmentAddStreamBinding binding;
private Settings settings;
private Integer selectedCamera = null;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load existing settings (if any)
settings = Settings.fromDisk(getContext());
}
@Override
public View onCreateView(
@ -39,18 +27,6 @@ public class StreamUrlFragment extends Fragment {
) {
binding = FragmentAddStreamBinding.inflate(inflater, container, false);
// If passed an url, fill the details
Bundle args = getArguments();
if (args != null && args.containsKey(ARG_CAMERA)) {
this.selectedCamera = args.getInt(ARG_CAMERA);
Camera c = settings.getCameras().get(this.selectedCamera);
binding.streamName.setText(c.getName());
binding.streamName.setHint(getContext().getString(R.string.stream_list_default_camera_name).replace("{camNo}", (this.selectedCamera+1)+""));
binding.streamUrl.setText(c.getRtspUrl());
}
return binding.getRoot();
}
@ -69,19 +45,10 @@ public class StreamUrlFragment extends Fragment {
return;
}
// Name can be empty
String name = binding.streamName.getText().toString();
if (StreamUrlFragment.this.selectedCamera != null) {
// Update camera
Camera c = settings.getCameras().get(StreamUrlFragment.this.selectedCamera);
c.setName(name);
c.setRtspUrl(url);
} else {
// Add stream to list
settings.addCamera(new Camera(name, url));
}
// 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();
@ -89,7 +56,7 @@ public class StreamUrlFragment extends Fragment {
}
// Back to first fragment
NavHostFragment.findNavController(StreamUrlFragment.this)
NavHostFragment.findNavController(AddStreamFragment.this)
.popBackStack();
}
});

View File

@ -1,18 +0,0 @@
package it.danieleverducci.ojo.ui;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.fragment.app.Fragment;
import it.danieleverducci.ojo.databinding.FragmentInfoBinding;
public class InfoFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return FragmentInfoBinding.inflate(inflater, container, false).getRoot();
}
}

View File

@ -1,62 +1,49 @@
package it.danieleverducci.ojo.ui;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Build;
import android.os.Bundle;
import android.view.WindowManager;
import com.google.android.material.snackbar.Snackbar;
import androidx.appcompat.app.AppCompatActivity;
import it.danieleverducci.ojo.SharedPreferencesManager;
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 static final String TAG = "MainActivity";
private ActivityMainBinding binding;
private OnBackButtonPressedListener onBackButtonPressedListener;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Interface can go below notches
if (Build.VERSION.SDK_INT >= 28) {
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
);
}
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
binding.fab.setOnClickListener(view -> openSettings());
// 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);
}
});
}
@Override
protected void onStart() {
boolean rotationEnabledSetting = SharedPreferencesManager.loadRotationEnabled(this);
this.setRequestedOrientation(rotationEnabledSetting ? ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR : ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
super.onStart();
}
public void setOnBackButtonPressedListener(OnBackButtonPressedListener onBackButtonPressedListener) {
this.onBackButtonPressedListener = onBackButtonPressedListener;
}
@Override
public void onBackPressed() {
if (this.onBackButtonPressedListener != null && this.onBackButtonPressedListener.onBackPressed())
return;
super.onBackPressed();
}
private void openSettings() {
Intent i = new Intent(this, SettingsActivity.class);
startActivity(i);
}
}

View File

@ -1,5 +0,0 @@
package it.danieleverducci.ojo.ui;
public interface OnBackButtonPressedListener {
public boolean onBackPressed();
}

View File

@ -1,71 +0,0 @@
package it.danieleverducci.ojo.ui;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.WindowManager;
import androidx.appcompat.app.AppCompatActivity;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import it.danieleverducci.ojo.R;
import it.danieleverducci.ojo.SharedPreferencesManager;
import it.danieleverducci.ojo.databinding.ActivitySettingsBinding;
public class SettingsActivity extends AppCompatActivity {
private static final String TAG = "SettingsActivity";
private ActivitySettingsBinding binding;
private NavController navController;
private OnBackButtonPressedListener onBackButtonPressedListener;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivitySettingsBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_settings);
}
public void setOnBackButtonPressedListener(OnBackButtonPressedListener onBackButtonPressedListener) {
this.onBackButtonPressedListener = onBackButtonPressedListener;
}
@Override
public void onBackPressed() {
if (this.onBackButtonPressedListener != null && this.onBackButtonPressedListener.onBackPressed())
return;
super.onBackPressed();
}
public void navigateToFragment(int actionId) {
navigateToFragment(actionId, null);
}
public void navigateToFragment(int actionId, Bundle bundle) {
if (navController == null) {
Log.e(TAG, "Not initialized");
return;
}
try {
if (bundle != null)
navController.navigate(actionId, bundle);
else
navController.navigate(actionId);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Unable to navigate to fragment: " + e.getMessage());
}
}
public void toggleRotationEnabledSetting() {
SharedPreferencesManager.toggleRotationEnabled(this);
}
public boolean getRotationEnabledSetting() {
return SharedPreferencesManager.loadRotationEnabled(this);
}
}

View File

@ -1,116 +0,0 @@
package it.danieleverducci.ojo.ui;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import java.util.List;
import it.danieleverducci.ojo.R;
import it.danieleverducci.ojo.Settings;
import it.danieleverducci.ojo.SharedPreferencesManager;
import it.danieleverducci.ojo.databinding.FragmentSettingsItemListBinding;
import it.danieleverducci.ojo.entities.Camera;
import it.danieleverducci.ojo.ui.adapters.SettingsRecyclerViewAdapter;
import it.danieleverducci.ojo.utils.ItemMoveCallback;
/**
* A fragment representing a list of Items.
*/
public class SettingsFragment extends Fragment {
private FragmentSettingsItemListBinding binding;
private Settings settings;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding = FragmentSettingsItemListBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
// Setup toolbar
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
binding.settingsToolbar.getOverflowIcon().setTint(Color.WHITE);
}
binding.settingsToolbar.inflateMenu(R.menu.settings_menu);
MenuItem rotMenuItem = binding.settingsToolbar.getMenu().findItem(R.id.menuitem_allow_rotation);
rotMenuItem.setTitle(((SettingsActivity)getActivity()).getRotationEnabledSetting() ? R.string.menuitem_deny_rotation : R.string.menuitem_allow_rotation);
// Register for item click
binding.settingsToolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.menuitem_add_camera:
((SettingsActivity)getActivity()).navigateToFragment(R.id.action_settingsToCameraUrl);
return true;
case R.id.menuitem_allow_rotation:
((SettingsActivity)getActivity()).toggleRotationEnabledSetting();
SharedPreferencesManager.saveRotationEnabled(getContext(), ((SettingsActivity)getActivity()).getRotationEnabledSetting());
item.setTitle(((SettingsActivity)getActivity()).getRotationEnabledSetting() ? R.string.menuitem_deny_rotation : R.string.menuitem_allow_rotation);
return true;
case R.id.menuitem_info:
((SettingsActivity)getActivity()).navigateToFragment(R.id.action_SettingsToInfoFragment);
return true;
}
return false;
}
});
}
@Override
public void onResume() {
super.onResume();
// Load cameras
settings = Settings.fromDisk(getContext());
List<Camera> cams = settings.getCameras();
// Set the adapter
RecyclerView recyclerView = binding.list;
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
SettingsRecyclerViewAdapter adapter = new SettingsRecyclerViewAdapter(cams);
ItemTouchHelper.Callback callback =
new ItemMoveCallback(adapter);
ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
touchHelper.attachToRecyclerView(recyclerView);
adapter.setOnDragListener(touchHelper::startDrag);
recyclerView.setAdapter(adapter);
// Onclick listener
adapter.setOnClickListener(new SettingsRecyclerViewAdapter.OnClickListener() {
@Override
public void onItemClick(int pos) {
Bundle b = new Bundle();
b.putInt(StreamUrlFragment.ARG_CAMERA, pos);
((SettingsActivity)getActivity()).navigateToFragment(R.id.action_settingsToCameraUrl, b);
}
});
}
@Override
public void onPause() {
super.onPause();
// Save cameras
List<Camera> cams = ((SettingsRecyclerViewAdapter)binding.list.getAdapter()).getItems();
this.settings.setCameras(cams);
this.settings.save();
}
}

View File

@ -1,7 +1,6 @@
package it.danieleverducci.ojo.ui;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@ -13,15 +12,13 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowManager;
import android.view.WindowInsets;
import android.view.WindowInsetsController;
import android.widget.LinearLayout;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.core.view.WindowInsetsControllerCompat;
import androidx.fragment.app.Fragment;
import org.videolan.libvlc.IVLCVout;
import org.videolan.libvlc.interfaces.IVLCVout;
import org.videolan.libvlc.LibVLC;
import org.videolan.libvlc.Media;
import org.videolan.libvlc.MediaPlayer;
@ -79,73 +76,48 @@ public class SurveillanceFragment extends Fragment {
1.0f
);
// 1,1 instead of 0,0 because the latter doesn't work on android 13+
hiddenLayoutParams = new LinearLayout.LayoutParams(1, 1);
hiddenLayoutParams = new LinearLayout.LayoutParams(0, 0);
binding = FragmentSurveillanceBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
public void onResume() {
super.onResume();
leanbackMode(true);
// 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);
}
}
fullscreenCameraView = false;
addAllCameras();
// Start playback for all streams
for (CameraView cv : cameraViews) {
cv.startPlayback();
}
expandToCameraViewIfRequired();
// Register for back pressed events
((MainActivity)getActivity()).setOnBackButtonPressedListener(new OnBackButtonPressedListener() {
@Override
public boolean onBackPressed() {
if(fullscreenCameraView && cameraViews.size() > 1) {
fullscreenCameraView = false;
showAllCameras();
return true;
}
return false;
}
});
}
/**
* Goes fullscreen igoring the device screen insets (camera etc)
*/
private void leanbackMode(boolean leanback) {
Window w = requireActivity().getWindow();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P)
return;
if (leanback) {
w.getAttributes().layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
// Hide system bar
WindowInsetsControllerCompat windowInsetsController = WindowCompat.getInsetsController(w, w.getDecorView());
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars());
// System bar is hidden when not touched for a while
windowInsetsController.setSystemBarsBehavior(WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
} else {
// Show system bar
//WindowInsetsControllerCompat windowInsetsController = WindowCompat.getInsetsController(w, w.getDecorView());
//windowInsetsController.show(WindowInsetsCompat.Type.systemBars());
}
}
@Override
public void onPause() {
super.onPause();
leanbackMode(false);
disposeAllCameras();
}
@ -154,14 +126,14 @@ public class SurveillanceFragment extends Fragment {
Settings settings = Settings.fromDisk(getContext());
List<Camera> cc = settings.getCameras();
int[] gridSize = calcGridDimensionsBasedOnNumberOfElements(cc.size());
int elemsPerSide = calcGridSideElements(cc.size());
int camIdx = 0;
for (int r = 0; r < gridSize[0]; r++) {
for (int r = 0; r < elemsPerSide; r++) {
// Create row and add to row container
LinearLayout row = new LinearLayout(getContext());
binding.gridRowContainer.addView(row, rowLayoutParams);
// Add camera viewers to the row
for (int c = 0; c < gridSize[1]; c++) {
for (int c = 0; c < elemsPerSide; c++) {
if ( camIdx < cc.size() ) {
Camera cam = cc.get(camIdx);
CameraView cv = addCameraView(cam, row);
@ -240,60 +212,13 @@ public class SurveillanceFragment extends Fragment {
}
/**
* Returns the dimensions of the grid based on the number of elements.
* 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 6 elements is needed a 9-element grid, with 3 elements per side (a 2x3 grid)
* Es: to display 7 elements is needed a 9-element grid, with 3 elements per side (a 3x3 grid)
* @param elements
*/
private int[] calcGridDimensionsBasedOnNumberOfElements(int elements) {
int rows = 1;
int cols = 1;
while (rows * cols < elements) {
cols += 1;
if (rows * cols >= elements) break;
rows += 1;
}
int[] dimensions = {rows, cols};
return dimensions;
}
private void expandToCameraViewIfRequired() {
final String EXTRA_CAMERA_NUMBER = "it.danieleverducci.ojo.CAMERA_NUMBER";
final String EXTRA_CAMERA_NAME = "it.danieleverducci.ojo.CAMERA_NAME";
final String OPEN_CAMERA = "it.danieleverducci.ojo.OPEN_CAMERA";
if (this.getActivity() == null) {
return;
}
Intent intent = this.getActivity().getIntent();
if (OPEN_CAMERA.equals(intent.getAction())) {
String cameraName = intent.getStringExtra(EXTRA_CAMERA_NAME);
if (cameraName == null) {
int cameraNumber = intent.getIntExtra(EXTRA_CAMERA_NUMBER, 0) - 1;
expandByIndex(cameraNumber);
return;
}
expandByName(cameraName);
}
}
private void expandByIndex(int index) {
if (index < 0 || cameraViews.size() <= index) {
return;
}
hideAllCameraViewsButNot(cameraViews.get(index).surfaceView);
}
private void expandByName(String name) {
for(CameraView cameraView: cameraViews) {
if (cameraView.camera.getName().equals(name)) {
hideAllCameraViewsButNot(cameraView.surfaceView);
break;
}
}
private int calcGridSideElements(int elements) {
return (int)(Math.ceil(Math.sqrt(elements)));
}
/**
@ -317,7 +242,6 @@ public class SurveillanceFragment extends Fragment {
}
});
surfaceView.setOnFocusChangeListener((view, hasFocus) -> view.setBackgroundResource(hasFocus ? R.drawable.focus_border : 0));
SurfaceHolder holder = surfaceView.getHolder();
holder.setKeepScreenOn(true);

View File

@ -1,137 +0,0 @@
package it.danieleverducci.ojo.ui.adapters;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import it.danieleverducci.ojo.R;
import it.danieleverducci.ojo.databinding.FragmentSettingsItemBinding;
import it.danieleverducci.ojo.entities.Camera;
import it.danieleverducci.ojo.utils.ItemMoveCallback;
import java.util.Collections;
import java.util.List;
/**
* {@link RecyclerView.Adapter} that can display a {@link Camera}.
* TODO: Replace the implementation with code for your data type.
*/
public class SettingsRecyclerViewAdapter extends RecyclerView.Adapter<SettingsRecyclerViewAdapter.ViewHolder> implements ItemMoveCallback.ItemTouchHelperContract {
private final List<Camera> mValues;
private OnDragListener dragListener;
private OnClickListener clickListener;
public SettingsRecyclerViewAdapter(List<Camera> items) {
mValues = items;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
ViewHolder vh = new ViewHolder(FragmentSettingsItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
vh.dragHandle.setOnTouchListener((v, event) -> {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
dragListener.onItemDrag(vh);
}
return false;
});
return vh;
}
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
String cameraName = mValues.get(position).getName();
if (cameraName == null || cameraName.length() == 0)
cameraName = holder.name.getContext().getString(R.string.stream_list_default_camera_name).replace("{camNo}", (position+1)+"");
holder.name.setText(cameraName);
holder.url.setText(mValues.get(position).getRtspUrl());
holder.root.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
clickListener.onItemClick(holder.getBindingAdapterPosition());
}
});
holder.deleteButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mValues.remove(holder.getBindingAdapterPosition());
notifyItemRemoved(holder.getBindingAdapterPosition());
}
});
}
@Override
public int getItemCount() {
return mValues.size();
}
// ============= Drag&Drop TouchHelper methods =============
@Override
public void onRowMoved(int fromPosition, int toPosition) {
if (fromPosition < toPosition) {
for (int i = fromPosition; i < toPosition; i++) {
Collections.swap(mValues, i, i + 1);
}
} else {
for (int i = fromPosition; i > toPosition; i--) {
Collections.swap(mValues, i, i - 1);
}
}
notifyItemMoved(fromPosition, toPosition);
}
@Override
public void onRowSelected(RecyclerView.ViewHolder myViewHolder) {
}
@Override
public void onRowClear(RecyclerView.ViewHolder myViewHolder) {
}
public void setOnDragListener(OnDragListener dragListener) {
this.dragListener = dragListener;
}
public void setOnClickListener(OnClickListener clickListener) {
this.clickListener = clickListener;
}
public List<Camera> getItems() {
return mValues;
}
public class ViewHolder extends RecyclerView.ViewHolder {
public View root;
public TextView name;
public TextView url;
public View deleteButton;
public View dragHandle;
public ViewHolder(FragmentSettingsItemBinding binding) {
super(binding.getRoot());
this.root = binding.getRoot();
this.name = binding.cameraName;
this.url = binding.cameraUrl;
this.deleteButton = binding.cameraDelete;
this.dragHandle = binding.cameraDragHandle;
}
}
public interface OnDragListener {
void onItemDrag(ViewHolder vh);
}
public interface OnClickListener {
void onItemClick(int pos);
}
}

View File

@ -1,67 +0,0 @@
package it.danieleverducci.ojo.utils;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
/**
* Implementation of recycleview drag&drop
*/
public class ItemMoveCallback extends ItemTouchHelper.Callback {
private final ItemTouchHelperContract mAdapter;
public ItemMoveCallback(ItemTouchHelperContract adapter) {
mAdapter = adapter;
}
@Override
public boolean isLongPressDragEnabled() {
return false;
}
@Override
public boolean isItemViewSwipeEnabled() {
return false;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {}
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
return makeMovementFlags(dragFlags, 0);
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
RecyclerView.ViewHolder target) {
mAdapter.onRowMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition());
return true;
}
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder,
int actionState) {
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
mAdapter.onRowSelected(viewHolder);
}
super.onSelectedChanged(viewHolder, actionState);
}
@Override
public void clearView(@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
mAdapter.onRowClear(viewHolder);
}
public interface ItemTouchHelperContract {
void onRowMoved(int fromPosition, int toPosition);
void onRowSelected(RecyclerView.ViewHolder myViewHolder);
void onRowClear(RecyclerView.ViewHolder myViewHolder);
}
}

View File

@ -1,11 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF"
android:alpha="0.8">
<path
android:fillColor="@android:color/white"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</vector>

View File

@ -1,11 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF"
android:alpha="0.8">
<path
android:fillColor="@android:color/white"
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
</vector>

View File

@ -1,11 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF"
android:alpha="0.8">
<path
android:fillColor="@android:color/white"
android:pathData="M20,9H4v2h16V9zM4,15h16v-2H4V15z"/>
</vector>

View File

@ -1,17 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF"
android:alpha="0.8">
<path
android:fillColor="@android:color/white"
android:pathData="M12,14m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
<path
android:fillColor="@android:color/white"
android:pathData="M16,3.33c2.58,0 4.67,2.09 4.67,4.67H22c0,-3.31 -2.69,-6 -6,-6v1.33M16,6c1.11,0 2,0.89 2,2h1.33c0,-1.84 -1.49,-3.33 -3.33,-3.33V6"/>
<path
android:fillColor="@android:color/white"
android:pathData="M17,9c0,-1.11 -0.89,-2 -2,-2L15,4L9,4L7.17,6L4,6c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,9h-5zM12,19c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z"/>
</vector>

View File

@ -1,11 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF"
android:alpha="0.8">
<path
android:fillColor="@android:color/white"
android:pathData="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z"/>
</vector>

View File

@ -1,11 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF"
android:alpha="0.8">
<path
android:fillColor="@android:color/white"
android:pathData="M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"/>
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 267 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 538 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 483 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 517 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 327 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 337 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 328 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 682 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 653 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 627 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 260 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 399 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 913 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 908 B

View File

@ -1,6 +0,0 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke
android:width="1dip"
android:color="@color/white" />
</shape>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M3,4V1h2v3h3v2H5v3H3V6H0V4H3zM6,10V7h3V4h7l1.83,2H21c1.1,0 2,0.9 2,2v12c0,1.1 -0.9,2 -2,2H5c-1.1,0 -2,-0.9 -2,-2V10H6zM13,19c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5s-5,2.24 -5,5S10.24,19 13,19zM9.8,14c0,1.77 1.43,3.2 3.2,3.2s3.2,-1.43 3.2,-3.2s-1.43,-3.2 -3.2,-3.2S9.8,12.23 9.8,14z"/>
</vector>

View File

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
</vector>

View File

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M20,9H4v2h16V9zM4,15h16v-2H4V15z"/>
</vector>

View File

@ -1,13 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:viewportWidth="108"
android:viewportHeight="108"
android:tint="#FFFFFF">
<group android:scaleX="0.58"
android:scaleY="0.58"
android:translateX="5.04"
android:translateY="5.04">
<group android:scaleX="2.61"
android:scaleY="2.61"
android:translateX="22.68"
android:translateY="22.68">
<path
android:fillColor="@android:color/white"
android:pathData="M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"/>

View File

@ -1,16 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,14m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
<path
android:fillColor="@android:color/white"
android:pathData="M16,3.33c2.58,0 4.67,2.09 4.67,4.67H22c0,-3.31 -2.69,-6 -6,-6v1.33M16,6c1.11,0 2,0.89 2,2h1.33c0,-1.84 -1.49,-3.33 -3.33,-3.33V6"/>
<path
android:fillColor="@android:color/white"
android:pathData="M17,9c0,-1.11 -0.89,-2 -2,-2L15,4L9,4L7.17,6L4,6c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,9h-5zM12,19c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z"/>
</vector>

View File

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z"/>
</vector>

View File

@ -6,12 +6,7 @@
android:layout_height="match_parent"
tools:context=".ui.MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_surveillance"
android:name="it.danieleverducci.ojo.ui.SurveillanceFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout="@layout/fragment_surveillance" />
<include layout="@layout/content_main" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
@ -19,12 +14,7 @@
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
android:src="@drawable/ic_settings"
app:srcCompat="@drawable/ic_settings"
app:tint="@color/white"
android:tint="@color/white"
android:backgroundTint="@color/fab_background"
android:hint="@string/fab_add_camera"
android:contentDescription="@string/fab_add_camera"/>
app:srcCompat="@drawable/ic_add_camera"
app:tint="@color/purple_500"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.SettingsActivity">
<include layout="@layout/content_settings" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -6,7 +6,7 @@
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<fragment
android:id="@+id/nav_host_fragment_content_settings"
android:id="@+id/nav_host_fragment_content_main"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"

View File

@ -14,32 +14,14 @@
android:gravity="center_horizontal">
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:padding="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="50dp"
android:background="@color/ic_launcher_background"
android:src="@drawable/ic_eye"/>
android:src="@mipmap/ic_launcher_round"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/add_stream_name"/>
<EditText
android:id="@+id/stream_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:hint="@string/add_stream_placeholder_name"
android:lines="1"
android:maxLines="1"
android:inputType="textUri"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:text="@string/add_stream"/>
<EditText
@ -59,6 +41,48 @@
android:layout_marginTop="30dp"
android:text="@string/add_stream_save"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_margin="50dp"
android:background="@color/purple_200"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:textAppearance="@style/TextAppearance.AppCompat.Title"
android:textColor="@color/purple_500"
android:text="@string/app_info_title"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/app_info_creator_desc"
android:autoLink="web"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/app_info_license_desc"
android:autoLink="web"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/app_info_repo_desc"
android:autoLink="web"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/app_info_lib_desc"
android:autoLink="web"/>
</LinearLayout>
</ScrollView>

View File

@ -1,76 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center_horizontal"
android:padding="50dp">
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:padding="20dp"
android:background="@color/ic_launcher_background"
android:src="@drawable/ic_eye"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_margin="50dp"
android:background="@color/purple_200"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:textAppearance="@style/TextAppearance.AppCompat.Title"
android:textColor="@color/purple_500"
android:text="@string/app_info_title"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/app_info_creator_desc"
android:autoLink="web"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/app_info_license_desc"
android:autoLink="web"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/app_info_repo_desc"
android:autoLink="web"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/app_info_lib_desc"
android:autoLink="web"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:textStyle="bold"
android:text="@string/app_info_translators_title"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:text="@string/app_info_translators_names"/>
</LinearLayout>
</ScrollView>

View File

@ -1,64 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/text_margin"
android:paddingBottom="@dimen/text_margin"
android:orientation="horizontal"
android:focusable="true"
android:focusableInTouchMode="true">
<ImageView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:padding="10dp"
android:layout_weight="0"
app:srcCompat="@drawable/ic_network_camera"
app:tint="@color/design_default_color_primary"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/camera_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="5sp"
android:textAppearance="?attr/textAppearanceListItem"
android:textColor="@color/design_default_color_primary"
android:lines="1"
android:text="@tools:sample/lorem/random"/>
<TextView
android:id="@+id/camera_url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceListItemSecondary"
android:lines="1"
android:text="@tools:sample/lorem/random" />
</LinearLayout>
<ImageView
android:id="@+id/camera_delete"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:padding="10dp"
android:layout_weight="0"
app:srcCompat="@drawable/ic_delete"
app:tint="@color/purple_200" />
<ImageView
android:id="@+id/camera_drag_handle"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:padding="10dp"
android:layout_weight="0"
app:srcCompat="@drawable/ic_drag_handle"
app:tint="@color/purple_200" />
</LinearLayout>

View File

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/settingsToolbar"
app:title="@string/app_name"
style="@style/ToolBarStyle"
android:focusable="true"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list"
android:name="it.danieleverducci.ojo.ui.SettingsFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
app:layoutManager="LinearLayoutManager"
tools:context=".ui.SettingsFragment"
tools:listitem="@layout/fragment_settings_item"
android:focusable="true" />
</LinearLayout>

View File

@ -6,7 +6,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".ui.StreamUrlFragment"
tools:context=".ui.AddStreamFragment"
android:background="@color/purple_500">
</LinearLayout>

View File

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/menuitem_add_camera"
android:icon="@drawable/ic_add"
android:title="@string/menuitem_add_camera"
app:showAsAction="always"/>
<item android:id="@+id/menuitem_allow_rotation"
android:title="@string/menuitem_allow_rotation"
app:showAsAction="never"/>
<item android:id="@+id/menuitem_info"
android:title="@string/menuitem_info"
app:showAsAction="never"/>
</menu>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@ -3,32 +3,26 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph"
app:startDestination="@id/SettingsFragment">
app:startDestination="@id/FirstFragment">
<fragment
android:id="@+id/CameraUrlFragment"
android:name="it.danieleverducci.ojo.ui.StreamUrlFragment"
android:id="@+id/FirstFragment"
android:name="it.danieleverducci.ojo.ui.SurveillanceFragment"
android:label="@string/first_fragment_label"
tools:layout="@layout/fragment_surveillance">
<action
android:id="@+id/action_FirstFragment_to_SecondFragment"
app:destination="@id/SecondFragment" />
</fragment>
<fragment
android:id="@+id/SecondFragment"
android:name="it.danieleverducci.ojo.ui.AddStreamFragment"
android:label="@string/second_fragment_label"
tools:layout="@layout/fragment_add_stream">
<action
android:id="@+id/action_cameraUrlToSettings"
app:destination="@id/SettingsFragment" />
</fragment>
<fragment
android:id="@+id/SettingsFragment"
android:name="it.danieleverducci.ojo.ui.SettingsFragment"
android:label="fragment_settings_item_list"
tools:layout="@layout/fragment_settings_item_list" >
<action
android:id="@+id/action_settingsToCameraUrl"
app:destination="@id/CameraUrlFragment" />
<action
android:id="@+id/action_SettingsToInfoFragment"
app:destination="@id/infoFragment" />
android:id="@+id/action_SecondFragment_to_FirstFragment"
app:destination="@id/FirstFragment" />
</fragment>
<fragment
android:id="@+id/infoFragment"
android:name="it.danieleverducci.ojo.ui.InfoFragment"
android:label="InfoFragment" />
</navigation>

View File

@ -5,29 +5,16 @@
<string name="first_fragment_label">First Fragment</string>
<string name="second_fragment_label">Second Fragment</string>
<string name="fab_add_camera">Add</string>
<string name="stream_list_default_camera_name">Videocamera senza nome n°{camNo}</string>
<string name="add_stream_placeholder_url">rtsp://username:password@192.168.1.123:554</string>
<string name="add_stream_placeholder_name">Nome della IP Camera</string>
<string name="add_stream_name">Nome della IP Camera</string>
<string name="add_stream">Inserisci l\'url dello stream RTSP della tua IP Camera. Nota che questo differisce tra un modello e l\'altro. Consulta il pannello di configurazione o il manuale della tua IP Camera.</string>
<string name="add_stream_save">Salva</string>
<string name="add_stream_invalid_url">L\'URL RTSP non è valido</string>
<string name="add_stream_invalid_url_dismiss">Chiudi</string>
<string name="add_stream_error_saving">Si è verificato un errore durante il salvataggio della configurazione.</string>
<string name="add_stream">Inserisci l\'url dello stream RTSP della tua IP Camera. Nota che questo differisce tra un modello e l\'altro. Consulta il pannello di configurazione o il manuale della tua IP Camera.</string>
<string name="menuitem_allow_rotation">Allow screen rotation</string>
<string name="menuitem_deny_rotation">Landscape only</string>
<string name="menuitem_info">Info</string>
<string name="menuitem_add_camera">Aggiungi</string>
<string name="app_info_title">Informazioni su Ojo</string>
<string name="app_info_creator_desc">Creato da Daniele Verducci.</string>
<string name="app_info_license_desc">Questa app è rilasciata sotto licenza GNU GENERAL PUBLIC LICENSE v3+. Puoi ottenerne una copia qui: https://raw.githubusercontent.com/penguin86/ojo/master/LICENSE</string>
<string name="app_info_repo_desc">Puoi trovare il codice sorgente al repository: https://github.com/penguin86/ojo</string>
<string name="app_info_lib_desc">Questa app è resa possibile dal magnifico lavoro dei team vlc and vlc-android! Per saperne di più o ottenere il codice sorgente: https://code.videolan.org/videolan/vlc-android</string>
<string name="app_info_translators_title">Traduttori:</string>
<string name="app_info_translators_names">Yurt Page (Russian)</string>
</resources>

View File

@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Ojo</string>
<!-- Strings used for fragments for navigation -->
<string name="first_fragment_label">Первый Фрагмент</string>
<string name="second_fragment_label">Второй Фрагмент</string>
<string name="fab_add_camera">Добавить</string>
<string name="stream_list_default_camera_name">VidБезымянная камера №{camNo}</string>
<string name="add_stream_placeholder_url">rtsp://username:password@192.168.1.123:554</string>
<string name="add_stream_placeholder_name">Название IP камеры</string>
<string name="add_stream_name">Нзвание потока IP камеры</string>
<string name="add_stream_save">Сохранить</string>
<string name="add_stream_invalid_url">Неправильный RTSP URL</string>
<string name="add_stream_invalid_url_dismiss">Закрыть</string>
<string name="add_stream_error_saving">Произошла ошибка при сохранении конфигурации</string>
<string name="add_stream">Пожалуйста введите RTSP поток вашей камеры. Обратите внимание, что URL отличается от камеры к камере: вы можете найти полный URL адрес в настройках вашей камеры или руководстве пользователя.</string>
<string name="menuitem_allow_rotation">Разрешить вращение экрана</string>
<string name="menuitem_deny_rotation">Только альбомная</string>
<string name="menuitem_info">Инфо</string>
<string name="menuitem_add_camera">Добавить</string>
<string name="app_info_title">О программе</string>
<string name="app_info_creator_desc">Автор: Daniele Verducci.</string>
<string name="app_info_license_desc">Это приложение лицензировано в соответствии с GNU GENERAL PUBLIC LICENSE v3+. Ознакомьтесь тут: https://raw.githubusercontent.com/penguin86/ojo/master/LICENSE</string>
<string name="app_info_repo_desc">Исходный код в репозитории на GitHub:: https://github.com/penguin86/ojo</string>
<string name="app_info_lib_desc">Это приложение стало возможным благодаря усилиям команды VLC и VLC-Android! Вы можете узнать больше или получить исходный код тут: https://code.videolan.org/videolan/vlc-android</string>
</resources>

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="Theme.Ojo" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<!-- Primary brand color. -->
@ -12,8 +13,7 @@
<item name="colorSecondaryVariant">@color/purple_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@ -5,7 +5,4 @@
<color name="purple_700">#FF3700B3</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="fab_foreground">@color/purple_500</color>
<color name="fab_background">@color/purple_200</color>
</resources>

View File

@ -1,4 +1,3 @@
<resources>
<dimen name="fab_margin">16dp</dimen>
<dimen name="text_margin">16dp</dimen>
</resources>

View File

@ -4,28 +4,16 @@
<string name="first_fragment_label">First Fragment</string>
<string name="second_fragment_label">Second Fragment</string>
<string name="fab_add_camera">Add</string>
<string name="stream_list_default_camera_name">Unnamed camera {camNo}</string>
<string name="add_stream_placeholder_url">rtsp://username:password@192.168.1.123:554</string>
<string name="add_stream_placeholder_name">Camera name</string>
<string name="add_stream_name">Camera name</string>
<string name="add_stream">Please insert your camera\'s RTSP stream. Note that the URL differs from camera to camera: you can find the complete URL in your camera\'s settings or user manual.</string>
<string name="add_stream_save">Save</string>
<string name="add_stream_invalid_url">Invalid RTSP url</string>
<string name="add_stream_invalid_url_dismiss">Dismiss</string>
<string name="add_stream_error_saving">An error has occurred while saving configuration</string>
<string name="add_stream">Please insert your camera\'s RTSP stream. Note that the URL differs from camera to camera: you can find the complete URL in your camera\'s settings or user manual.</string>
<string name="menuitem_allow_rotation">Allow screen rotation</string>
<string name="menuitem_deny_rotation">Landscape only</string>
<string name="menuitem_info">Info</string>
<string name="menuitem_add_camera">Add</string>
<string name="app_info_title">About Ojo</string>
<string name="app_info_creator_desc">Created by Daniele Verducci.</string>
<string name="app_info_license_desc">This application is licensed under the GNU GENERAL PUBLIC LICENSE v3+. You can obtain a copy here: https://raw.githubusercontent.com/penguin86/ojo/master/LICENSE</string>
<string name="app_info_repo_desc">The source code can be obtained at the GitHub repository: https://github.com/penguin86/ojo</string>
<string name="app_info_lib_desc">This app is made possible by the gorgeous VLC and VLC-Android teams effort! You can know more or obtain the source code at https://code.videolan.org/videolan/vlc-android</string>
<string name="app_info_translators_title">Translators:</string>
<string name="app_info_translators_names" translatable="false">Yurt Page (Russian)</string>
<string name="app_info_repo_desc">The source code can be obtained at the github repository: https://github.com/penguin86/ojo</string>
<string name="app_info_lib_desc">This app is made possible by the gourgeous vlc and vlc-android teams effort! You can know more or obtain the source code at https://code.videolan.org/videolan/vlc-android</string>
</resources>

View File

@ -12,16 +12,6 @@
<item name="colorSecondary">@color/purple_500</item>
<item name="colorSecondaryVariant">@color/purple_700</item>
<item name="colorOnSecondary">@color/white</item>
<item name="colorAccent">@color/white</item>
</style>
<style name="ToolBarStyle" parent="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<item name="android:background">@color/purple_500</item>
<item name="titleTextColor">@color/white</item>
<item name="actionMenuTextColor">@color/white</item>
<item name="android:actionMenuTextColor">@color/white</item>
<item name="colorOnPrimary">@color/white</item>
</style>
</resources>

View File

@ -5,7 +5,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.4.0'
classpath "com.android.tools.build:gradle:4.2.2"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

View File

@ -1 +0,0 @@
Downgraded LibVLC library to fix black screen bug on certain devices

View File

@ -1,3 +0,0 @@
RTSP Streams can be removed, edited and reordered.
Now supporting screen rotation (disabled by default).
Better back button handling in full screen mode.

View File

@ -1,2 +0,0 @@
App can be opened with url intent ojo://view
Backported to Android >= 4.0.3

View File

@ -1,2 +0,0 @@
Added russian translation (thanks to Yurt Page!)

View File

@ -1,16 +1,6 @@
Ojo is a basic IP Camera surveillance wall.
IP camera's RTSP streams are added via its url and shown in the classic tile configuration. The number of tiles is automatically chosen based on the number of configured cameras: a single camera goes full screen, adding more cameras the app switches to a grid view: 2x2, 3x3, 4x4 and so on.
IP camera's RTSP streams are added via its url and shown in the classic tile configuration. The number of tiles is automatically choosen based on the number of configured cameras: a single camera goes full screen, adding more cameras the app switches to a grid view: 2x2, 3x3, 4x4 and so on.
The maximum number of cameras is determined by the device's capabilities.
The stream decoding and rendering is demanded to VLC's library: without their effort this app wouldn't be possible.
This app was specifically developed for F-Droid, as I couldn't find any open source RTSP viewers in the main repository.
The app can be opened deeplinking to url ojo://view
To open the app with focus on a specific camera, you can use an intent (it.danieleverducci.ojo.OPEN_CAMERA) to specify which camera you want to view.
The extra argument it.danieleverducci.ojo.CAMERA_NAME will open the app with the camera with the name you specified while adding the camera.
The extra argument it.danieleverducci.ojo.CAMERA_NUMBER starting at 1 could be used as well, if you have multiple cameras with the same name.
See below an example how to use the intent. The flag (-f 268468224) could be useful if you want to switch to another camera while the app is running:
adb -s <YOUR_DEVICE> shell am start -a it.danieleverducci.ojo.OPEN_CAMERA -f 268468224 --es it.danieleverducci.ojo.CAMERA_NAME <YOUR_CAMERA_NAME>
adb -s <YOUR_DEVICE> shell am start -a it.danieleverducci.ojo.OPEN_CAMERA -f 268468224 --es it.danieleverducci.ojo.CAMERA_NUMBER <YOUR_CAMERA_NUMBER>
This app was specifically developed for F-Droid, as I couldn't find any open source RTSP vievers in the main repository.

View File

@ -1 +0,0 @@
Downgrade della libreria libVLC per risolvere il bug della schermata nera in determinati dispositivi

View File

@ -1,3 +0,0 @@
Possibilità di rimuovere, modificare e riordinare gli stream RTSP.
Supporto per la rotazione dello schermo (disabilitato di default).
Migliorato il comportamento quando si preme il bottone Indietro in fullscreen.

View File

@ -1,2 +0,0 @@
L'app può essere aperta con intent url ojo://view
Aggiunta compatibilità con vecchie versioni di Android >= 4.0.3

View File

@ -1 +0,0 @@
Aggiunta traduzione in russo (grazie a Yurt Page!)

View File

@ -1,19 +0,0 @@
OJO это базовый просмотрщик IP камер наблюдения.
RTSP потоки IP камеры добавляются через его URL адрес и показаны в классической плиточной конфигурации. Количество плиток автоматически выбирается на основе количества настроенных камер: одна камера показана на весь экран, добавление больше камер переключает на сетку: 2x2, 3x3, 4x4 и т. д.
Максимальное количество камер определяется возможностями устройства.
Декодирование и рендеринг потока требует библиотеки VLC: без их стараний это приложение не было бы возможно.
Это приложение было специально разработано для F-Droid, так как я не мог найти каких-либо просмотрщиков RTSP с открытым исходным кодом в основном репозитории.
Приложение может быть открыто с глубокой ссылкой по URL ojo://view.
Чтобы открыть приложение с фокусом на определенной камере, вы можете использовать intent (it.danieleverducci.ojo.OPEN_CAMERA) чтобы указать какую камеру вы хотите просмотреть.
Дополнительный аргумент it.danieleverducci.ojo.CAMERA_NAME откроет приложение с камерой с указанным вами названием при добавлении камеры.
Дополнительный аргумент it.danieleverducci.ojo.CAMERA_NUMBER начиная с 1 также может использоваться, если у вас есть несколько камер с одним и тем же названием.
Ниже пример как использовать intent. Флаг (-F 268468224) может быть полезен, если вы хотите переключиться на другую камеру во время работы приложения:
adb -s <ВАШЕ_УСТРОЙСТВО> shell am start -a it.danieleverducci.ojo.OPEN_CAMERA -f 268468224 --es it.danieleverducci.ojo.CAMERA_NAME <ИМЯ_ВАШЕЙ_КАМЕРЫ>
adb -s <ВАШЕ_УСТРОЙСТВО> shell am start -a it.danieleverducci.ojo.OPEN_CAMERA -f 268468224 --es it.danieleverducci.ojo.CAMERA_NUMBER <НОМЕР_ВАШЕЙ_КАМЕРЫ>

View File

@ -1 +0,0 @@
OJO это базовый просмотрщик IP камер наблюдения.

View File

@ -1 +0,0 @@
Ojo RTSP просмотрщик IP Камер

View File

@ -1,6 +1,6 @@
#Wed Feb 15 08:51:54 CET 2023
#Fri Aug 13 07:42:34 CEST 2021
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME