├── app ├── .gitignore ├── src │ ├── test │ │ ├── resources │ │ │ └── robolectric.properties │ │ └── java │ │ │ └── pl │ │ │ └── huczeq │ │ │ └── rtspplayer │ │ │ ├── BaseAndroidTest.java │ │ │ ├── BaseDITestTemplate.java │ │ │ ├── domain │ │ │ ├── usecases │ │ │ │ ├── BaseUseCaseTestTemplateTest.java │ │ │ │ └── BaseUseCaseTestTemplate.java │ │ │ └── cameragenerator │ │ │ │ ├── ExpressionTest.java │ │ │ │ └── ModelGeneratorForTests.java │ │ │ └── FakeRtspPlayerApp.java │ ├── main │ │ ├── res │ │ │ ├── values-land │ │ │ │ └── dimens.xml │ │ │ ├── mipmap │ │ │ │ └── icon_camera.png │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── values │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ ├── dimens.xml │ │ │ │ ├── attrs.xml │ │ │ │ └── colors.xml │ │ │ ├── drawable │ │ │ │ ├── bg_control_player_gradient.xml │ │ │ │ ├── add.xml │ │ │ │ ├── ic_back.xml │ │ │ │ ├── ic_check.xml │ │ │ │ ├── ic_expand_less.xml │ │ │ │ ├── ic_expand_more.xml │ │ │ │ ├── header_icon_checklist.xml │ │ │ │ ├── ic_checklist.xml │ │ │ │ ├── ic_download.xml │ │ │ │ ├── ic_upload.xml │ │ │ │ ├── ic_enter_picture_in_picture_mode.xml │ │ │ │ ├── video_camera_back.xml │ │ │ │ ├── ic_volume_on.xml │ │ │ │ ├── folder_open.xml │ │ │ │ ├── ic_volume_off.xml │ │ │ │ ├── launcher_icon.xml │ │ │ │ ├── launcher_icon_small.xml │ │ │ │ ├── ic_confirm.xml │ │ │ │ ├── launcher_monochrome_icon_small.xml │ │ │ │ └── ic_settings.xml │ │ │ ├── color │ │ │ │ └── text_input_box_stroke_color.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── values-w820dp │ │ │ │ └── dimens.xml │ │ │ ├── layout │ │ │ │ ├── pref_m3_checkbox.xml │ │ │ │ ├── pref_m3_switch.xml │ │ │ │ ├── activity_settings.xml │ │ │ │ ├── activity_app_license.xml │ │ │ │ ├── item_camera_preview.xml │ │ │ │ ├── toolbar.xml │ │ │ │ ├── activity_start.xml │ │ │ │ ├── activity_select_camera.xml │ │ │ │ ├── item_camera_select_radiobutton.xml │ │ │ │ ├── player_control_interface.xml │ │ │ │ ├── activity_app_info.xml │ │ │ │ └── item_view_camera.xml │ │ │ ├── menu │ │ │ │ ├── menu_camera_item.xml │ │ │ │ └── menu_camera_group_item.xml │ │ │ ├── values-v27 │ │ │ │ └── styles.xml │ │ │ └── values-night │ │ │ │ └── colors.xml │ │ ├── ic_launcher-playstore.png │ │ └── java │ │ │ └── pl │ │ │ └── huczeq │ │ │ └── rtspplayer │ │ │ ├── util │ │ │ ├── validation │ │ │ │ ├── interfaces │ │ │ │ │ ├── BasicCondition.java │ │ │ │ │ ├── FieldRule.java │ │ │ │ │ ├── ValueTransform.java │ │ │ │ │ └── ValueProvider.java │ │ │ │ ├── Errors.java │ │ │ │ ├── RuleWithValueTransform.java │ │ │ │ ├── FieldRules.java │ │ │ │ └── FieldValidator.java │ │ │ ├── interfaces │ │ │ │ ├── IOnListItemSelected.java │ │ │ │ └── IOnMenuItemListSelected.java │ │ │ ├── states │ │ │ │ ├── BaseState.java │ │ │ │ ├── ProcessingStateType.java │ │ │ │ ├── ResultState.java │ │ │ │ └── CompletableState.java │ │ │ ├── DiffCallback.java │ │ │ └── Timer.java │ │ │ ├── player │ │ │ ├── OnVideoLayoutChanged.java │ │ │ ├── PlayerEventListener.java │ │ │ ├── vlc │ │ │ │ └── VlcFactory.java │ │ │ ├── RtspPlayer.java │ │ │ └── VideoLayout.java │ │ │ ├── domain │ │ │ ├── cameragenerator │ │ │ │ ├── exceptions │ │ │ │ │ ├── LimitReachedException.java │ │ │ │ │ └── ParsingException.java │ │ │ │ ├── expression │ │ │ │ │ ├── VariableModel.java │ │ │ │ │ ├── ProcessedVariable.java │ │ │ │ │ ├── Variations.java │ │ │ │ │ └── Expression.java │ │ │ │ ├── CameraGroupGenerator.java │ │ │ │ └── CameraInstancesGenerator.java │ │ │ ├── usecases │ │ │ │ ├── base │ │ │ │ │ ├── BaseUseCase.java │ │ │ │ │ ├── DisposableUseCase.java │ │ │ │ │ ├── NoResultUseCase.java │ │ │ │ │ ├── SingleUseCase.java │ │ │ │ │ └── CompletableUseCase.java │ │ │ │ ├── DeleteCameraUseCase.java │ │ │ │ ├── LoadCameraUseCase.java │ │ │ │ ├── LoadBackupUseCase.java │ │ │ │ ├── CreateCameraGroupUseCase.java │ │ │ │ ├── ExportBackupUseCase.java │ │ │ │ ├── UpdateCameraGroupUseCase.java │ │ │ │ ├── DataMigrationUseCase.java │ │ │ │ ├── ImportBackupUseCase.java │ │ │ │ ├── GenerateCameraGroupUseCase.java │ │ │ │ └── SaveCameraThumbnailUseCase.java │ │ │ ├── model │ │ │ │ ├── CameraPatternWithVariables.java │ │ │ │ └── CameraGroupModel.java │ │ │ ├── backup │ │ │ │ ├── LoadedBackup.java │ │ │ │ ├── LoadBackupTask.java │ │ │ │ └── Keys.java │ │ │ ├── StartingCameraIntegrityHelper.java │ │ │ ├── CameraThumbnailsIntegrityHelper.java │ │ │ └── urlgenerator │ │ │ │ └── UrlGenerator.java │ │ │ ├── ui │ │ │ ├── settings │ │ │ │ ├── exportbackup │ │ │ │ │ └── ExportBackupHandler.java │ │ │ │ ├── importbackup │ │ │ │ │ └── ImportBackupHandler.java │ │ │ │ ├── SettingsActivity.java │ │ │ │ └── info │ │ │ │ │ ├── LicenseViewerActivity.java │ │ │ │ │ └── AboutAppActivity.java │ │ │ ├── addeditcamera │ │ │ │ ├── CameraFormHandler.java │ │ │ │ ├── editcamera │ │ │ │ │ └── EditCameraActivity.java │ │ │ │ ├── addcamera │ │ │ │ │ └── AddCameraActivity.java │ │ │ │ └── CameraPreviewListAdapter.java │ │ │ ├── player │ │ │ │ ├── view │ │ │ │ │ └── renderer │ │ │ │ │ │ ├── OnImageCapturedListener.java │ │ │ │ │ │ ├── PlayerRendererCallback.java │ │ │ │ │ │ ├── Shader.java │ │ │ │ │ │ └── FrameShader.java │ │ │ │ ├── PlayerHandler.java │ │ │ │ └── PlayerCameraViewModel.java │ │ │ ├── adapters │ │ │ │ ├── dropdown │ │ │ │ │ ├── ModelsDropdownAdapter.java │ │ │ │ │ ├── ProducersDropdownAdapter.java │ │ │ │ │ └── StreamTypesDropdownAdapter.java │ │ │ │ └── base │ │ │ │ │ └── BaseRecyclerViewAdapter.java │ │ │ ├── views │ │ │ │ ├── DropDownListView.java │ │ │ │ └── materialpreferences │ │ │ │ │ └── SwitchPreferenceMaterial.java │ │ │ ├── start │ │ │ │ ├── StartActivity.java │ │ │ │ └── DataMigrationViewModel.java │ │ │ ├── MyBindingAdapter.java │ │ │ ├── BaseActivity.java │ │ │ └── cameralist │ │ │ │ └── CameraListViewModel.java │ │ │ ├── data │ │ │ ├── repositories │ │ │ │ ├── base │ │ │ │ │ ├── CameraThumbnailRepository.java │ │ │ │ │ ├── UrlTemplateRepository.java │ │ │ │ │ └── CameraRepository.java │ │ │ │ └── CameraThumbnailRepositoryImpl.java │ │ │ ├── sources │ │ │ │ ├── local │ │ │ │ │ ├── database │ │ │ │ │ │ ├── CameraGroupDao.java │ │ │ │ │ │ ├── AppDatabase.java │ │ │ │ │ │ ├── CameraPatternDao.java │ │ │ │ │ │ ├── CameraDao.java │ │ │ │ │ │ └── CameraInstanceDao.java │ │ │ │ │ ├── SaveBitmapTask.java │ │ │ │ │ ├── ReadBitmapTask.java │ │ │ │ │ └── ThumbnailDiskCache.java │ │ │ │ └── cache │ │ │ │ │ └── ThumbnailCache.java │ │ │ ├── typeconverters │ │ │ │ └── MapConverter.java │ │ │ └── model │ │ │ │ ├── Camera.java │ │ │ │ ├── urltemplates │ │ │ │ ├── Model.java │ │ │ │ ├── Producer.java │ │ │ │ └── UrlTemplate.java │ │ │ │ ├── CameraGroup.java │ │ │ │ └── CameraInstance.java │ │ │ ├── di │ │ │ ├── ExecutorsModule.java │ │ │ ├── DatabaseModule.java │ │ │ └── RepositoriesModule.java │ │ │ ├── RtspPlayerProvider.java │ │ │ ├── AppConfiguration.java │ │ │ ├── RtspPlayerApp.java │ │ │ ├── AppExecutors.java │ │ │ └── AppThemeHelper.java │ └── androidTest │ │ └── java │ │ └── pl │ │ └── huczeq │ │ └── rtspplayer │ │ └── ExampleInstrumentedTest.java └── proguard-rules.pro ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── settings.gradle ├── .gitignore ├── gradle.properties ├── README.md └── gradlew.bat /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/src/test/resources/robolectric.properties: -------------------------------------------------------------------------------- 1 | sdk=30 -------------------------------------------------------------------------------- /app/src/main/res/values-land/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianhoppe/RTSP-Player/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianhoppe/RTSP-Player/HEAD/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap/icon_camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianhoppe/RTSP-Player/HEAD/app/src/main/res/mipmap/icon_camera.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianhoppe/RTSP-Player/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianhoppe/RTSP-Player/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianhoppe/RTSP-Player/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianhoppe/RTSP-Player/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianhoppe/RTSP-Player/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianhoppe/RTSP-Player/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianhoppe/RTSP-Player/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianhoppe/RTSP-Player/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianhoppe/RTSP-Player/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianhoppe/RTSP-Player/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #7BE380 4 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/util/validation/interfaces/BasicCondition.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.util.validation.interfaces; 2 | 3 | public interface BasicCondition { 4 | boolean allows(); 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/player/OnVideoLayoutChanged.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.player; 2 | 3 | public interface OnVideoLayoutChanged { 4 | void onVideoLayoutChanged(VideoLayout videoLayout); 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/util/interfaces/IOnListItemSelected.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.util.interfaces; 2 | 3 | public interface IOnListItemSelected { 4 | void onCameraItemSelected(T item); 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/util/validation/interfaces/FieldRule.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.util.validation.interfaces; 2 | 3 | public interface FieldRule { 4 | 5 | Integer checkValidity(String text); 6 | } -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/util/validation/interfaces/ValueTransform.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.util.validation.interfaces; 2 | 3 | 4 | public interface ValueTransform { 5 | 6 | T transform(T value); 7 | } -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/domain/cameragenerator/exceptions/LimitReachedException.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.domain.cameragenerator.exceptions; 2 | 3 | public class LimitReachedException extends RuntimeException { 4 | } 5 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/ui/settings/exportbackup/ExportBackupHandler.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.ui.settings.exportbackup; 2 | 3 | public interface ExportBackupHandler { 4 | void selectBackupDestinationFile(); 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/ui/settings/importbackup/ImportBackupHandler.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.ui.settings.importbackup; 2 | 3 | public interface ImportBackupHandler { 4 | void selectBackupFile(); 5 | void restoreBackup(); 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/ui/addeditcamera/CameraFormHandler.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.ui.addeditcamera; 2 | 3 | public interface CameraFormHandler { 4 | void toggleAdvancedVisibility(); 5 | void startPreview(); 6 | void confirmForm(); 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/player/PlayerEventListener.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.player; 2 | 3 | public interface PlayerEventListener { 4 | void onStartRendering(); 5 | void onPlaying(); 6 | void onEndReached(); 7 | void onEncounteredError(); 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/util/interfaces/IOnMenuItemListSelected.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.util.interfaces; 2 | 3 | import android.view.MenuItem; 4 | 5 | public interface IOnMenuItemListSelected { 6 | void onMenuItemSelected(MenuItem menuItem, T item); 7 | } 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Sep 20 00:51:12 CEST 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/ui/player/view/renderer/OnImageCapturedListener.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.ui.player.view.renderer; 2 | 3 | import android.graphics.Bitmap; 4 | 5 | public interface OnImageCapturedListener { 6 | void onImageCaptured(Bitmap bitmap, long id); 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/ui/player/view/renderer/PlayerRendererCallback.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.ui.player.view.renderer; 2 | 3 | public interface PlayerRendererCallback { 4 | 5 | void onSurfaceCreated(); 6 | void onVideoLayoutChanged(int width, int height); 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/ui/player/PlayerHandler.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.ui.player; 2 | 3 | public interface PlayerHandler { 4 | void togglePlayerControlVisibility(); 5 | void reconnect(); 6 | void switchVolume(); 7 | void enterPlayerIntoPictureInPictureMode(); 8 | 9 | void back(); 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_control_player_gradient.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/add.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_back.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/color/text_input_box_stroke_color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_check.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_expand_less.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_expand_more.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/util/states/BaseState.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.util.states; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | @Getter 7 | @AllArgsConstructor 8 | public abstract class BaseState { 9 | 10 | @ProcessingStateType 11 | protected int type; 12 | 13 | public void reset() { 14 | this.type = ProcessingStateType.IDLE; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/util/validation/Errors.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.util.validation; 2 | 3 | public class Errors { 4 | public static final int INCORRECT_VALUE = 1; 5 | public static final int IS_REQUIRED = 2; 6 | public static final int EXPRESSION_OPENSIGN_AFTER_OPEN = 3; 7 | public static final int EXPRESSION_CLOSEDSIGN_WITHOUT_OPEN = 4; 8 | public static final int EXPRESSION_NOT_CLOSED = 5; 9 | } 10 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | maven { url 'https://jitpack.io' } 7 | } 8 | } 9 | dependencyResolutionManagement { 10 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 11 | repositories { 12 | google() 13 | mavenCentral() 14 | } 15 | } 16 | include ':app' 17 | rootProject.name = "RTSP Player" -------------------------------------------------------------------------------- /app/src/main/res/drawable/header_icon_checklist.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_checklist.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/test/java/pl/huczeq/rtspplayer/BaseAndroidTest.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer; 2 | 3 | import org.junit.After; 4 | 5 | import javax.inject.Inject; 6 | 7 | import pl.huczeq.rtspplayer.data.sources.local.database.AppDatabase; 8 | 9 | public abstract class BaseAndroidTest extends BaseDITestTemplate { 10 | 11 | @Inject 12 | public AppDatabase database; 13 | 14 | @After 15 | public void after() { 16 | this.database.close(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/pref_m3_checkbox.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/pref_m3_switch.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_download.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_upload.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/data/repositories/base/CameraThumbnailRepository.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.data.repositories.base; 2 | 3 | import android.graphics.Bitmap; 4 | 5 | import io.reactivex.rxjava3.subjects.PublishSubject; 6 | 7 | public interface CameraThumbnailRepository { 8 | 9 | PublishSubject getThumbnailUpdatedSubject(); 10 | void saveThumbnail(String id, Bitmap bitmap); 11 | Bitmap getThumbnail(String id); 12 | void deleteThumbnail(String previewImg); 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/util/states/ProcessingStateType.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.util.states; 2 | 3 | import androidx.annotation.IntDef; 4 | 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | 8 | @IntDef({ProcessingStateType.IDLE, ProcessingStateType.PROCESSING, ProcessingStateType.DONE}) 9 | @Retention(RetentionPolicy.SOURCE) 10 | public @interface ProcessingStateType { 11 | int IDLE = 0; 12 | int PROCESSING= 1; 13 | int DONE = 2; 14 | } -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/data/sources/local/database/CameraGroupDao.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.data.sources.local.database; 2 | 3 | import androidx.room.Dao; 4 | import androidx.room.Query; 5 | import androidx.room.Transaction; 6 | 7 | import java.util.List; 8 | 9 | import pl.huczeq.rtspplayer.data.model.CameraGroup; 10 | 11 | @Dao 12 | public abstract class CameraGroupDao { 13 | 14 | @Transaction 15 | @Query("SELECT * FROM camerapattern") 16 | public abstract List getAll(); 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_enter_picture_in_picture_mode.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/di/ExecutorsModule.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.di; 2 | 3 | import javax.inject.Singleton; 4 | 5 | import dagger.Module; 6 | import dagger.Provides; 7 | import dagger.hilt.InstallIn; 8 | import dagger.hilt.components.SingletonComponent; 9 | import pl.huczeq.rtspplayer.AppExecutors; 10 | 11 | @Module 12 | @InstallIn(SingletonComponent.class) 13 | public class ExecutorsModule { 14 | 15 | @Provides 16 | @Singleton 17 | public AppExecutors executors() { 18 | return new AppExecutors(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/video_camera_back.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Gradle files 2 | .gradle/ 3 | build/ 4 | 5 | # Local configuration file (sdk path, etc) 6 | local.properties 7 | 8 | # Log/OS Files 9 | *.log 10 | 11 | # Android Studio generated files and folders 12 | captures/ 13 | .externalNativeBuild/ 14 | .cxx/ 15 | *.apk 16 | output.json 17 | 18 | # IntelliJ 19 | *.iml 20 | .idea/ 21 | misc.xml 22 | deploymentTargetDropDown.xml 23 | render.experimental.xml 24 | 25 | # Keystore files 26 | *.jks 27 | *.keystore 28 | 29 | # Google Services (e.g. APIs or Firebase) 30 | google-services.json 31 | 32 | # Android Profiling 33 | *.hprof -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_volume_on.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/folder_open.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/domain/usecases/base/BaseUseCase.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.domain.usecases.base; 2 | 3 | import pl.huczeq.rtspplayer.AppExecutors; 4 | 5 | public abstract class BaseUseCase { 6 | 7 | protected AppExecutors executors; 8 | 9 | public BaseUseCase(AppExecutors executors) { 10 | this.executors = executors; 11 | } 12 | 13 | public AppExecutors.AppExecutor postExecutor() { 14 | return this.executors.mainThread(); 15 | } 16 | 17 | public AppExecutors.AppExecutor executor() { 18 | return this.executors.bgThread(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/data/sources/local/SaveBitmapTask.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.data.sources.local; 2 | 3 | import android.graphics.Bitmap; 4 | 5 | import java.io.File; 6 | import java.io.FileOutputStream; 7 | import java.io.IOException; 8 | 9 | public class SaveBitmapTask { 10 | 11 | public static void save(Bitmap bitmap, File directory, String fileName) throws IOException { 12 | File f = new File(directory, fileName); 13 | FileOutputStream fouts = new FileOutputStream(f); 14 | bitmap.compress(Bitmap.CompressFormat.PNG, 100, fouts); 15 | fouts.flush(); 16 | fouts.close(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/data/repositories/base/UrlTemplateRepository.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.data.repositories.base; 2 | 3 | import androidx.annotation.WorkerThread; 4 | import androidx.lifecycle.LiveData; 5 | 6 | import java.util.List; 7 | 8 | import io.reactivex.rxjava3.core.Flowable; 9 | import io.reactivex.rxjava3.core.Single; 10 | import io.reactivex.rxjava3.core.SingleObserver; 11 | import pl.huczeq.rtspplayer.data.model.urltemplates.Producer; 12 | 13 | public interface UrlTemplateRepository { 14 | 15 | LiveData> getAllProducers(); 16 | 17 | @WorkerThread 18 | List loadAllProducersAndGet(); 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/domain/model/CameraPatternWithVariables.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.domain.model; 2 | 3 | import androidx.room.Ignore; 4 | 5 | import java.util.Map; 6 | 7 | import lombok.ToString; 8 | import pl.huczeq.rtspplayer.data.model.CameraPattern; 9 | 10 | @ToString 11 | public class CameraPatternWithVariables extends CameraPattern { 12 | 13 | @Ignore 14 | private Map variables; 15 | 16 | public Map getVariables() { 17 | return variables; 18 | } 19 | 20 | public void setVariables(Map variables) { 21 | this.variables = variables; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/domain/model/CameraGroupModel.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.domain.model; 2 | 3 | import lombok.Data; 4 | import pl.huczeq.rtspplayer.data.model.urltemplates.Model; 5 | import pl.huczeq.rtspplayer.data.model.urltemplates.Producer; 6 | 7 | @Data 8 | public class CameraGroupModel { 9 | 10 | private String name; 11 | private Producer producer; 12 | private Model model; 13 | private String userName; 14 | private String password; 15 | private String addressIp; 16 | private String port; 17 | private String serverUrl; 18 | private String channel; 19 | private int streamType; 20 | private String url; 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/util/DiffCallback.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.util; 2 | 3 | import androidx.recyclerview.widget.DiffUtil; 4 | 5 | import java.util.List; 6 | 7 | public abstract class DiffCallback extends DiffUtil.Callback { 8 | 9 | protected List oldItems; 10 | protected List newItems; 11 | 12 | public DiffCallback(List oldItems, List newItems) { 13 | this.oldItems = oldItems; 14 | this.newItems = newItems; 15 | } 16 | 17 | @Override 18 | public int getOldListSize() { 19 | return this.oldItems.size(); 20 | } 21 | 22 | @Override 23 | public int getNewListSize() { 24 | return this.newItems.size(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_camera_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 9 | 12 | 15 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/domain/usecases/base/DisposableUseCase.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.domain.usecases.base; 2 | 3 | import androidx.annotation.CallSuper; 4 | 5 | import io.reactivex.rxjava3.disposables.CompositeDisposable; 6 | import pl.huczeq.rtspplayer.AppExecutors; 7 | 8 | public abstract class DisposableUseCase extends BaseUseCase { 9 | 10 | protected final CompositeDisposable disposables = new CompositeDisposable(); 11 | 12 | public DisposableUseCase(AppExecutors executors) { 13 | super(executors); 14 | } 15 | 16 | @CallSuper 17 | public void dispose() { 18 | if (!disposables.isDisposed()) { 19 | disposables.dispose(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/domain/cameragenerator/expression/VariableModel.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.domain.cameragenerator.expression; 2 | 3 | public class VariableModel { 4 | protected String name; 5 | protected String value; 6 | 7 | public VariableModel(String name, String value) { 8 | this.name = name; 9 | this.value = value; 10 | } 11 | 12 | public String getName() { 13 | return name; 14 | } 15 | 16 | public void setName(String name) { 17 | this.name = name; 18 | } 19 | 20 | public String getValue() { 21 | return value; 22 | } 23 | 24 | public void setValue(String value) { 25 | this.value = value; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/data/typeconverters/MapConverter.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.data.typeconverters; 2 | 3 | import androidx.room.TypeConverter; 4 | 5 | import com.google.gson.Gson; 6 | import com.google.gson.reflect.TypeToken; 7 | 8 | import java.util.Map; 9 | 10 | public class MapConverter { 11 | 12 | @TypeConverter 13 | public static String mapToString(Map map) { 14 | Gson gson = new Gson(); 15 | return gson.toJson(map); 16 | } 17 | 18 | @TypeConverter 19 | public static Map mapFromString(String value) { 20 | Gson gson = new Gson(); 21 | return gson.fromJson(value, new TypeToken>(){}.getType()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_volume_off.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/domain/backup/LoadedBackup.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.domain.backup; 2 | 3 | import com.google.gson.JsonObject; 4 | 5 | public class LoadedBackup { 6 | 7 | public final JsonObject jsonObject; 8 | public final boolean containsSettings; 9 | public final boolean containsCameras; 10 | 11 | public LoadedBackup(JsonObject jsonObject) { 12 | this(jsonObject, jsonObject.has(Keys.SETTINGS), jsonObject.has(Keys.V1.CAMERAS) || jsonObject.has(Keys.V2.CAMERA_GROUPS)); 13 | } 14 | 15 | public LoadedBackup(JsonObject jsonObject, boolean containsSettings, boolean containsCameras) { 16 | this.jsonObject = jsonObject; 17 | this.containsSettings = containsSettings; 18 | this.containsCameras = containsCameras; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/di/DatabaseModule.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.di; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.room.Room; 6 | 7 | import javax.inject.Singleton; 8 | 9 | import dagger.Module; 10 | import dagger.Provides; 11 | import dagger.hilt.InstallIn; 12 | import dagger.hilt.android.qualifiers.ApplicationContext; 13 | import dagger.hilt.components.SingletonComponent; 14 | import pl.huczeq.rtspplayer.data.sources.local.database.AppDatabase; 15 | 16 | @Module 17 | @InstallIn(SingletonComponent.class) 18 | public class DatabaseModule { 19 | 20 | @Provides 21 | @Singleton 22 | public AppDatabase appDatabase(@ApplicationContext Context context) { 23 | return Room.databaseBuilder(context, AppDatabase.class, "database").build(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/ui/adapters/dropdown/ModelsDropdownAdapter.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.ui.adapters.dropdown; 2 | 3 | import android.content.Context; 4 | 5 | import pl.huczeq.rtspplayer.R; 6 | import pl.huczeq.rtspplayer.data.model.urltemplates.Model; 7 | import pl.huczeq.rtspplayer.ui.adapters.base.BaseArrayAdapter; 8 | 9 | public class ModelsDropdownAdapter extends BaseArrayAdapter { 10 | 11 | public ModelsDropdownAdapter(Context context) { 12 | super(context, false, R.layout.support_simple_spinner_dropdown_item, 0, R.string.none); 13 | } 14 | 15 | @Override 16 | protected Model buildNoneItem() { 17 | return null; 18 | } 19 | 20 | @Override 21 | public String itemToString(Model item) { 22 | return item.getName(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/res/values-v27/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 16 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/RtspPlayerProvider.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer; 2 | 3 | import javax.inject.Inject; 4 | 5 | import pl.huczeq.rtspplayer.Settings; 6 | import pl.huczeq.rtspplayer.player.RtspPlayer; 7 | import pl.huczeq.rtspplayer.player.exo.ExoPlayerImpl; 8 | import pl.huczeq.rtspplayer.player.vlc.VlcPlayerImpl; 9 | 10 | public class RtspPlayerProvider { 11 | 12 | @Inject 13 | public VlcPlayerImpl.Factory vlcFactory; 14 | @Inject 15 | public ExoPlayerImpl.Factory exoFactory; 16 | @Inject 17 | public Settings settings; 18 | 19 | @Inject 20 | public RtspPlayerProvider() {} 21 | 22 | public RtspPlayer build(String... args) { 23 | if(settings.useExoPlayer()) 24 | return exoFactory.build(args); 25 | return vlcFactory.build(args); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/main/res/drawable/launcher_icon.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/data/sources/cache/ThumbnailCache.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.data.sources.cache; 2 | 3 | import android.graphics.Bitmap; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | import javax.inject.Inject; 9 | import javax.inject.Singleton; 10 | 11 | @Singleton 12 | public class ThumbnailCache { 13 | 14 | private Map bitmaps; 15 | 16 | @Inject 17 | public ThumbnailCache() { 18 | this.bitmaps = new HashMap<>(); 19 | } 20 | 21 | public void save(String key, Bitmap bitmap) { 22 | bitmaps.put(key, bitmap); 23 | } 24 | 25 | public Bitmap get(String key) { 26 | if(key == null) 27 | return null; 28 | return bitmaps.get(key); 29 | } 30 | 31 | public void cleanUp() { 32 | bitmaps.clear(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/ui/adapters/dropdown/ProducersDropdownAdapter.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.ui.adapters.dropdown; 2 | 3 | import android.content.Context; 4 | 5 | import pl.huczeq.rtspplayer.R; 6 | import pl.huczeq.rtspplayer.data.model.urltemplates.Producer; 7 | import pl.huczeq.rtspplayer.ui.adapters.base.BaseArrayAdapter; 8 | 9 | public class ProducersDropdownAdapter extends BaseArrayAdapter { 10 | 11 | public ProducersDropdownAdapter(Context context) { 12 | super(context, true, R.layout.support_simple_spinner_dropdown_item, 0, R.string.none); 13 | } 14 | 15 | @Override 16 | protected Producer buildNoneItem() { 17 | return new Producer((String) null); 18 | } 19 | 20 | @Override 21 | public String itemToString(Producer item) { 22 | return item.getName(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/launcher_icon_small.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/AppConfiguration.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer; 2 | 3 | import android.content.Context; 4 | 5 | import java.io.File; 6 | 7 | import javax.inject.Inject; 8 | import javax.inject.Singleton; 9 | 10 | import dagger.hilt.android.qualifiers.ApplicationContext; 11 | 12 | @Singleton 13 | public class AppConfiguration { 14 | 15 | public static String getFullAppVersion() { 16 | return BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ") - " + BuildConfig.BUILD_TIME; 17 | } 18 | 19 | private final Context appContext; 20 | 21 | @Inject 22 | public AppConfiguration(@ApplicationContext Context context) { 23 | this.appContext = context.getApplicationContext(); 24 | } 25 | 26 | public File getThumbnailDirectory() { 27 | return this.appContext.getCacheDir(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/util/validation/interfaces/ValueProvider.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.util.validation.interfaces; 2 | 3 | public interface ValueProvider { 4 | 5 | T provideValue(); 6 | 7 | static ValueProvider transform(ValueTransform transform, ValueProvider valueProvider) { 8 | return new ValueProvider() { 9 | @Override 10 | public T provideValue() { 11 | return transform.transform(valueProvider.provideValue()); 12 | } 13 | }; 14 | } 15 | 16 | static FieldRule fieldRuleWith(ValueTransform transform, FieldRule fieldRule) { 17 | return new FieldRule() { 18 | @Override 19 | public Integer checkValidity(String text) { 20 | return null; 21 | } 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/androidTest/java/pl/huczeq/rtspplayer/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.ext.junit.runners.AndroidJUnit4; 6 | import androidx.test.platform.app.InstrumentationRegistry; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 23 | assertEquals("pl.huczeq.rtspplayer", appContext.getPackageName()); 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_confirm.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/launcher_monochrome_icon_small.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/test/java/pl/huczeq/rtspplayer/BaseDITestTemplate.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer; 2 | 3 | import androidx.annotation.CallSuper; 4 | 5 | import org.junit.Before; 6 | import org.junit.Rule; 7 | import org.junit.runner.RunWith; 8 | import org.robolectric.RobolectricTestRunner; 9 | import org.robolectric.annotation.Config; 10 | 11 | import dagger.hilt.android.testing.HiltAndroidRule; 12 | import dagger.hilt.android.testing.HiltAndroidTest; 13 | import dagger.hilt.android.testing.HiltTestApplication; 14 | 15 | @RunWith(RobolectricTestRunner.class) 16 | @HiltAndroidTest 17 | @Config(application = HiltTestApplication.class) 18 | public abstract class BaseDITestTemplate { 19 | 20 | @Rule 21 | public HiltAndroidRule hiltRule = new HiltAndroidRule(this); 22 | 23 | @Before 24 | public void prepareBaseDiTestTemplate() { 25 | this.hiltRule.inject(); 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/data/sources/local/database/AppDatabase.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.data.sources.local.database; 2 | 3 | import androidx.room.Database; 4 | import androidx.room.RoomDatabase; 5 | import androidx.room.TypeConverters; 6 | 7 | import pl.huczeq.rtspplayer.data.model.CameraInstance; 8 | import pl.huczeq.rtspplayer.data.model.CameraPattern; 9 | import pl.huczeq.rtspplayer.data.typeconverters.MapConverter; 10 | 11 | @Database(entities = {CameraInstance.class, CameraPattern.class}, version = 1, exportSchema = true) 12 | @TypeConverters({MapConverter.class}) 13 | public abstract class AppDatabase extends RoomDatabase { 14 | 15 | public abstract CameraDao cameraDao(); 16 | public abstract CameraInstanceDao cameraInstanceDao(); 17 | public abstract CameraPatternDao cameraPatternDao(); 18 | public abstract CameraGroupDao cameraGroupDao(); 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_camera_group_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 9 | 12 | 15 | 18 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/data/model/Camera.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.data.model; 2 | 3 | import androidx.room.Embedded; 4 | import androidx.room.Relation; 5 | 6 | import lombok.Data; 7 | import lombok.Value; 8 | 9 | @Data 10 | public class Camera { 11 | 12 | @Embedded 13 | private CameraInstance cameraInstance; 14 | 15 | @Relation( 16 | parentColumn = "patternId", 17 | entityColumn = "id" 18 | ) 19 | private CameraPattern cameraPattern; 20 | 21 | public Camera(CameraInstance cameraInstance, CameraPattern cameraPattern) { 22 | this.cameraInstance = cameraInstance; 23 | this.cameraPattern = cameraPattern; 24 | } 25 | 26 | public CameraInstance getCameraInstance() { 27 | return cameraInstance; 28 | } 29 | 30 | public CameraPattern getCameraPattern() { 31 | return cameraPattern; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/util/validation/RuleWithValueTransform.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.util.validation; 2 | 3 | 4 | import pl.huczeq.rtspplayer.util.validation.interfaces.FieldRule; 5 | import pl.huczeq.rtspplayer.util.validation.interfaces.ValueTransform; 6 | 7 | public abstract class RuleWithValueTransform implements ValueTransform, FieldRule { 8 | 9 | private FieldRule rule; 10 | 11 | public RuleWithValueTransform(FieldRule rule) { 12 | this.rule = rule; 13 | } 14 | 15 | @Override 16 | public Integer checkValidity(String text) { 17 | String newValue = transform(text); 18 | Integer optv = onPostTransformValidity(); 19 | if(optv != null) 20 | return optv; 21 | return rule.checkValidity(newValue); 22 | } 23 | 24 | protected Integer onPostTransformValidity() { 25 | return null; 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_app_license.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | 11 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_camera_preview.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 16 | 23 | -------------------------------------------------------------------------------- /app/src/test/java/pl/huczeq/rtspplayer/domain/usecases/BaseUseCaseTestTemplateTest.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.domain.usecases; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.robolectric.RobolectricTestRunner; 8 | import org.robolectric.annotation.Config; 9 | 10 | import dagger.hilt.android.testing.HiltAndroidTest; 11 | import dagger.hilt.android.testing.HiltTestApplication; 12 | import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; 13 | 14 | @RunWith(RobolectricTestRunner.class) 15 | @HiltAndroidTest 16 | @Config(application = HiltTestApplication.class) 17 | public class BaseUseCaseTestTemplateTest extends BaseUseCaseTestTemplate{ 18 | 19 | @Test 20 | public void mainThreadScheduler_Should_NotEquals_AndroidMainThreadScheduler() { 21 | assertNotEquals(appExecutors.mainThread().scheduler(), AndroidSchedulers.mainThread()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/domain/usecases/base/NoResultUseCase.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.domain.usecases.base; 2 | 3 | import androidx.annotation.CallSuper; 4 | 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import io.reactivex.rxjava3.core.Scheduler; 8 | import io.reactivex.rxjava3.core.Single; 9 | import io.reactivex.rxjava3.disposables.Disposable; 10 | import pl.huczeq.rtspplayer.AppExecutors; 11 | 12 | public abstract class NoResultUseCase extends DisposableUseCase { 13 | 14 | public NoResultUseCase(AppExecutors executors) { 15 | super(executors); 16 | } 17 | 18 | @NotNull 19 | public Scheduler getDefaultScheduler() { 20 | return executors.dbIO().scheduler(); 21 | } 22 | 23 | protected abstract Runnable buildRunnable(Params params); 24 | 25 | @CallSuper 26 | public void execute(Params params) { 27 | this.disposables.add(getDefaultScheduler().scheduleDirect(buildRunnable(params))); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/domain/usecases/DeleteCameraUseCase.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.domain.usecases; 2 | 3 | import javax.inject.Inject; 4 | 5 | import pl.huczeq.rtspplayer.AppExecutors; 6 | import pl.huczeq.rtspplayer.data.model.Camera; 7 | import pl.huczeq.rtspplayer.data.repositories.base.CameraRepository; 8 | import pl.huczeq.rtspplayer.domain.usecases.base.NoResultUseCase; 9 | 10 | public class DeleteCameraUseCase extends NoResultUseCase { 11 | 12 | private CameraRepository cameraRepository; 13 | 14 | @Inject 15 | public DeleteCameraUseCase(CameraRepository cameraRepository, AppExecutors executors) { 16 | super(executors); 17 | this.cameraRepository = cameraRepository; 18 | } 19 | 20 | @Override 21 | protected Runnable buildRunnable(Camera camera) { 22 | return new Runnable() { 23 | @Override 24 | public void run() { 25 | cameraRepository.deleteCameraGroup(camera.getCameraPattern()); 26 | } 27 | }; 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/test/java/pl/huczeq/rtspplayer/domain/cameragenerator/ExpressionTest.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.domain.cameragenerator; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.robolectric.RobolectricTestRunner; 8 | 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | import pl.huczeq.rtspplayer.domain.cameragenerator.expression.Expression; 14 | 15 | @RunWith(RobolectricTestRunner.class) 16 | public class ExpressionTest { 17 | 18 | @Test 19 | public void test_oneParameter() { 20 | Map variables = new HashMap<>(); 21 | variables.put("par1", "1-4"); 22 | Expression expression = new Expression("abcd{par1}", variables); 23 | List> data = expression.generateVariations(); 24 | assertEquals(4, data.size()); 25 | for(int i = 0; i < data.size(); i++) { 26 | assertEquals(String.valueOf(i+1), data.get(i).get("par1")); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/data/sources/local/ReadBitmapTask.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.data.sources.local; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.BitmapFactory; 5 | 6 | import java.io.File; 7 | import java.io.FileInputStream; 8 | import java.io.FileNotFoundException; 9 | 10 | public class ReadBitmapTask { 11 | 12 | public static Bitmap load(File directory, String fileName) { 13 | File f = new File(directory, fileName); 14 | if(!f.exists()) { 15 | return null; 16 | } 17 | return loadBitmapFromFile(f); 18 | } 19 | 20 | private static Bitmap loadBitmapFromFile(File file) { 21 | Bitmap bitmap = null; 22 | BitmapFactory.Options options = new BitmapFactory.Options(); 23 | options.inPreferredConfig = Bitmap.Config.ARGB_8888; 24 | try { 25 | bitmap = BitmapFactory.decodeStream(new FileInputStream(file), null, options); 26 | } catch (FileNotFoundException e) { 27 | e.printStackTrace(); 28 | } 29 | return bitmap; 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/player/vlc/VlcFactory.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.player.vlc; 2 | 3 | import android.content.Context; 4 | 5 | import org.videolan.libvlc.LibVLC; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | import java.util.Collections; 10 | 11 | import pl.huczeq.rtspplayer.Settings; 12 | 13 | public class VlcFactory { 14 | 15 | public static ArrayList buildArgumentList(Settings settings, String... params) { 16 | ArrayList args = new ArrayList<>(Arrays.asList("--vout=android-display,none", "-vvv")); 17 | 18 | args.add("--avcodec-skiploopfilter"); 19 | args.add("0"); 20 | args.add("--avcodec-skip-frame"); 21 | args.add("0"); 22 | args.add("--avcodec-skip-idct"); 23 | args.add("0"); 24 | 25 | Collections.addAll(args, params); 26 | return args; 27 | } 28 | 29 | public static LibVLC buildLibVLC(Context context, Settings settings, String ...params) { 30 | ArrayList args = buildArgumentList(settings, params); 31 | return new LibVLC(context, args); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/domain/backup/LoadBackupTask.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.domain.backup; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.JsonObject; 5 | 6 | import java.io.InputStream; 7 | import java.io.InputStreamReader; 8 | import java.util.concurrent.Callable; 9 | 10 | import javax.inject.Inject; 11 | 12 | public class LoadBackupTask implements Callable { 13 | private InputStream inputStream; 14 | 15 | @Inject 16 | public LoadBackupTask() {} 17 | 18 | public void init(InputStream inputStream) { 19 | this.inputStream = inputStream; 20 | } 21 | 22 | @Override 23 | public LoadedBackup call() { 24 | assert this.inputStream != null; 25 | Gson gson = new Gson(); 26 | JsonObject jsonObject = gson.fromJson(new InputStreamReader(inputStream), JsonObject.class); 27 | if(!jsonObject.has(Keys.SETTINGS) && !jsonObject.has(Keys.V1.CAMERAS) && !jsonObject.has(Keys.V2.CAMERA_GROUPS)) 28 | throw new RuntimeException("Backup file contains no data!"); 29 | return new LoadedBackup(jsonObject); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/domain/StartingCameraIntegrityHelper.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.domain; 2 | 3 | import com.google.common.base.Strings; 4 | 5 | import javax.inject.Inject; 6 | import javax.inject.Singleton; 7 | 8 | import pl.huczeq.rtspplayer.Settings; 9 | import pl.huczeq.rtspplayer.data.model.CameraInstance; 10 | import pl.huczeq.rtspplayer.data.repositories.base.CameraRepository; 11 | import pl.huczeq.rtspplayer.data.repositories.base.CameraThumbnailRepository; 12 | 13 | @Singleton 14 | public class StartingCameraIntegrityHelper { 15 | 16 | private final Settings settings; 17 | 18 | @Inject 19 | public StartingCameraIntegrityHelper(CameraRepository cameraRepository, Settings settings) { 20 | this.settings = settings; 21 | cameraRepository.getCameraInstancesInvalidatedSubject().subscribe(this::accept); 22 | } 23 | 24 | public void accept(CameraInstance cameraInstance) { 25 | long cameraInstanceId = cameraInstance.getId(); 26 | if(settings.getAppStartCameraId() == cameraInstanceId) { 27 | settings.setAppStartCameraId(-1); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/data/sources/local/database/CameraPatternDao.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.data.sources.local.database; 2 | 3 | import androidx.lifecycle.LiveData; 4 | import androidx.room.Dao; 5 | import androidx.room.Insert; 6 | import androidx.room.Query; 7 | import androidx.room.Update; 8 | 9 | import java.util.List; 10 | 11 | import pl.huczeq.rtspplayer.data.model.CameraPattern; 12 | 13 | @Dao 14 | public abstract class CameraPatternDao { 15 | 16 | @Query("SELECT * FROM camerapattern WHERE id = :id") 17 | public abstract LiveData getCameraPatternByIdSync(long id); 18 | 19 | @Query("SELECT * FROM camerapattern") 20 | public abstract List getAllCameraPatternsSync(); 21 | 22 | @Insert 23 | public abstract long insertCameraPattern(CameraPattern cameraPattern); 24 | 25 | @Update 26 | public abstract void updateCameraPattern(CameraPattern cameraPattern); 27 | 28 | @Query("DELETE FROM camerapattern WHERE id = :id") 29 | public abstract void deleteCameraPattern(long id); 30 | 31 | @Query("DELETE FROM camerapattern") 32 | public abstract void deleteAll(); 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/data/sources/local/database/CameraDao.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.data.sources.local.database; 2 | 3 | import androidx.lifecycle.LiveData; 4 | import androidx.room.Dao; 5 | import androidx.room.Query; 6 | import androidx.room.Transaction; 7 | 8 | import java.util.List; 9 | 10 | import pl.huczeq.rtspplayer.data.model.Camera; 11 | 12 | @Dao 13 | public abstract class CameraDao { 14 | 15 | @Transaction 16 | @Query("SELECT * FROM camerainstance WHERE id = :id") 17 | public abstract LiveData getCameraById(long id); 18 | 19 | @Transaction 20 | @Query("SELECT * FROM camerainstance WHERE id = :id") 21 | public abstract Camera getCameraByIdSync(long id); 22 | 23 | @Transaction 24 | @Query("SELECT * FROM camerainstance") 25 | public abstract LiveData> getAllCameras2(); 26 | 27 | @Transaction 28 | @Query("SELECT * FROM camerainstance ORDER BY patternId ASC, id ASC") 29 | public abstract LiveData> getAllCameras(); 30 | 31 | @Transaction 32 | @Query("SELECT * FROM camerainstance") 33 | public abstract List getAllCamerasSync(); 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/ui/settings/SettingsActivity.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.ui.settings; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.util.Log; 6 | 7 | import androidx.annotation.Nullable; 8 | 9 | import dagger.hilt.android.AndroidEntryPoint; 10 | import pl.huczeq.rtspplayer.R; 11 | import pl.huczeq.rtspplayer.ui.BaseActivity; 12 | 13 | @AndroidEntryPoint 14 | public class SettingsActivity extends BaseActivity { 15 | 16 | @Override 17 | protected void onCreate(@Nullable Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | setContentView(R.layout.activity_settings); 20 | showBackToolbarIcon(); 21 | // 22 | // Bundle bundle = new Bundle(); 23 | // bundle.putString(SettingsFragment.ARG_PREFERENCE_ROOT, "root"); 24 | // 25 | // SettingsFragment settingsFragment = new SettingsFragment(); 26 | // settingsFragment.setArguments(bundle); 27 | 28 | getSupportFragmentManager() 29 | .beginTransaction() 30 | .replace(R.id.settings_container, new SettingsFragment()) 31 | .commit(); 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/ui/adapters/dropdown/StreamTypesDropdownAdapter.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.ui.adapters.dropdown; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | 6 | import java.util.List; 7 | 8 | import pl.huczeq.rtspplayer.R; 9 | import pl.huczeq.rtspplayer.data.model.urltemplates.UrlTemplate; 10 | import pl.huczeq.rtspplayer.ui.adapters.base.BaseArrayAdapter; 11 | 12 | public class StreamTypesDropdownAdapter extends BaseArrayAdapter { 13 | 14 | private final Resources resources; 15 | 16 | public StreamTypesDropdownAdapter(Context context) { 17 | super(context, R.layout.support_simple_spinner_dropdown_item, List.of(UrlTemplate.StreamType.MAIN_STREAM, UrlTemplate.StreamType.SUB_STREAM)); 18 | this.resources = context.getResources(); 19 | } 20 | 21 | @Override 22 | public String itemToString(Integer item) { 23 | return item == 0? resources.getString(R.string.stream_type_label_main_stream) : resources.getString(R.string.stream_type_label_sub_stream); 24 | } 25 | 26 | @Override 27 | protected Integer buildNoneItem() { 28 | return null; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/domain/cameragenerator/exceptions/ParsingException.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.domain.cameragenerator.exceptions; 2 | 3 | public class ParsingException extends RuntimeException { 4 | 5 | private Error error; 6 | private String data; 7 | 8 | public ParsingException(Error error) { 9 | super(error.toString()); 10 | this.error = error; 11 | this.data = null; 12 | } 13 | 14 | public ParsingException(Error error, String data) { 15 | super(error.toString()); 16 | this.error = error; 17 | this.data = data; 18 | } 19 | 20 | public Error getError() { 21 | return this.error; 22 | } 23 | 24 | public String getData() { 25 | return data; 26 | } 27 | 28 | public enum Error { 29 | DATA_EMPTY, 30 | DATA_INCORRECT, 31 | FORMAT_NUMBER_ERROR, 32 | NUMBER_INTERVAL_ARRAY_ERROR, 33 | NUMBER_INTERVAL_ORDER_ERROR, 34 | FIRST_LENGHT_ARRAY_ERROR, 35 | NEGATIVE_NUMBER_ERROR, 36 | EXCEEDED_MAX_NUMBER_OF_VARIATIONS, 37 | EMPTY_EXPRESSION, 38 | DATA_EXPECTED, 39 | NAME_IS_TAKEN 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/data/model/urltemplates/Model.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.data.model.urltemplates; 2 | 3 | import org.json.JSONException; 4 | import org.json.JSONObject; 5 | 6 | public class Model { 7 | 8 | private String name; 9 | private UrlTemplate urlTemplate; 10 | 11 | public Model(String name, UrlTemplate urlTemplate) { 12 | this.name = name; 13 | this.urlTemplate = urlTemplate; 14 | } 15 | 16 | public Model(JSONObject json) { 17 | this.name = json.optString("name", "-"); 18 | try { 19 | this.urlTemplate = new UrlTemplate(json.getJSONObject("urlTemplate")); 20 | if(!this.urlTemplate.isCorrect()) this.name = null; 21 | } catch (JSONException e) { 22 | e.printStackTrace(); 23 | this.name = null; 24 | } 25 | } 26 | 27 | public boolean isCorrect() { 28 | return (this.name!=null); 29 | } 30 | 31 | public String getName() { 32 | return name; 33 | } 34 | 35 | public void setName(String name) { 36 | this.name = name; 37 | } 38 | 39 | public UrlTemplate getUrlTemplate() { 40 | return urlTemplate; 41 | } 42 | } -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/di/RepositoriesModule.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.di; 2 | 3 | import dagger.Binds; 4 | import dagger.Module; 5 | import dagger.hilt.InstallIn; 6 | import dagger.hilt.components.SingletonComponent; 7 | import pl.huczeq.rtspplayer.data.repositories.base.CameraRepository; 8 | import pl.huczeq.rtspplayer.data.repositories.CameraRepositoryImpl; 9 | import pl.huczeq.rtspplayer.data.repositories.base.CameraThumbnailRepository; 10 | import pl.huczeq.rtspplayer.data.repositories.CameraThumbnailRepositoryImpl; 11 | import pl.huczeq.rtspplayer.data.repositories.base.UrlTemplateRepository; 12 | import pl.huczeq.rtspplayer.data.repositories.UrlTemplateRepositoryImpl; 13 | 14 | @Module 15 | @InstallIn(SingletonComponent.class) 16 | public abstract class RepositoriesModule { 17 | 18 | @Binds 19 | public abstract CameraRepository camerasRepository(CameraRepositoryImpl camerasRepository); 20 | 21 | @Binds 22 | public abstract CameraThumbnailRepository thumbnailsRepository(CameraThumbnailRepositoryImpl thumbnailsRepository); 23 | 24 | @Binds 25 | public abstract UrlTemplateRepository urlTemplatesRepository(UrlTemplateRepositoryImpl urlTemplatesRepository); 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/domain/usecases/LoadCameraUseCase.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.domain.usecases; 2 | 3 | import java.util.concurrent.Callable; 4 | 5 | import javax.inject.Inject; 6 | 7 | import io.reactivex.rxjava3.core.Single; 8 | import pl.huczeq.rtspplayer.AppExecutors; 9 | import pl.huczeq.rtspplayer.data.model.Camera; 10 | import pl.huczeq.rtspplayer.data.repositories.base.CameraRepository; 11 | import pl.huczeq.rtspplayer.domain.usecases.base.SingleUseCase; 12 | 13 | public class LoadCameraUseCase extends SingleUseCase { 14 | 15 | private CameraRepository cameraRepository; 16 | 17 | @Inject 18 | public LoadCameraUseCase(AppExecutors executors, CameraRepository cameraRepository) { 19 | super(executors); 20 | this.cameraRepository = cameraRepository; 21 | } 22 | 23 | @Override 24 | protected Single buildObservable(Long cameraInstanceId) { 25 | return Single.fromCallable(new Callable() { 26 | @Override 27 | public Camera call() throws Exception { 28 | return cameraRepository.getCameraByIdSync(cameraInstanceId); 29 | } 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/ui/settings/info/LicenseViewerActivity.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.ui.settings.info; 2 | 3 | import android.os.Bundle; 4 | import android.view.View; 5 | import android.webkit.WebView; 6 | import android.webkit.WebViewClient; 7 | import android.widget.ProgressBar; 8 | 9 | import androidx.appcompat.app.AppCompatActivity; 10 | 11 | import pl.huczeq.rtspplayer.R; 12 | 13 | public class LicenseViewerActivity extends AppCompatActivity { 14 | 15 | private ProgressBar progressBar; 16 | private WebView webView; 17 | 18 | protected void onCreate(Bundle savedInstanceState) { 19 | super.onCreate(savedInstanceState); 20 | setContentView(R.layout.activity_app_license); 21 | 22 | progressBar = findViewById(R.id.progressBar); 23 | webView = findViewById(R.id.webView); 24 | 25 | webView.setWebViewClient(new WebViewClient() { 26 | @Override 27 | public void onPageFinished(WebView view, String url) { 28 | super.onPageFinished(view, url); 29 | progressBar.setVisibility(View.INVISIBLE); 30 | } 31 | }); 32 | webView.loadUrl("file:///android_asset/license.txt"); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | org.gradle.daemon=true 21 | android.nonTransitiveRClass=false 22 | android.nonFinalResIds=false -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/domain/usecases/LoadBackupUseCase.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.domain.usecases; 2 | 3 | import java.io.InputStream; 4 | import java.io.OutputStream; 5 | 6 | import javax.inject.Inject; 7 | 8 | import io.reactivex.rxjava3.core.Completable; 9 | import io.reactivex.rxjava3.core.Single; 10 | import pl.huczeq.rtspplayer.AppExecutors; 11 | import pl.huczeq.rtspplayer.domain.backup.ExportBackupTask; 12 | import pl.huczeq.rtspplayer.domain.backup.LoadBackupTask; 13 | import pl.huczeq.rtspplayer.domain.backup.LoadedBackup; 14 | import pl.huczeq.rtspplayer.domain.usecases.base.CompletableUseCase; 15 | import pl.huczeq.rtspplayer.domain.usecases.base.SingleUseCase; 16 | 17 | public class LoadBackupUseCase extends SingleUseCase { 18 | 19 | private LoadBackupTask loadBackupTask; 20 | 21 | @Inject 22 | public LoadBackupUseCase(AppExecutors executors, LoadBackupTask loadBackupTask) { 23 | super(executors); 24 | this.loadBackupTask = loadBackupTask; 25 | } 26 | 27 | @Override 28 | protected Single buildObservable(InputStream inputStream) { 29 | this.loadBackupTask.init(inputStream); 30 | return Single.fromCallable(this.loadBackupTask); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/domain/CameraThumbnailsIntegrityHelper.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.domain; 2 | 3 | import android.util.Log; 4 | 5 | import com.google.common.base.Strings; 6 | 7 | import javax.inject.Inject; 8 | import javax.inject.Singleton; 9 | 10 | import io.reactivex.rxjava3.disposables.Disposable; 11 | import pl.huczeq.rtspplayer.data.model.CameraInstance; 12 | import pl.huczeq.rtspplayer.data.repositories.base.CameraRepository; 13 | import pl.huczeq.rtspplayer.data.repositories.base.CameraThumbnailRepository; 14 | 15 | @Singleton 16 | public class CameraThumbnailsIntegrityHelper { 17 | 18 | private final CameraThumbnailRepository cameraThumbnailRepository; 19 | 20 | @Inject 21 | public CameraThumbnailsIntegrityHelper(CameraRepository cameraRepository, CameraThumbnailRepository cameraThumbnailRepository) { 22 | this.cameraThumbnailRepository = cameraThumbnailRepository; 23 | cameraRepository.getCameraInstancesInvalidatedSubject().subscribe(this::accept); 24 | } 25 | 26 | public void accept(CameraInstance cameraInstance) { 27 | if(Strings.isNullOrEmpty(cameraInstance.getPreviewImg())) 28 | return; 29 | cameraThumbnailRepository.deleteThumbnail(cameraInstance.getPreviewImg()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/ui/addeditcamera/editcamera/EditCameraActivity.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.ui.addeditcamera.editcamera; 2 | 3 | import android.widget.Toast; 4 | 5 | import androidx.lifecycle.ViewModelProvider; 6 | 7 | import javax.inject.Inject; 8 | 9 | import dagger.hilt.android.AndroidEntryPoint; 10 | import pl.huczeq.rtspplayer.R; 11 | import pl.huczeq.rtspplayer.ui.addeditcamera.BaseCameraFormActivity; 12 | import pl.huczeq.rtspplayer.ui.addeditcamera.CameraFormViewModel; 13 | 14 | @AndroidEntryPoint 15 | public class EditCameraActivity extends BaseCameraFormActivity { 16 | 17 | @Inject 18 | public EditCameraViewModel.AssistedFactory viewModelAssistedFactory; 19 | 20 | private CameraFormViewModel viewModel; 21 | 22 | @Override 23 | protected CameraFormViewModel buildViewModel() { 24 | long cameraInstanceId = getIntent().getLongExtra(EXTRA_CAMERA_INSTANCE_ID, -1); 25 | if(cameraInstanceId < 0) { 26 | Toast.makeText(this, R.string.error, Toast.LENGTH_SHORT).show(); 27 | return null; 28 | } 29 | viewModel = new ViewModelProvider(this, new EditCameraViewModel.Factory(this.viewModelAssistedFactory, cameraInstanceId)).get(EditCameraViewModel.class); 30 | return viewModel; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/res/layout/toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 17 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/domain/usecases/CreateCameraGroupUseCase.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.domain.usecases; 2 | 3 | import javax.inject.Inject; 4 | 5 | import io.reactivex.rxjava3.core.Completable; 6 | import pl.huczeq.rtspplayer.AppExecutors; 7 | import pl.huczeq.rtspplayer.data.model.CameraGroup; 8 | import pl.huczeq.rtspplayer.data.repositories.base.CameraRepository; 9 | import pl.huczeq.rtspplayer.domain.usecases.base.CompletableUseCase; 10 | import pl.huczeq.rtspplayer.domain.cameragenerator.CameraGroupGenerator; 11 | import pl.huczeq.rtspplayer.domain.model.CameraGroupModel; 12 | 13 | public class CreateCameraGroupUseCase extends CompletableUseCase { 14 | 15 | @Inject 16 | public CameraRepository cameraRepository; 17 | 18 | @Inject 19 | public CreateCameraGroupUseCase(AppExecutors executors) { 20 | super(executors); 21 | } 22 | 23 | @Override 24 | protected Completable buildObservable(CameraGroupModel cameraGroupModel) { 25 | return Completable.fromRunnable(new Runnable() { 26 | @Override 27 | public void run() { 28 | CameraGroupGenerator cameraGroupGenerator = new CameraGroupGenerator(); 29 | CameraGroup cameraGroup = cameraGroupGenerator.generate(cameraGroupModel); 30 | cameraRepository.insertCameraGroup(cameraGroup); 31 | } 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/player/RtspPlayer.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.player; 2 | 3 | import android.net.Uri; 4 | 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | import pl.huczeq.rtspplayer.ui.player.view.PlayerSurfaceView; 8 | 9 | public abstract class RtspPlayer { 10 | 11 | protected PlayerEventListener eventListener; 12 | protected OnVideoLayoutChanged surfaceSizeChangedListener; 13 | 14 | public void setEventListener(PlayerEventListener eventListener) { 15 | this.eventListener = eventListener; 16 | } 17 | protected void setOnSurfaceSizeChangedListener(OnVideoLayoutChanged surfaceSizeChangedListener) { 18 | this.surfaceSizeChangedListener = surfaceSizeChangedListener; 19 | } 20 | 21 | public abstract void loadMedia(RtspMedia rtspMedia); 22 | public abstract boolean isViewAttached(); 23 | public abstract void attachView(PlayerSurfaceView view); 24 | public abstract void detachView(); 25 | public abstract void play(); 26 | public abstract void pause(); 27 | public abstract void stop(); 28 | public abstract void release(); 29 | public abstract boolean isPlaying(); 30 | public abstract boolean isMute(); 31 | public abstract void setMute(boolean isMuted); 32 | 33 | @AllArgsConstructor 34 | @Getter 35 | public static class RtspMedia { 36 | private Uri uri; 37 | private boolean forceTcpEnabled; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/data/model/CameraGroup.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.data.model; 2 | 3 | import androidx.room.Embedded; 4 | import androidx.room.Ignore; 5 | import androidx.room.Relation; 6 | 7 | import java.util.List; 8 | 9 | import lombok.Data; 10 | 11 | @Data 12 | public class CameraGroup { 13 | 14 | @Embedded 15 | private CameraPattern cameraPattern; 16 | 17 | @Relation( 18 | parentColumn = "id", 19 | entityColumn = "patternId" 20 | ) 21 | private List cameraInstances; 22 | 23 | public CameraGroup() {} 24 | 25 | @Ignore 26 | public CameraGroup(CameraPattern cameraPattern){ 27 | this.cameraPattern = cameraPattern; 28 | } 29 | 30 | @Ignore 31 | public CameraGroup(CameraPattern cameraPattern, List cameraInstances) { 32 | this.cameraPattern = cameraPattern; 33 | this.cameraInstances = cameraInstances; 34 | } 35 | 36 | public CameraPattern getCameraPattern() { 37 | return cameraPattern; 38 | } 39 | 40 | public List getCameraInstances() { 41 | return cameraInstances; 42 | } 43 | 44 | public void setCameraPattern(CameraPattern cameraPattern) { 45 | this.cameraPattern = cameraPattern; 46 | } 47 | 48 | public void setCameraInstances(List cameraInstances) { 49 | this.cameraInstances = cameraInstances; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/domain/cameragenerator/CameraGroupGenerator.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.domain.cameragenerator; 2 | 3 | import java.util.List; 4 | 5 | import pl.huczeq.rtspplayer.data.model.CameraGroup; 6 | import pl.huczeq.rtspplayer.data.model.CameraInstance; 7 | import pl.huczeq.rtspplayer.data.model.CameraPattern; 8 | import pl.huczeq.rtspplayer.domain.model.CameraPatternWithVariables; 9 | import pl.huczeq.rtspplayer.domain.model.CameraGroupModel; 10 | 11 | public class CameraGroupGenerator { 12 | 13 | 14 | public static final String TAG = CameraGroupGenerator.class.getSimpleName(); 15 | 16 | public CameraGroup generate(CameraGroupModel cameraGroupModel) { 17 | CameraPattern cameraPattern = CameraPatternMapper.toCameraPattern(cameraGroupModel); 18 | CameraPatternWithVariables cameraPatternWithVariables = CameraPatternMapper.toPatternWithVariables(cameraPattern, cameraGroupModel.getModel()!=null? cameraGroupModel.getModel().getUrlTemplate() : null); 19 | 20 | CameraGroup cameraGroup = new CameraGroup(cameraPattern); 21 | 22 | CameraInstancesGenerator instancesFactory = new CameraInstancesGenerator(cameraPatternWithVariables); 23 | List instancesGenerated = instancesFactory.build(); 24 | cameraGroup.setCameraInstances(instancesGenerated); 25 | cameraGroup.getCameraPattern().setNumberOfInstances(instancesGenerated.size()); 26 | return cameraGroup; 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/domain/usecases/base/SingleUseCase.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.domain.usecases.base; 2 | 3 | import androidx.annotation.CallSuper; 4 | 5 | import io.reactivex.rxjava3.core.Single; 6 | import io.reactivex.rxjava3.core.SingleObserver; 7 | import io.reactivex.rxjava3.observers.DisposableSingleObserver; 8 | import pl.huczeq.rtspplayer.AppExecutors; 9 | 10 | public abstract class SingleUseCase extends DisposableUseCase { 11 | 12 | public SingleUseCase(AppExecutors executors) { 13 | super(executors); 14 | } 15 | 16 | protected abstract Single buildObservable(Params params); 17 | 18 | private Single buildSingle(Params params) { 19 | return buildObservable(params) 20 | .subscribeOn(executor().scheduler()) 21 | .observeOn(postExecutor().scheduler()); 22 | } 23 | 24 | @CallSuper 25 | public Single execute(Params params, SingleObserver observer) { 26 | Single observable = buildSingle(params); 27 | if(observer != null) 28 | observable.subscribe(observer); 29 | return observable; 30 | } 31 | 32 | @CallSuper 33 | public Single execute(Params params, DisposableSingleObserver observer) { 34 | Single observable = buildSingle(params); 35 | if(observer != null) 36 | disposables.add(observable.subscribeWith(observer)); 37 | return observable; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/domain/cameragenerator/expression/ProcessedVariable.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.domain.cameragenerator.expression; 2 | 3 | import org.jetbrains.annotations.TestOnly; 4 | 5 | import java.util.List; 6 | 7 | import pl.huczeq.rtspplayer.domain.cameragenerator.exceptions.ParsingException; 8 | 9 | public class ProcessedVariable { 10 | 11 | private VariableModel model; 12 | private List values; 13 | 14 | 15 | public ProcessedVariable(String name, String value) { 16 | this(new VariableModel(name, value)); 17 | } 18 | 19 | public ProcessedVariable(VariableModel model) throws ParsingException{ 20 | this.model = model; 21 | try { 22 | parse(); 23 | }catch (ParsingException e) { 24 | e.printStackTrace(); 25 | } 26 | } 27 | 28 | public VariableModel getModel() { 29 | return model; 30 | } 31 | 32 | public List getValues() { 33 | return values; 34 | } 35 | 36 | public String getName() { 37 | return this.model.getName(); 38 | } 39 | 40 | public void setName(String name) { 41 | this.model.setName(name); 42 | } 43 | 44 | public String getValue() { 45 | return this.model.getValue(); 46 | } 47 | 48 | public void setValue(String value) { 49 | this.model.setValue(value); 50 | } 51 | 52 | @TestOnly 53 | public void parse() { 54 | this.values = NumberParser.parse(this.model.value); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_start.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 19 | 20 | 33 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/data/repositories/base/CameraRepository.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.data.repositories.base; 2 | 3 | import androidx.lifecycle.LiveData; 4 | 5 | import java.util.List; 6 | 7 | import io.reactivex.rxjava3.subjects.PublishSubject; 8 | import pl.huczeq.rtspplayer.data.model.Camera; 9 | import pl.huczeq.rtspplayer.data.model.CameraGroup; 10 | import pl.huczeq.rtspplayer.data.model.CameraInstance; 11 | import pl.huczeq.rtspplayer.data.model.CameraPattern; 12 | 13 | public interface CameraRepository { 14 | 15 | LiveData> fetchAllCameras(); 16 | List getAllCameras(); 17 | LiveData getCameraById(long id); 18 | CameraInstance getCameraInstanceByIdSync(Long cameraInstanceId); 19 | Camera getCameraByIdSync(long id); 20 | LiveData getCameraPatternById(long id); 21 | List getAllCameraPatternsSync(); 22 | void insertCameraGroup(CameraGroup cameraGroup); 23 | void insertCameraGroups(List cameraGroups); 24 | void updateCameraGroup(CameraGroup cameraGroup); 25 | void deleteCameraGroup(CameraPattern cameraPattern); 26 | LiveData getNumberOfCameras(); 27 | void updateCameraInstanceSync(CameraInstance cameraInstance); 28 | PublishSubject getCameraInstancesInvalidatedSubject(); 29 | List getAllCameraGroups(); 30 | void clearAndInsertCameraGroups(List cameraGroups); 31 | void insertCameraGroupsSync(List cameraGroups); 32 | } 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RTSP-Player 2 | 3 | A simple player of video streams, mainly intended for viewing from IP cameras. 4 | 5 | 6 | Get it on Google Play 7 | 8 | 9 | ## Description 10 | 11 | It makes it easy to add camera previews using the built-in list of templates for popular IP camera models. It also allows you to enter only the url, for easy editing, automatically generates thumbnails when previewing the video and more... 12 | 13 | ## Features 14 | 15 | - Url templates 16 | - Advanced settings for screen rotation in player 17 | - Backup/import streams and settings 18 | - PIP mode 19 | - Camera groups 20 | - Material Design 3 21 | - Selection of the starting camera - preview of the camera when the application starts 22 | - Selection of connection protocol (TCP/UDP) - global option 23 | 24 | ## TODO 25 | 26 | - Selection of connection protocol (TCP/UDP) per camera/camera group 27 | - Android TV Support 28 | - View multiple streams simultaneously 29 | - Widgets/Shortcuts 30 | 31 | ## Donation 32 | 33 | If you like the project and would like to support me, you can donate in the following way: 34 | - [PayPal](https://www.paypal.com/donate/?hosted_button_id=U889NXKZRM6G8) 35 | - [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/O4O3JXPCI) 36 | 37 | ## License 38 | 39 | RTSP-Player is licensed under [GPL-3.0](LICENSE) 40 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/ui/settings/info/AboutAppActivity.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.ui.settings.info; 2 | 3 | import android.os.Bundle; 4 | import android.view.View; 5 | import android.widget.TextView; 6 | 7 | import androidx.appcompat.app.AppCompatActivity; 8 | 9 | import javax.inject.Inject; 10 | 11 | import dagger.hilt.android.AndroidEntryPoint; 12 | import pl.huczeq.rtspplayer.AppConfiguration; 13 | import pl.huczeq.rtspplayer.AppNavigator; 14 | import pl.huczeq.rtspplayer.BuildConfig; 15 | import pl.huczeq.rtspplayer.R; 16 | 17 | @AndroidEntryPoint 18 | public class AboutAppActivity extends AppCompatActivity { 19 | 20 | @Inject 21 | public AppNavigator navigator; 22 | 23 | private TextView tvVersion; 24 | private TextView tvSiteUrl; 25 | private TextView tvShowLicense; 26 | 27 | protected void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | setContentView(R.layout.activity_app_info); 30 | 31 | tvVersion = findViewById(R.id.tv_version); 32 | tvSiteUrl = findViewById(R.id.tv_siteUrl); 33 | tvShowLicense = findViewById(R.id.tv_showLicense); 34 | 35 | tvVersion.setText(AppConfiguration.getFullAppVersion()); 36 | tvSiteUrl.setText(BuildConfig.SITE_URL); 37 | tvShowLicense.setOnClickListener(new View.OnClickListener() { 38 | @Override 39 | public void onClick(View view) { 40 | navigator.startLicenseViewerActivity(); 41 | } 42 | }); 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/util/states/ResultState.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.util.states; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import javax.annotation.Nullable; 6 | 7 | import lombok.Getter; 8 | 9 | @Getter 10 | public class ResultState extends CompletableState { 11 | 12 | @Nullable 13 | private T result; 14 | 15 | public ResultState() { 16 | super(ProcessingStateType.IDLE); 17 | } 18 | 19 | public ResultState(@ProcessingStateType int type) { 20 | super(type); 21 | } 22 | 23 | public ResultState(@NotNull Throwable exception) { 24 | super(exception); 25 | } 26 | 27 | public ResultState(@NotNull T result) { 28 | super(ProcessingStateType.DONE); 29 | this.result = result; 30 | } 31 | 32 | @Nullable 33 | public T getResult() { 34 | return result; 35 | } 36 | 37 | public static class Builder { 38 | 39 | public static ResultState idleState() { 40 | return new ResultState(); 41 | } 42 | 43 | public static ResultState processingState() { 44 | return new ResultState(ProcessingStateType.PROCESSING); 45 | } 46 | 47 | public static ResultState successfully(@NotNull T result) { 48 | return new ResultState(result); 49 | } 50 | 51 | public static ResultState failed(@NotNull Throwable exception) { 52 | return new ResultState(exception); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/domain/usecases/ExportBackupUseCase.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.domain.usecases; 2 | 3 | import java.io.OutputStream; 4 | 5 | import javax.inject.Inject; 6 | 7 | import io.reactivex.rxjava3.core.Completable; 8 | import pl.huczeq.rtspplayer.AppExecutors; 9 | import pl.huczeq.rtspplayer.domain.backup.ExportBackupTask; 10 | import pl.huczeq.rtspplayer.domain.usecases.base.CompletableUseCase; 11 | 12 | public class ExportBackupUseCase extends CompletableUseCase { 13 | 14 | private ExportBackupTask exportBackupTask; 15 | 16 | @Inject 17 | public ExportBackupUseCase(AppExecutors executors, ExportBackupTask exportBackupTask) { 18 | super(executors); 19 | this.exportBackupTask = exportBackupTask; 20 | } 21 | 22 | @Override 23 | protected Completable buildObservable(Params params) { 24 | this.exportBackupTask.init(params.outputStream, params.exportCameras, params.exportSettings); 25 | return Completable.fromRunnable(this.exportBackupTask); 26 | } 27 | 28 | public static class Params { 29 | public final OutputStream outputStream; 30 | public final boolean exportCameras; 31 | public final boolean exportSettings; 32 | 33 | public Params(OutputStream outputStream, boolean exportCameras, boolean exportSettings) { 34 | this.outputStream = outputStream; 35 | this.exportCameras = exportCameras; 36 | this.exportSettings = exportSettings; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/ui/player/view/renderer/Shader.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.ui.player.view.renderer; 2 | 3 | import android.opengl.GLES20; 4 | import android.util.Log; 5 | 6 | public class Shader { 7 | 8 | private static final String TAG = "Shader"; 9 | 10 | private static int loadShader(int type, String source) { 11 | int shader = GLES20.glCreateShader(type); 12 | GLES20.glShaderSource(shader, source); 13 | GLES20.glCompileShader(shader); 14 | final int[] compileStatus = new int[1]; 15 | GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compileStatus, 0); 16 | if(compileStatus[0] == 0) { 17 | GLES20.glDeleteShader(shader); 18 | String errorMessage = "Error: " + GLES20.glGetShaderInfoLog(shader); 19 | throw new RuntimeException(errorMessage); 20 | } 21 | return shader; 22 | } 23 | 24 | protected int program; 25 | private int vertexShader; 26 | private int fragmentShader; 27 | 28 | public Shader(String vertexShaderSource, String fragmentShaderSource) { 29 | this.program = GLES20.glCreateProgram(); 30 | this.vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderSource); 31 | this.fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderSource); 32 | 33 | GLES20.glAttachShader(program, vertexShader); 34 | GLES20.glAttachShader(program, fragmentShader); 35 | GLES20.glLinkProgram(program); 36 | GLES20.glUseProgram(program); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/ui/views/DropDownListView.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.ui.views; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.annotation.Nullable; 8 | 9 | import com.google.android.material.textfield.MaterialAutoCompleteTextView; 10 | 11 | import pl.huczeq.rtspplayer.data.model.urltemplates.Model; 12 | import pl.huczeq.rtspplayer.data.model.urltemplates.Producer; 13 | 14 | public class DropDownListView extends MaterialAutoCompleteTextView { 15 | 16 | public DropDownListView(@NonNull Context context) { 17 | super(context); 18 | init(); 19 | } 20 | 21 | public DropDownListView(@NonNull Context context, @Nullable AttributeSet attributeSet) { 22 | super(context, attributeSet); 23 | init(); 24 | } 25 | 26 | public DropDownListView(@NonNull Context context, @Nullable AttributeSet attributeSet, int defStyleAttr) { 27 | super(context, attributeSet, defStyleAttr); 28 | init(); 29 | } 30 | 31 | @Override 32 | protected CharSequence convertSelectionToString(Object selectedItem) { 33 | if(selectedItem == null) 34 | return ""; 35 | if(selectedItem instanceof Producer) { 36 | return ((Producer) selectedItem).getName(); 37 | }else if(selectedItem instanceof Model) { 38 | return ((Model) selectedItem).getName(); 39 | } 40 | return super.convertSelectionToString(selectedItem); 41 | } 42 | 43 | protected void init() { } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/test/java/pl/huczeq/rtspplayer/FakeRtspPlayerApp.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer; 2 | 3 | import android.content.Context; 4 | 5 | import javax.inject.Inject; 6 | 7 | import io.reactivex.rxjava3.plugins.RxJavaPlugins; 8 | import pl.huczeq.rtspplayer.data.sources.cache.ThumbnailCache; 9 | import pl.huczeq.rtspplayer.domain.CameraThumbnailsIntegrityHelper; 10 | import pl.huczeq.rtspplayer.ui.start.DataMigrationViewModel; 11 | 12 | public class FakeRtspPlayerApp extends RtspPlayerApp { 13 | 14 | public static RtspPlayerApp get(Context context) { 15 | return (FakeRtspPlayerApp) context.getApplicationContext(); 16 | } 17 | 18 | @Inject 19 | public Settings settings; 20 | @Inject 21 | public ThumbnailCache thumbnailCache; 22 | @Inject 23 | public DataMigrationViewModel dataMigrationManager; 24 | @Inject 25 | public AppThemeHelper appThemeHelper; 26 | @Inject 27 | public CameraThumbnailsIntegrityHelper cameraThumbnailsIntegrityHelper; 28 | 29 | @Override 30 | public void onCreate() { 31 | super.onCreate(); 32 | RxJavaPlugins.setErrorHandler(Throwable::printStackTrace); 33 | this.appThemeHelper.applyDarkLightTheme(); 34 | this.dataMigrationManager.startProcessing(); 35 | } 36 | 37 | @Override 38 | public void onLowMemory() { 39 | super.onLowMemory(); 40 | cleanMemory(); 41 | } 42 | 43 | @Override 44 | public void onTrimMemory(int level) { 45 | super.onTrimMemory(level); 46 | cleanMemory(); 47 | } 48 | 49 | public void cleanMemory() { 50 | this.thumbnailCache.cleanUp(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/data/sources/local/database/CameraInstanceDao.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.data.sources.local.database; 2 | 3 | import androidx.lifecycle.LiveData; 4 | import androidx.room.Dao; 5 | import androidx.room.Insert; 6 | import androidx.room.Query; 7 | import androidx.room.Update; 8 | 9 | import com.google.common.base.Optional; 10 | 11 | import java.util.List; 12 | 13 | import pl.huczeq.rtspplayer.data.model.CameraInstance; 14 | 15 | @Dao 16 | public abstract class CameraInstanceDao { 17 | 18 | @Query("SELECT * FROM camerainstance WHERE id = :id") 19 | public abstract CameraInstance getCameraInstanceByIdSync(long id); 20 | 21 | @Query("SELECT * FROM camerainstance WHERE id = :id") 22 | public abstract LiveData> getCameraInstanceById(long id); 23 | 24 | @Query("SELECT * FROM camerainstance WHERE patternId == :patternId") 25 | public abstract List getCameraInstancesByPatternIdSync(long patternId); 26 | 27 | @Insert 28 | public abstract long insertCameraInstance(CameraInstance cameraInstance); 29 | 30 | @Insert 31 | public abstract void insertCameraInstances(List cameraInstances); 32 | 33 | @Update 34 | public abstract void updateCameraInstance(CameraInstance cameraInstance); 35 | 36 | @Query("DELETE FROM camerainstance WHERE id = :id") 37 | public abstract void deleteCameraInstancesWithId(long id); 38 | 39 | @Query("DELETE FROM camerainstance WHERE patternId = :patternId") 40 | public abstract void deleteCameraInstancesWithPatternId(long patternId); 41 | 42 | @Query("DELETE FROM camerainstance") 43 | public abstract void deleteAll(); 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/util/states/CompletableState.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.util.states; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import javax.annotation.Nullable; 6 | 7 | import lombok.Getter; 8 | 9 | @Getter 10 | public class CompletableState extends BaseState { 11 | 12 | @Nullable 13 | protected Throwable exception; 14 | 15 | public CompletableState() { 16 | super(ProcessingStateType.IDLE); 17 | } 18 | 19 | public CompletableState(@ProcessingStateType int type) { 20 | super(type); 21 | } 22 | 23 | public CompletableState(@NotNull Throwable exception) { 24 | super(ProcessingStateType.DONE); 25 | this.exception = exception; 26 | } 27 | 28 | public boolean isCompletedSuccessfully() { 29 | return exception == null && type == ProcessingStateType.DONE; 30 | } 31 | 32 | public boolean isProcessing() { 33 | return this.type == ProcessingStateType.PROCESSING; 34 | } 35 | 36 | public boolean isCompleted() { 37 | return this.type == ProcessingStateType.DONE; 38 | } 39 | 40 | public static class Builder { 41 | 42 | public static CompletableState IDLE() { 43 | return new CompletableState(); 44 | } 45 | 46 | public static CompletableState PROCESSING() { 47 | return new CompletableState(ProcessingStateType.PROCESSING); 48 | } 49 | 50 | public static CompletableState SUCCESSFULLY() { 51 | return new CompletableState(ProcessingStateType.DONE); 52 | } 53 | 54 | public static CompletableState FAILED(@NotNull Throwable exception) { 55 | return new CompletableState(exception); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/domain/usecases/UpdateCameraGroupUseCase.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.domain.usecases; 2 | 3 | import javax.inject.Inject; 4 | 5 | import io.reactivex.rxjava3.core.Completable; 6 | import lombok.Builder; 7 | import lombok.Getter; 8 | import pl.huczeq.rtspplayer.AppExecutors; 9 | import pl.huczeq.rtspplayer.data.model.CameraGroup; 10 | import pl.huczeq.rtspplayer.data.repositories.base.CameraRepository; 11 | import pl.huczeq.rtspplayer.domain.usecases.base.CompletableUseCase; 12 | import pl.huczeq.rtspplayer.domain.cameragenerator.CameraGroupGenerator; 13 | import pl.huczeq.rtspplayer.domain.model.CameraGroupModel; 14 | 15 | public class UpdateCameraGroupUseCase extends CompletableUseCase { 16 | 17 | @Inject 18 | public CameraRepository cameraRepository; 19 | 20 | @Inject 21 | public UpdateCameraGroupUseCase(AppExecutors executors) { 22 | super(executors); 23 | } 24 | 25 | @Override 26 | protected Completable buildObservable(UpdateCameraGroupUseCase.Params params) { 27 | return Completable.fromRunnable(new Runnable() { 28 | @Override 29 | public void run() { 30 | CameraGroupGenerator cameraGroupGenerator = new CameraGroupGenerator(); 31 | CameraGroup cameraGroup = cameraGroupGenerator.generate(params.model); 32 | cameraGroup.getCameraPattern().setId(params.cameraPatternId); 33 | cameraRepository.updateCameraGroup(cameraGroup); 34 | } 35 | }); 36 | } 37 | 38 | @Builder 39 | @Getter 40 | public static class Params { 41 | private final long cameraPatternId; 42 | private final CameraGroupModel model; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/domain/usecases/DataMigrationUseCase.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.domain.usecases; 2 | 3 | import android.content.Context; 4 | 5 | import java.io.File; 6 | 7 | import javax.inject.Inject; 8 | 9 | import dagger.hilt.android.qualifiers.ApplicationContext; 10 | import io.reactivex.rxjava3.core.Completable; 11 | import pl.huczeq.rtspplayer.AppExecutors; 12 | import pl.huczeq.rtspplayer.data.JsonToDatabaseMigration; 13 | import pl.huczeq.rtspplayer.data.repositories.base.CameraRepository; 14 | import pl.huczeq.rtspplayer.data.repositories.base.UrlTemplateRepository; 15 | import pl.huczeq.rtspplayer.data.sources.local.JsonDataFileLoader; 16 | import pl.huczeq.rtspplayer.domain.usecases.base.CompletableUseCase; 17 | 18 | public class DataMigrationUseCase extends CompletableUseCase { 19 | 20 | private Context context; 21 | private CameraRepository cameraRepository; 22 | private UrlTemplateRepository urlTemplateRepository; 23 | 24 | @Inject 25 | public DataMigrationUseCase(@ApplicationContext Context context, AppExecutors executors, CameraRepository cameraRepository, UrlTemplateRepository urlTemplateRepository) { 26 | super(executors); 27 | this.context = context; 28 | this.cameraRepository = cameraRepository; 29 | this.urlTemplateRepository = urlTemplateRepository; 30 | } 31 | 32 | @Override 33 | protected Completable buildObservable(Object o) { 34 | return Completable.fromRunnable(new Runnable() { 35 | @Override 36 | public void run() { 37 | JsonToDatabaseMigration.tryRestoreData(new File(context.getFilesDir(), JsonDataFileLoader.FILE_NAME), cameraRepository, urlTemplateRepository); 38 | } 39 | }); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/data/model/urltemplates/Producer.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.data.model.urltemplates; 2 | 3 | import android.util.Log; 4 | 5 | import org.json.JSONArray; 6 | import org.json.JSONException; 7 | import org.json.JSONObject; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | public class Producer { 13 | 14 | private final static String TAG = "Producer"; 15 | private String name; 16 | List modelList; 17 | 18 | public Producer(String name) { 19 | this.name = name; 20 | this.modelList = new ArrayList<>(); 21 | } 22 | 23 | public Producer(JSONObject json) { 24 | this(json.optString("name", "-")); 25 | try { 26 | JSONArray array = json.getJSONArray("models"); 27 | for(int i = 0; i < array.length(); i++) { 28 | Model model = new Model(array.getJSONObject(i)); 29 | if(model.isCorrect()) this.modelList.add(model); 30 | } 31 | } catch (JSONException e) { 32 | e.printStackTrace(); 33 | } 34 | } 35 | 36 | public boolean isCorrect() { 37 | return this.modelList.size()>0; 38 | } 39 | 40 | public String getName() { 41 | return name; 42 | } 43 | 44 | public void setName(String name) { 45 | this.name = name; 46 | } 47 | 48 | public List getModelList() { 49 | return modelList; 50 | } 51 | 52 | public void setModelList(List modelList) { 53 | this.modelList = modelList; 54 | } 55 | 56 | public int getModelIndex(String name) { 57 | for(int i = 0; i extends DisposableUseCase { 17 | 18 | public CompletableUseCase(AppExecutors executors) { 19 | super(executors); 20 | } 21 | 22 | protected abstract Completable buildObservable(Params params); 23 | 24 | private Completable buildCompletable(Params params) { 25 | return buildObservable(params) 26 | .subscribeOn(executor().scheduler()) 27 | .observeOn(postExecutor().scheduler()); 28 | } 29 | 30 | @CallSuper 31 | public Completable execute(Params params, CompletableObserver observer) { 32 | Completable observable = buildCompletable(params); 33 | if(observer != null) 34 | observable.subscribe(observer); 35 | return observable; 36 | } 37 | 38 | @CallSuper 39 | public Completable execute(Params params, DisposableCompletableObserver observer) { 40 | Completable observable = buildCompletable(params); 41 | if(observer != null) 42 | disposables.add(observable.subscribeWith(observer)); 43 | return observable; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/test/java/pl/huczeq/rtspplayer/domain/usecases/BaseUseCaseTestTemplate.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.domain.usecases; 2 | 3 | import org.junit.Before; 4 | 5 | import java.util.concurrent.Executors; 6 | 7 | import javax.inject.Inject; 8 | import javax.inject.Singleton; 9 | 10 | import dagger.Module; 11 | import dagger.Provides; 12 | import dagger.hilt.components.SingletonComponent; 13 | import dagger.hilt.testing.TestInstallIn; 14 | import pl.huczeq.rtspplayer.AppExecutors; 15 | import pl.huczeq.rtspplayer.BaseAndroidTest; 16 | import pl.huczeq.rtspplayer.di.ExecutorsModule; 17 | 18 | public abstract class BaseUseCaseTestTemplate extends BaseAndroidTest { 19 | 20 | @Inject 21 | public AppExecutors appExecutors; 22 | 23 | protected TestAppExecutors testAppExecutor; 24 | 25 | @Before 26 | public void prepareBaseUseCaseTestTemplate() { 27 | this.testAppExecutor = ((TestAppExecutors) this.appExecutors); 28 | this.testAppExecutor.replaceMainThread = true; 29 | } 30 | 31 | public static class TestAppExecutors extends AppExecutors { 32 | 33 | private boolean replaceMainThread = false; 34 | 35 | private AppExecutor fakeMainThreadExecutor; 36 | 37 | public TestAppExecutors() { 38 | this.fakeMainThreadExecutor = new AppExecutor(Executors.newSingleThreadExecutor()); 39 | } 40 | 41 | @Override 42 | public AppExecutor mainThread() { 43 | if(replaceMainThread) 44 | return fakeMainThreadExecutor; 45 | return super.mainThread(); 46 | } 47 | } 48 | 49 | @Module 50 | @TestInstallIn(components = SingletonComponent.class, replaces = ExecutorsModule.class) 51 | public static class TestExecutorsModule { 52 | 53 | @Provides 54 | @Singleton 55 | public AppExecutors bindAppExecutors() { 56 | return new TestAppExecutors(); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/domain/usecases/ImportBackupUseCase.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.domain.usecases; 2 | 3 | import java.io.InputStream; 4 | import java.io.OutputStream; 5 | 6 | import javax.inject.Inject; 7 | 8 | import io.reactivex.rxjava3.core.Completable; 9 | import io.reactivex.rxjava3.core.Single; 10 | import pl.huczeq.rtspplayer.AppExecutors; 11 | import pl.huczeq.rtspplayer.domain.backup.ImportBackupTask; 12 | import pl.huczeq.rtspplayer.domain.backup.LoadBackupTask; 13 | import pl.huczeq.rtspplayer.domain.backup.LoadedBackup; 14 | import pl.huczeq.rtspplayer.domain.usecases.base.CompletableUseCase; 15 | import pl.huczeq.rtspplayer.domain.usecases.base.SingleUseCase; 16 | 17 | public class ImportBackupUseCase extends CompletableUseCase { 18 | 19 | private ImportBackupTask importBackupTask; 20 | 21 | @Inject 22 | public ImportBackupUseCase(AppExecutors executors, ImportBackupTask importBackupTask) { 23 | super(executors); 24 | this.importBackupTask = importBackupTask; 25 | } 26 | 27 | @Override 28 | protected Completable buildObservable(ImportBackupUseCase.Params params) { 29 | this.importBackupTask.init(params.loadedBackup, params.importCameras, params.clearImport, params.importSettings); 30 | return Completable.fromRunnable(this.importBackupTask); 31 | } 32 | 33 | public static class Params { 34 | public final LoadedBackup loadedBackup; 35 | public final boolean importCameras; 36 | public final boolean importSettings; 37 | public final boolean clearImport; 38 | 39 | public Params(LoadedBackup loadedBackup, boolean importCameras, boolean importSettings, boolean clearImport) { 40 | this.loadedBackup = loadedBackup; 41 | this.importCameras = importCameras; 42 | this.importSettings = importSettings; 43 | this.clearImport = clearImport; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #25A761 4 | #0000 5 | #EFEFEF 6 | #3666 7 | 8 | true 9 | true 10 | 11 | 12 | #006D3A 13 | #FFFFFF 14 | #82FAAB 15 | #00210E 16 | #4F6353 17 | #FFFFFF 18 | #D2E8D4 19 | #0D1F13 20 | #3A646F 21 | #FFFFFF 22 | #BEEAF6 23 | #001F26 24 | #BA1A1A 25 | #FFDAD6 26 | #FFFFFF 27 | #410002 28 | #FBFDF8 29 | #191C19 30 | #FBFDF8 31 | #191C19 32 | #DDE5DB 33 | #414942 34 | #717971 35 | #F0F1EC 36 | #2E312E 37 | #65DD91 38 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/domain/cameragenerator/expression/Variations.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.domain.cameragenerator.expression; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | import pl.huczeq.rtspplayer.domain.cameragenerator.exceptions.LimitReachedException; 9 | 10 | public class Variations { 11 | 12 | public static List> generate(List variables, int limit) throws LimitReachedException { 13 | List> outVariations = new ArrayList<>(); 14 | generate(outVariations, variables, new HashMap<>(), 0, limit); 15 | return outVariations; 16 | } 17 | 18 | private static void generate(List> outVariations, List variables, Map currentPatternData, int variableIndex, int limit) throws LimitReachedException { 19 | if(Thread.currentThread().isInterrupted()) 20 | throw new RuntimeException("Thread is interrupted"); 21 | if(variableIndex >= variables.size()) { 22 | if(Thread.currentThread().isInterrupted()) 23 | throw new RuntimeException("Thread is interrupted"); 24 | outVariations.add(new HashMap<>(currentPatternData)); 25 | if(limit > 0 && outVariations.size() >= limit) { 26 | throw new LimitReachedException(); 27 | } 28 | return; 29 | } 30 | String tempString = null; 31 | ProcessedVariable variable = variables.get(variableIndex); 32 | variableIndex++; 33 | for(String value : variable.getValues()) { 34 | if(Thread.currentThread().isInterrupted()) 35 | throw new RuntimeException("Thread is interrupted"); 36 | currentPatternData.put(variable.getName(), value); 37 | generate(outVariations, variables, currentPatternData, variableIndex, limit); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #25A761 4 | #FFF 5 | #333 6 | #3DDD 7 | 8 | false 9 | false 10 | 11 | 12 | 13 | #65DD91 14 | #00391C 15 | #00522B 16 | #82FAAB 17 | #B6CCB8 18 | #223527 19 | #384B3C 20 | #D2E8D4 21 | #A2CEDA 22 | #02363F 23 | #214C57 24 | #BEEAF6 25 | #FFB4AB 26 | #93000A 27 | #690005 28 | #FFDAD6 29 | #191C19 30 | #E1E3DE 31 | #191C19 32 | #E1E3DE 33 | #414942 34 | #C1C9BF 35 | #8B938A 36 | #191C19 37 | #E1E3DE 38 | #006D3A 39 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/data/model/CameraInstance.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.data.model; 2 | 3 | import androidx.room.Entity; 4 | import androidx.room.Ignore; 5 | import androidx.room.PrimaryKey; 6 | 7 | import java.util.Map; 8 | 9 | import lombok.ToString; 10 | 11 | @Entity 12 | @ToString 13 | public class CameraInstance { 14 | 15 | @PrimaryKey(autoGenerate = true) 16 | private long id; 17 | 18 | private String name; 19 | private String url; 20 | private String previewImg; 21 | private long patternId; 22 | 23 | private Map variablesData; 24 | 25 | @Ignore 26 | public CameraInstance() { } 27 | 28 | public CameraInstance(String name, String url, String previewImg, long patternId) { 29 | this.name = name; 30 | this.url = url; 31 | this.previewImg = previewImg; 32 | this.patternId = patternId; 33 | } 34 | 35 | public long getId() { 36 | return id; 37 | } 38 | 39 | public void setId(long id) { 40 | this.id = id; 41 | } 42 | 43 | public String getName() { 44 | return name; 45 | } 46 | 47 | public void setName(String name) { 48 | this.name = name; 49 | } 50 | 51 | public String getUrl() { 52 | return url; 53 | } 54 | 55 | public void setUrl(String url) { 56 | this.url = url; 57 | } 58 | 59 | public String getPreviewImg() { 60 | return previewImg; 61 | } 62 | 63 | public void setPreviewImg(String previewImg) { 64 | this.previewImg = previewImg; 65 | } 66 | 67 | public long getPatternId() { 68 | return patternId; 69 | } 70 | 71 | public void setPatternId(long patternId) { 72 | this.patternId = patternId; 73 | } 74 | 75 | public Map getVariablesData() { 76 | return variablesData; 77 | } 78 | 79 | public void setVariablesData(Map variablesData) { 80 | this.variablesData = variablesData; 81 | } 82 | } -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/domain/cameragenerator/CameraInstancesGenerator.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.domain.cameragenerator; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import pl.huczeq.rtspplayer.data.model.CameraInstance; 8 | import pl.huczeq.rtspplayer.domain.cameragenerator.expression.ExpressionHelper; 9 | import pl.huczeq.rtspplayer.domain.model.CameraPatternWithVariables; 10 | import pl.huczeq.rtspplayer.domain.cameragenerator.expression.Expression; 11 | 12 | public class CameraInstancesGenerator { 13 | 14 | private CameraPatternWithVariables cameraPattern; 15 | 16 | public CameraInstancesGenerator(CameraPatternWithVariables cameraPattern) { 17 | this.cameraPattern = cameraPattern; 18 | } 19 | 20 | public List build() { 21 | List cameraInstances = new ArrayList<>(); 22 | 23 | List specialNames = List.of("i"); 24 | 25 | Expression expression = new Expression(cameraPattern.getUrl(), cameraPattern.getVariables(), specialNames); 26 | List> variations = expression.generateVariations(); 27 | int index = 1; 28 | for(Map variation : variations) { 29 | if(Thread.currentThread().isInterrupted()) 30 | throw new RuntimeException("Thread interrupted"); 31 | variation.put("i", String.valueOf(index)); 32 | 33 | CameraInstance cameraInstance = new CameraInstance(); 34 | cameraInstance.setName(ExpressionHelper.loadDataToExpression(cameraPattern.getName(), variation, specialNames)); 35 | cameraInstance.setUrl(ExpressionHelper.loadDataToExpression(expression.getPartiallyExpression(), variation)); 36 | cameraInstance.setPatternId(cameraPattern.getId()); 37 | cameraInstance.setVariablesData(variation); 38 | cameraInstances.add(cameraInstance); 39 | index++; 40 | } 41 | return cameraInstances; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/RtspPlayerApp.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | import javax.inject.Inject; 7 | 8 | import dagger.hilt.android.HiltAndroidApp; 9 | import io.reactivex.rxjava3.plugins.RxJavaPlugins; 10 | import pl.huczeq.rtspplayer.data.repositories.base.CameraRepository; 11 | import pl.huczeq.rtspplayer.data.sources.cache.ThumbnailCache; 12 | import pl.huczeq.rtspplayer.domain.CameraThumbnailsIntegrityHelper; 13 | import pl.huczeq.rtspplayer.domain.StartingCameraIntegrityHelper; 14 | import pl.huczeq.rtspplayer.ui.start.DataMigrationViewModel; 15 | 16 | @HiltAndroidApp 17 | public class RtspPlayerApp extends Application { 18 | 19 | public static RtspPlayerApp get(Context context) { 20 | return (RtspPlayerApp) context.getApplicationContext(); 21 | } 22 | 23 | @Inject 24 | public Settings settings; 25 | @Inject 26 | public ThumbnailCache thumbnailCache; 27 | @Inject 28 | public DataMigrationViewModel dataMigrationManager; 29 | @Inject 30 | public AppThemeHelper appThemeHelper; 31 | @Inject 32 | public CameraThumbnailsIntegrityHelper cameraThumbnailsIntegrityHelper; 33 | 34 | @Inject 35 | public StartingCameraIntegrityHelper startingCameraIntegrityHelper; 36 | 37 | @Override 38 | public void onCreate() { 39 | super.onCreate(); 40 | RxJavaPlugins.setErrorHandler(Throwable::printStackTrace); 41 | this.appThemeHelper.applyDarkLightTheme(); 42 | this.dataMigrationManager.startProcessing(); 43 | } 44 | 45 | @Override 46 | public void onTerminate() { 47 | super.onTerminate(); 48 | } 49 | 50 | @Override 51 | public void onLowMemory() { 52 | super.onLowMemory(); 53 | cleanMemory(); 54 | } 55 | 56 | @Override 57 | public void onTrimMemory(int level) { 58 | super.onTrimMemory(level); 59 | cleanMemory(); 60 | } 61 | 62 | public void cleanMemory() { 63 | this.thumbnailCache.cleanUp(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/ui/views/materialpreferences/SwitchPreferenceMaterial.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.ui.views.materialpreferences; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.util.AttributeSet; 6 | import android.view.View; 7 | 8 | import androidx.annotation.NonNull; 9 | import androidx.annotation.Nullable; 10 | import androidx.appcompat.widget.SwitchCompat; 11 | import androidx.preference.PreferenceViewHolder; 12 | import androidx.preference.SwitchPreference; 13 | 14 | import pl.huczeq.rtspplayer.R; 15 | 16 | public class SwitchPreferenceMaterial extends SwitchPreference { 17 | 18 | public SwitchPreferenceMaterial(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { 19 | super(context, attrs, defStyleAttr, defStyleRes); 20 | init(); 21 | } 22 | 23 | public SwitchPreferenceMaterial(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 24 | super(context, attrs, defStyleAttr); 25 | init(); 26 | } 27 | 28 | public SwitchPreferenceMaterial(@NonNull Context context, @Nullable AttributeSet attrs) { 29 | super(context, attrs); 30 | init(); 31 | } 32 | 33 | public SwitchPreferenceMaterial(@NonNull Context context) { 34 | super(context); 35 | init(); 36 | } 37 | 38 | private void init() { 39 | setWidgetLayoutResource(R.layout.pref_m3_switch); 40 | } 41 | 42 | @SuppressLint("RestrictedApi") 43 | @Override 44 | protected void performClick(@NonNull View view) { 45 | super.performClick(view); 46 | syncSwitch(view); 47 | } 48 | 49 | @Override 50 | public void onBindViewHolder(@NonNull PreferenceViewHolder holder) { 51 | super.onBindViewHolder(holder); 52 | syncSwitch(holder.itemView); 53 | } 54 | 55 | private void syncSwitch(View view) { 56 | SwitchCompat switchView = view.findViewById(R.id.switchWidget); 57 | if(switchView == null) 58 | return; 59 | switchView.setChecked(mChecked); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/ui/start/StartActivity.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.ui.start; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | 6 | import androidx.core.splashscreen.SplashScreen; 7 | import androidx.lifecycle.Observer; 8 | import androidx.lifecycle.ViewModelProvider; 9 | 10 | import javax.inject.Inject; 11 | 12 | import dagger.hilt.android.AndroidEntryPoint; 13 | import pl.huczeq.rtspplayer.AppNavigator; 14 | import pl.huczeq.rtspplayer.R; 15 | import pl.huczeq.rtspplayer.Settings; 16 | import pl.huczeq.rtspplayer.data.model.Camera; 17 | import pl.huczeq.rtspplayer.ui.BaseActivity; 18 | 19 | @AndroidEntryPoint 20 | public class StartActivity extends BaseActivity { 21 | 22 | @Inject 23 | public AppNavigator navigator; 24 | 25 | public StartViewModel viewModel; 26 | 27 | @Inject 28 | public Settings settings; 29 | 30 | @Override 31 | protected void onCreate(Bundle savedInstanceState) { 32 | SplashScreen splashScreen = SplashScreen.installSplashScreen(this); 33 | super.onCreate(savedInstanceState); 34 | setContentView(R.layout.activity_start); 35 | 36 | this.viewModel = new ViewModelProvider(this).get(StartViewModel.class); 37 | 38 | splashScreen.setKeepOnScreenCondition(new SplashScreen.KeepOnScreenCondition() { 39 | @Override 40 | public boolean shouldKeepOnScreen() { 41 | return !Boolean.TRUE.equals(viewModel.getAppStarted().getValue()); 42 | } 43 | }); 44 | 45 | viewModel.getAppStarted().observe(this, new Observer() { 46 | @Override 47 | public void onChanged(Boolean ready) { 48 | if(ready != null && ready) 49 | startNextActivity(viewModel.getAppStartCamera()); 50 | } 51 | }); 52 | } 53 | 54 | private void startNextActivity(Camera camera) { 55 | if(camera != null) 56 | startActivities(new Intent[]{navigator.buildMainActivityIntent(), navigator.buildPlayerCameraActivityIntent(camera)}); 57 | else 58 | navigator.startMainActivity(); 59 | finish(); 60 | } 61 | } -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/domain/cameragenerator/expression/Expression.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.domain.cameragenerator.expression; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | public class Expression { 9 | 10 | private final String expression; 11 | private final ExpressionHelper helper; 12 | private final List partiallyExpression; 13 | private final Map rawVariables; 14 | 15 | private final List specialNames; 16 | 17 | /** 18 | * @param expression Input text that may contain variables 19 | * @param rawVariables Map of variables; key - name of the variable, value - raw text of the variable value 20 | * @param specialNames 21 | */ 22 | public Expression(String expression, Map rawVariables, List specialNames) { 23 | this.expression = expression; 24 | this.helper = new ExpressionHelper<>(); 25 | //Split an expression into a list to find variables faster 26 | //Instead of searching character by character, if the first character of the element is '{' - variable 27 | this.partiallyExpression = helper.splitExpression(this.expression, rawVariables, specialNames); 28 | this.rawVariables = rawVariables; 29 | this.specialNames = specialNames; 30 | } 31 | 32 | public Expression(String expression, Map rawVariables) { 33 | this(expression, rawVariables, List.of()); 34 | } 35 | 36 | 37 | public List> generateVariations() { 38 | List variables = new ArrayList<>(); 39 | Map variablesMap = new HashMap<>(); 40 | //Prepare data; partiallyExpression and rawVariables ->> variables and variablesMap 41 | helper.prepareVariableMaps(partiallyExpression, rawVariables, variables, variablesMap); 42 | 43 | //Generate variations 44 | List> variations = Variations.generate(variables, -1); 45 | return variations; 46 | } 47 | 48 | public List getPartiallyExpression() { 49 | return this.partiallyExpression; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/player/VideoLayout.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.player; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public class VideoLayout { 7 | 8 | public static boolean isEmptyOrNull(VideoLayout videoLayout) { 9 | if(videoLayout == null) 10 | return true; 11 | return videoLayout.visibleWidth * videoLayout.visibleHeight == 0; 12 | } 13 | 14 | private int width; 15 | private int height; 16 | private int visibleWidth; 17 | private int visibleHeight; 18 | private float widthToHeightRatio; 19 | private float heightToWidthRatio; 20 | 21 | public VideoLayout(int width, int height) { 22 | this(width, height, width, height); 23 | } 24 | 25 | public VideoLayout(int width, int height, int visibleWidth, int visibleHeight) { 26 | this.width = width; 27 | this.height = height; 28 | this.visibleWidth = visibleWidth; 29 | this.visibleHeight = visibleHeight; 30 | if(visibleWidth * visibleHeight != 0) { 31 | this.widthToHeightRatio = (float) visibleWidth / visibleHeight; 32 | this.heightToWidthRatio = (float) visibleHeight / visibleWidth; 33 | } 34 | } 35 | 36 | @Override 37 | public boolean equals(Object o) { 38 | if (this == o) return true; 39 | if (o == null || getClass() != o.getClass()) return false; 40 | VideoLayout that = (VideoLayout) o; 41 | return width == that.width && height == that.height && visibleWidth == that.visibleWidth && visibleHeight == that.visibleHeight; 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | return "VideoLayout{" + 47 | "width=" + width + 48 | ", height=" + height + 49 | ", visibleWidth=" + visibleWidth + 50 | ", visibleHeight=" + visibleHeight + 51 | ", aspectRatio=" + widthToHeightRatio + 52 | '}'; 53 | } 54 | 55 | public String toShortString() { 56 | return "VideoLayout{" + 57 | "visibleWidth=" + visibleWidth + 58 | ", visibleHeight=" + visibleHeight + 59 | ", aspectRatio=" + widthToHeightRatio + 60 | '}'; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/ui/MyBindingAdapter.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.ui; 2 | 3 | import androidx.databinding.BindingAdapter; 4 | 5 | import com.google.android.material.textfield.TextInputLayout; 6 | 7 | import pl.huczeq.rtspplayer.ui.views.ProgressFloatingActionButton; 8 | import pl.huczeq.rtspplayer.ui.views.ProgressMaterialButton; 9 | import pl.huczeq.rtspplayer.util.validation.Errors; 10 | 11 | public class MyBindingAdapter { 12 | 13 | @BindingAdapter(value = {"progressVisible"}) 14 | public static void setProgressVisible(ProgressFloatingActionButton button, boolean progressVisible) { 15 | button.setProgressVisible(progressVisible); 16 | } 17 | 18 | @BindingAdapter(value = {"progressVisible"}) 19 | public static void setProgressVisible(ProgressMaterialButton button, boolean progressVisible) { 20 | button.setProgressVisible(progressVisible); 21 | } 22 | 23 | @BindingAdapter(value = {"android:enabled"}) 24 | public static void setEnabled(ProgressFloatingActionButton view, boolean enabled) { 25 | view.setEnabled(enabled); 26 | } 27 | 28 | @BindingAdapter("errorMessage") 29 | public static void setErrorMessage(TextInputLayout view, String errorMessage) { 30 | view.setError(errorMessage); 31 | } 32 | 33 | @BindingAdapter("errorMessage") 34 | public static void setErrorMessage(TextInputLayout view, Integer erroCode) { 35 | if(erroCode == null) { 36 | view.setError(null); 37 | return; 38 | } 39 | switch (erroCode) { 40 | case Errors.IS_REQUIRED: 41 | view.setError("This field is required"); 42 | break; 43 | case Errors.EXPRESSION_NOT_CLOSED: 44 | view.setError("Nie zapomnij o zamknięciu wyrażenia"); 45 | break; 46 | case Errors.EXPRESSION_OPENSIGN_AFTER_OPEN: 47 | view.setError("Musisz najpierw zamknąc poprzednie wyrazenie"); 48 | break; 49 | case Errors.EXPRESSION_CLOSEDSIGN_WITHOUT_OPEN: 50 | view.setError("Musisz najpierw otworzyc wyrazenie"); 51 | break; 52 | default: 53 | view.setError("Incorrect value"); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/ui/start/DataMigrationViewModel.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.ui.start; 2 | 3 | import android.content.Context; 4 | 5 | import javax.inject.Inject; 6 | 7 | import dagger.hilt.android.qualifiers.ApplicationContext; 8 | import io.reactivex.rxjava3.annotations.NonNull; 9 | import io.reactivex.rxjava3.observers.DisposableCompletableObserver; 10 | import io.reactivex.rxjava3.subjects.BehaviorSubject; 11 | import pl.huczeq.rtspplayer.RtspPlayerApp; 12 | import pl.huczeq.rtspplayer.data.repositories.base.CameraRepository; 13 | import pl.huczeq.rtspplayer.domain.usecases.DataMigrationUseCase; 14 | import pl.huczeq.rtspplayer.util.states.CompletableState; 15 | 16 | public class DataMigrationViewModel { 17 | 18 | private RtspPlayerApp app; 19 | private CameraRepository cameraRepository; 20 | private BehaviorSubject processingState; 21 | private DataMigrationUseCase dataMigrationUseCase; 22 | 23 | @Inject 24 | public DataMigrationViewModel(@ApplicationContext Context app, CameraRepository cameraRepository, DataMigrationUseCase dataMigrationUseCase) { 25 | this.app = (RtspPlayerApp) app; 26 | this.cameraRepository = cameraRepository; 27 | this.processingState = BehaviorSubject.create(); 28 | this.processingState.onNext(CompletableState.Builder.IDLE()); 29 | this.dataMigrationUseCase = dataMigrationUseCase; 30 | } 31 | 32 | public void startProcessing() { 33 | if(this.processingState.getValue().isProcessing()) 34 | return; 35 | this.processingState.onNext(CompletableState.Builder.PROCESSING()); 36 | this.dataMigrationUseCase.execute(null, new DisposableCompletableObserver() { 37 | @Override 38 | public void onComplete() { 39 | processingState.onNext(CompletableState.Builder.SUCCESSFULLY()); 40 | processingState.onComplete(); 41 | app.dataMigrationManager = null; 42 | } 43 | 44 | @Override 45 | public void onError(@NonNull Throwable e) { 46 | processingState.onError(e); 47 | } 48 | }); 49 | } 50 | 51 | public BehaviorSubject getProcessingState() { 52 | return processingState; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_select_camera.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 25 | 26 | 37 | 38 | 50 | 51 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/ui/addeditcamera/addcamera/AddCameraActivity.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.ui.addeditcamera.addcamera; 2 | 3 | import android.os.Bundle; 4 | import android.util.Log; 5 | import android.view.View; 6 | 7 | import androidx.lifecycle.ViewModelProvider; 8 | 9 | import javax.inject.Inject; 10 | 11 | import dagger.hilt.android.AndroidEntryPoint; 12 | import pl.huczeq.rtspplayer.R; 13 | import pl.huczeq.rtspplayer.domain.usecases.LoadCameraToPatternUseCase; 14 | import pl.huczeq.rtspplayer.ui.addeditcamera.BaseCameraFormActivity; 15 | import pl.huczeq.rtspplayer.ui.addeditcamera.CameraFormViewModel; 16 | 17 | @AndroidEntryPoint 18 | public class AddCameraActivity extends BaseCameraFormActivity { 19 | 20 | private static final String TAG = AddCameraActivity.class.getSimpleName(); 21 | 22 | public static final String EXTRA_MODE = "mode"; 23 | 24 | public static final int MODE_DUPLICATE_CAMERA_INSTACE = 1; 25 | public static final int MODE_DUPLICATE_CAMERA_GROUP = 2; 26 | 27 | @Inject 28 | public AddCameraViewModel.AssistedFactory viewModelAssistedFactory; 29 | 30 | private CameraFormViewModel viewModel; 31 | 32 | @Override 33 | protected CameraFormViewModel buildViewModel() { 34 | LoadCameraToPatternUseCase.Params params = null; 35 | long cameraInstanceId = getIntent().getLongExtra(EXTRA_CAMERA_INSTANCE_ID, -1); 36 | if(cameraInstanceId > 0) { 37 | int mode = getIntent().getIntExtra(EXTRA_MODE, 0); 38 | if(mode != 0) { 39 | switch (mode) { 40 | case MODE_DUPLICATE_CAMERA_INSTACE: 41 | params = new LoadCameraToPatternUseCase.Params(cameraInstanceId, LoadCameraToPatternUseCase.Mode.LOAD_CAMERA_INSTANCE); 42 | break; 43 | case MODE_DUPLICATE_CAMERA_GROUP: 44 | params = new LoadCameraToPatternUseCase.Params(cameraInstanceId, LoadCameraToPatternUseCase.Mode.LOAD_CAMERA_GROUP); 45 | break; 46 | default: 47 | break; 48 | } 49 | } 50 | } 51 | viewModel = new ViewModelProvider(this, new AddCameraViewModel.Factory(this.viewModelAssistedFactory, params)).get(AddCameraViewModel.class); 52 | return viewModel; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/domain/usecases/GenerateCameraGroupUseCase.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.domain.usecases; 2 | 3 | import android.util.Log; 4 | 5 | import java.util.concurrent.Callable; 6 | import java.util.concurrent.ExecutorService; 7 | import java.util.concurrent.Executors; 8 | 9 | import javax.inject.Inject; 10 | 11 | import io.reactivex.rxjava3.annotations.NonNull; 12 | import io.reactivex.rxjava3.core.Scheduler; 13 | import io.reactivex.rxjava3.core.Single; 14 | import io.reactivex.rxjava3.core.SingleEmitter; 15 | import io.reactivex.rxjava3.core.SingleOnSubscribe; 16 | import io.reactivex.rxjava3.disposables.Disposable; 17 | import io.reactivex.rxjava3.functions.Cancellable; 18 | import io.reactivex.rxjava3.schedulers.Schedulers; 19 | import pl.huczeq.rtspplayer.AppExecutors; 20 | import pl.huczeq.rtspplayer.data.model.CameraGroup; 21 | import pl.huczeq.rtspplayer.domain.usecases.base.SingleUseCase; 22 | import pl.huczeq.rtspplayer.domain.cameragenerator.CameraGroupGenerator; 23 | import pl.huczeq.rtspplayer.domain.model.CameraGroupModel; 24 | 25 | public class GenerateCameraGroupUseCase extends SingleUseCase { 26 | 27 | @Inject 28 | public GenerateCameraGroupUseCase(AppExecutors executors) { 29 | super(executors); 30 | } 31 | 32 | @Override 33 | public AppExecutors.AppExecutor executor() { 34 | return new AppExecutors.AppExecutor(Executors.newSingleThreadExecutor()); 35 | } 36 | 37 | @Override 38 | protected Single buildObservable(CameraGroupModel cameraGroupModel) { 39 | return Single.create(new SingleOnSubscribe() { 40 | @Override 41 | public void subscribe(@NonNull SingleEmitter emitter) throws Throwable { 42 | Thread workerThread = Thread.currentThread(); 43 | emitter.setCancellable(new Cancellable() { 44 | @Override 45 | public void cancel() throws Throwable { 46 | workerThread.interrupt(); 47 | } 48 | }); 49 | CameraGroupGenerator generator = new CameraGroupGenerator(); 50 | try { 51 | emitter.onSuccess(generator.generate(cameraGroupModel)); 52 | }catch (Exception e){ 53 | e.printStackTrace(); 54 | } 55 | } 56 | }); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/util/Timer.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.util; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | import android.util.Log; 6 | 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | public class Timer { 10 | 11 | public interface Callback { 12 | void onStart(); 13 | void onFinished(); 14 | } 15 | 16 | private int delay; 17 | private Callback callback; 18 | private Thread thread; 19 | private Handler uiThreadHandler = new Handler(Looper.getMainLooper()); 20 | 21 | public Timer(int delay, @NotNull Callback callback) { 22 | this.delay = delay; 23 | this.callback = callback; 24 | } 25 | 26 | public void start() { 27 | stopWithoutNotifying(); 28 | thread = createThread(); 29 | thread.start(); 30 | } 31 | 32 | public void stop() { 33 | if(thread != null) { 34 | thread.interrupt(); 35 | callback.onFinished(); 36 | } 37 | thread = null; 38 | } 39 | 40 | public void stopWithoutNotifying() { 41 | if(thread != null) 42 | thread.interrupt(); 43 | thread = null; 44 | } 45 | 46 | private Thread createThread() { 47 | return new Thread(new Runnable() { 48 | @Override 49 | public void run() { 50 | final Thread currentThread = Thread.currentThread(); 51 | if(currentThread.isInterrupted()) 52 | return; 53 | uiThreadHandler.post(new Runnable() { 54 | @Override 55 | public void run() { 56 | if(currentThread.isInterrupted()) 57 | return; 58 | callback.onStart(); 59 | } 60 | }); 61 | try { 62 | Thread.sleep(delay); 63 | } catch (InterruptedException e) { 64 | e.printStackTrace(); 65 | return; 66 | } 67 | uiThreadHandler.post(new Runnable() { 68 | @Override 69 | public void run() { 70 | if(currentThread.isInterrupted()) 71 | return; 72 | callback.onFinished(); 73 | } 74 | }); 75 | } 76 | }); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/data/sources/local/ThumbnailDiskCache.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.data.sources.local; 2 | 3 | import android.graphics.Bitmap; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.util.concurrent.Callable; 8 | import java.util.concurrent.Executors; 9 | 10 | import javax.inject.Inject; 11 | import javax.inject.Singleton; 12 | 13 | import io.reactivex.rxjava3.core.Single; 14 | import io.reactivex.rxjava3.core.SingleObserver; 15 | import pl.huczeq.rtspplayer.AppConfiguration; 16 | import pl.huczeq.rtspplayer.AppExecutors; 17 | 18 | @Singleton 19 | public class ThumbnailDiskCache { 20 | 21 | private final AppExecutors appExecutors; 22 | private final AppExecutors.AppExecutor ioExecutor; 23 | private final File thumbnailsDirectory; 24 | 25 | @Inject 26 | public ThumbnailDiskCache(AppExecutors appExecutors, AppConfiguration configuration) { 27 | this.appExecutors = appExecutors; 28 | this.ioExecutor = new AppExecutors.AppExecutor(Executors.newFixedThreadPool(2)); 29 | this.thumbnailsDirectory = configuration.getThumbnailDirectory(); 30 | } 31 | 32 | public void saveThumbnail(String name, Bitmap bitmap) { 33 | ioExecutor.execute(new Runnable() { 34 | @Override 35 | public void run() { 36 | try { 37 | SaveBitmapTask.save(bitmap, thumbnailsDirectory, name); 38 | } catch (IOException e) { 39 | e.printStackTrace(); 40 | } 41 | } 42 | }); 43 | } 44 | 45 | public void getThumbnail(String id, SingleObserver observer) { 46 | Single observable = Single.fromCallable(new Callable() { 47 | @Override 48 | public Bitmap call() throws Exception { 49 | return ReadBitmapTask.load(thumbnailsDirectory, id); 50 | } 51 | }).subscribeOn(ioExecutor.scheduler()) 52 | .observeOn(appExecutors.mainThread().scheduler()); 53 | observable.subscribe(observer); 54 | } 55 | 56 | public void deleteThumbnail(String name) { 57 | ioExecutor.execute(new Runnable() { 58 | @Override 59 | public void run() { 60 | File file = new File(thumbnailsDirectory, name); 61 | if(file.exists()) 62 | file.delete(); 63 | } 64 | }); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/ui/adapters/base/BaseRecyclerViewAdapter.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.ui.adapters.base; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | 6 | import androidx.annotation.MainThread; 7 | import androidx.annotation.NonNull; 8 | import androidx.recyclerview.widget.DiffUtil; 9 | import androidx.recyclerview.widget.RecyclerView; 10 | 11 | import java.util.ArrayDeque; 12 | import java.util.List; 13 | 14 | public abstract class BaseRecyclerViewAdapter extends RecyclerView.Adapter { 15 | 16 | protected List dataSet; 17 | private final ArrayDeque> pendingUpdates = new ArrayDeque<>(); 18 | private final Handler uiThreadHandler = new Handler(Looper.getMainLooper()); 19 | 20 | public BaseRecyclerViewAdapter(@NonNull List dataSet) { 21 | this.dataSet = dataSet; 22 | } 23 | 24 | public abstract DiffUtil.Callback buildDiffUtilCallback(List oldDataSet, List newDataSet); 25 | 26 | @MainThread 27 | public void updateDataSet(List newDataSet) { 28 | if(this.dataSet == newDataSet) 29 | return; 30 | pendingUpdates.add(newDataSet); 31 | if(pendingUpdates.size() == 1) 32 | internalUpdate(newDataSet); 33 | } 34 | 35 | private void internalUpdate(List newDataSet) { 36 | new Thread(new Runnable() { 37 | @Override 38 | public void run() { 39 | DiffUtil.DiffResult result = DiffUtil.calculateDiff(buildDiffUtilCallback(dataSet, newDataSet), false); 40 | uiThreadHandler.post(new Runnable() { 41 | @Override 42 | public void run() { 43 | dataSet = newDataSet; 44 | result.dispatchUpdatesTo(BaseRecyclerViewAdapter.this); 45 | processQueue(); 46 | } 47 | }); 48 | } 49 | }).start(); 50 | } 51 | 52 | @MainThread 53 | protected void processQueue() { 54 | pendingUpdates.remove(); 55 | if(pendingUpdates.isEmpty()) 56 | return; 57 | List lastList = pendingUpdates.peekLast(); 58 | if(!pendingUpdates.isEmpty()) { 59 | pendingUpdates.clear(); 60 | } 61 | internalUpdate(lastList); 62 | } 63 | 64 | @Override 65 | public int getItemCount() { 66 | return dataSet.size(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/util/validation/FieldRules.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.util.validation; 2 | 3 | import android.os.Build; 4 | import android.util.Patterns; 5 | 6 | import com.google.common.base.Strings; 7 | import com.google.common.net.InetAddresses; 8 | 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | import java.util.regex.Pattern; 12 | 13 | import pl.huczeq.rtspplayer.util.validation.interfaces.FieldRule; 14 | 15 | public class FieldRules { 16 | 17 | static final Pattern numericPattern = Pattern.compile("-?\\d+(\\.\\d+)?"); 18 | 19 | public static FieldRule NUMERIC() { 20 | return new FieldRule() { 21 | @Override 22 | public Integer checkValidity(String text) { 23 | if(Strings.isNullOrEmpty(text)) 24 | return Errors.INCORRECT_VALUE; 25 | if(!isNumeric(text)) 26 | return Errors.INCORRECT_VALUE; 27 | return null; 28 | } 29 | 30 | public boolean isNumeric(@NotNull String strNum) { 31 | return numericPattern.matcher(strNum).matches(); 32 | } 33 | }; 34 | } 35 | 36 | public static FieldRule REQUIRED() { 37 | return new FieldRule() { 38 | @Override 39 | public Integer checkValidity(String text) { 40 | if(text == null || text.isEmpty()) 41 | return Errors.IS_REQUIRED; 42 | return null; 43 | } 44 | }; 45 | } 46 | 47 | public static FieldRule ADDRESS_IP() { 48 | return new FieldRule() { 49 | @Override 50 | public Integer checkValidity(String text) { 51 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { 52 | if(!InetAddresses.isInetAddress(text) && !Patterns.DOMAIN_NAME.matcher(text).matches()) 53 | return Errors.INCORRECT_VALUE; 54 | } 55 | return null; 56 | } 57 | }; 58 | } 59 | 60 | public static FieldRule PORT() { 61 | return new FieldRule() { 62 | @Override 63 | public Integer checkValidity(String text) { 64 | try { 65 | int port = Integer.parseInt(text); 66 | if(port < 0 || port > 65536) 67 | return Errors.INCORRECT_VALUE; 68 | }catch (Exception e) { 69 | return Errors.INCORRECT_VALUE; 70 | } 71 | return null; 72 | } 73 | }; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_camera_select_radiobutton.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 21 | 30 | 39 | 40 | 41 | 56 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/domain/backup/Keys.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.domain.backup; 2 | 3 | public final class Keys { 4 | public static final String VERSION = "version"; 5 | public static final String SETTINGS = "settings"; 6 | 7 | public static final class V1 { 8 | public static final String CAMERAS = "camerasData"; 9 | public static final class Camera { 10 | public static final String NAME = "name"; 11 | public static final String URL = "url"; 12 | public static final String USERNAME = "userName"; 13 | public static final String PASSWORD = "password"; 14 | public static final String ADDRESS_IP = "addressIp"; 15 | public static final String PORT = "port"; 16 | public static final String CHANNEL = "channel"; 17 | public static final String STREAM = "stream"; 18 | public static final String PRODUCER = "producer"; 19 | public static final String MODEL = "model"; 20 | public static final String SERVER_URL = "serverUrl"; 21 | } 22 | } 23 | 24 | public static final class V2 { 25 | public static final String CAMERA_GROUPS = "cameraGroups"; 26 | public static final String CAMERA_GROUPS_VERSION = "cameraGroupsVersion"; 27 | 28 | public static final class CameraGroup { 29 | public static final String CAMERA_PATTERN = "cameraPattern"; 30 | public static final String CAMERA_INSTANCES = "cameraInstances"; 31 | } 32 | 33 | public static final class Camera { 34 | public static final String NAME = "name"; 35 | public static final String URL = "url"; 36 | } 37 | 38 | public static final class CameraPattern { 39 | public static final String USERNAME = "username"; 40 | public static final String PASSWORD = "password"; 41 | public static final String IP_ADDRESS = "ipaddress"; 42 | public static final String PORT = "port"; 43 | public static final String CHANNEL = "channel"; 44 | public static final String STREAM = "stream"; 45 | public static final String PRODUCER = "producer"; 46 | public static final String MODEL = "model"; 47 | public static final String SERVER_URL = "serverUrl"; 48 | } 49 | 50 | public static final class CameraInstance { 51 | public static final String VARIABLES_DATA = "variablesData"; 52 | } 53 | 54 | public static final class Settings { 55 | public static final String APP_START_CAMERA_INDEX = "appStartCameraIndex"; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/domain/urlgenerator/UrlGenerator.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.domain.urlgenerator; 2 | 3 | import pl.huczeq.rtspplayer.data.model.urltemplates.UrlTemplate; 4 | 5 | public class UrlGenerator { 6 | 7 | public static String generate(UrlTemplate urlTemplate, UrlComponentsProvider urlComponentsProvider) { 8 | if(urlComponentsProvider.usesAuthorization()) 9 | return parseTemplate(urlTemplate, urlTemplate.getUrlTemplateAuth(), urlComponentsProvider); 10 | return parseTemplate(urlTemplate, urlTemplate.getUrlTemplateNoneAuth(), urlComponentsProvider); 11 | } 12 | 13 | private static String parseTemplate(UrlTemplate urlTemplate, String url, UrlComponentsProvider urlComponentsProvider) { 14 | StringBuilder fullUrl = new StringBuilder(); 15 | String bane; 16 | int posS, posE; 17 | while((posS = url.indexOf("{")) >= 0) { 18 | posE = url.indexOf("}"); 19 | if(posE < 0) 20 | return fullUrl.toString(); 21 | fullUrl.append(url.substring(0, posS)); 22 | bane = url.substring(posS+1, posE); 23 | fullUrl.append(getValueByComponentName(urlTemplate, bane, urlComponentsProvider)); 24 | url = url.substring(posE+1); 25 | } 26 | fullUrl.append(url); 27 | return fullUrl.toString(); 28 | } 29 | 30 | private static String getValueByComponentName(UrlTemplate urlTemplate, String variable, UrlComponentsProvider urlComponentsProvider) { 31 | if(variable.equalsIgnoreCase("user")) { 32 | return urlComponentsProvider.getUserName(); 33 | }else if(variable.equalsIgnoreCase("password")) { 34 | return urlComponentsProvider.getPassword(); 35 | }else if(variable.equalsIgnoreCase("addressip")) { 36 | return urlComponentsProvider.getAddressIp(); 37 | }else if(variable.equalsIgnoreCase("port")) { 38 | return urlComponentsProvider.getPort(); 39 | }else if(variable.equalsIgnoreCase("channel")) { 40 | return urlComponentsProvider.getChannel(); 41 | }else if(variable.equalsIgnoreCase("stream")) { 42 | return streamTypeToString(urlTemplate, urlComponentsProvider.getStream()); 43 | }else if(variable.equalsIgnoreCase("serverurl")) { 44 | return urlComponentsProvider.getServerUrl(); 45 | }else { 46 | return ""; 47 | } 48 | } 49 | private static String streamTypeToString(UrlTemplate urlTemplate, int streamType) { 50 | if(streamType == UrlTemplate.StreamType.MAIN_STREAM) 51 | return urlTemplate.getMainStreamValue(); 52 | return urlTemplate.getSubStreamValue(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/AppExecutors.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | 6 | import androidx.annotation.CallSuper; 7 | 8 | import java.util.concurrent.Executor; 9 | import java.util.concurrent.Executors; 10 | 11 | import javax.inject.Inject; 12 | import javax.inject.Singleton; 13 | 14 | import io.reactivex.rxjava3.core.Scheduler; 15 | import io.reactivex.rxjava3.schedulers.Schedulers; 16 | 17 | public class AppExecutors { 18 | 19 | protected final AppExecutor diskIO; 20 | protected final AppExecutor dbIO; 21 | protected final AppExecutor bgThread; 22 | protected final AppExecutor mainThread; 23 | 24 | public AppExecutors() { 25 | this.diskIO = new AppExecutor(Executors.newFixedThreadPool(1)); 26 | this.dbIO = new AppExecutor(Executors.newFixedThreadPool(1)); 27 | this.bgThread = new AppExecutor(Executors.newFixedThreadPool(1)); 28 | this.mainThread = new AppExecutor(new MainThreadExecutor()); 29 | } 30 | 31 | protected AppExecutors(Executor mainThreadExecutor) { 32 | this.diskIO = new AppExecutor(Executors.newFixedThreadPool(1)); 33 | this.dbIO = new AppExecutor(Executors.newFixedThreadPool(1)); 34 | this.bgThread = new AppExecutor(Executors.newFixedThreadPool(1)); 35 | this.mainThread = new AppExecutor(mainThreadExecutor); 36 | } 37 | 38 | public AppExecutor diskIO() { 39 | return diskIO; 40 | } 41 | 42 | public AppExecutor dbIO() { 43 | return dbIO; 44 | } 45 | 46 | public AppExecutor bgThread() { 47 | return bgThread; 48 | } 49 | 50 | public AppExecutor mainThread() { 51 | return mainThread; 52 | } 53 | 54 | private static class MainThreadExecutor implements Executor { 55 | private Handler mainThreadHandler = new Handler(Looper.getMainLooper()); 56 | 57 | @Override 58 | public void execute(Runnable runnable) { 59 | mainThreadHandler.post(runnable); 60 | } 61 | } 62 | 63 | public static class AppExecutor implements Executor{ 64 | private Executor executor; 65 | private Scheduler scheduler; 66 | 67 | public AppExecutor(Executor executor) { 68 | this.executor = executor; 69 | this.scheduler = Schedulers.from(executor); 70 | } 71 | 72 | public Executor executor() { 73 | return this.executor; 74 | } 75 | 76 | public Scheduler scheduler() { 77 | return this.scheduler; 78 | } 79 | 80 | @Override 81 | public void execute(Runnable runnable) { 82 | this.executor.execute(runnable); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/domain/usecases/SaveCameraThumbnailUseCase.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.domain.usecases; 2 | 3 | import android.graphics.Bitmap; 4 | 5 | import androidx.room.util.StringUtil; 6 | 7 | import com.google.common.base.Strings; 8 | 9 | import java.text.SimpleDateFormat; 10 | import java.util.Calendar; 11 | 12 | import javax.inject.Inject; 13 | 14 | import io.reactivex.rxjava3.core.Completable; 15 | import pl.huczeq.rtspplayer.AppExecutors; 16 | import pl.huczeq.rtspplayer.data.model.CameraInstance; 17 | import pl.huczeq.rtspplayer.data.repositories.base.CameraRepository; 18 | import pl.huczeq.rtspplayer.data.repositories.base.CameraThumbnailRepository; 19 | import pl.huczeq.rtspplayer.domain.usecases.base.CompletableUseCase; 20 | 21 | public class SaveCameraThumbnailUseCase extends CompletableUseCase { 22 | 23 | private CameraRepository cameraRepository; 24 | private CameraThumbnailRepository thumbnailRepository; 25 | 26 | @Inject 27 | public SaveCameraThumbnailUseCase(AppExecutors executors, CameraRepository cameraRepository, CameraThumbnailRepository cameraThumbnailRepository) { 28 | super(executors); 29 | this.cameraRepository = cameraRepository; 30 | this.thumbnailRepository = cameraThumbnailRepository; 31 | } 32 | 33 | @Override 34 | protected Completable buildObservable(Params params) { 35 | return Completable.fromRunnable(new Runnable() { 36 | @Override 37 | public void run() { 38 | CameraInstance cameraInstance = cameraRepository.getCameraInstanceByIdSync(params.cameraId); 39 | String thumbnailFileName = cameraInstance.getPreviewImg(); 40 | boolean fileNameChanged = false; 41 | if(Strings.isNullOrEmpty(thumbnailFileName)) { 42 | SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd_HH:mm:ss"); 43 | thumbnailFileName = cameraInstance.getId() + "_" + simpleDateFormat.format(Calendar.getInstance().getTime()) + ".png"; 44 | cameraInstance.setPreviewImg(thumbnailFileName); 45 | fileNameChanged = true; 46 | } 47 | thumbnailRepository.saveThumbnail(thumbnailFileName, params.thumbnailBitmap); 48 | if(fileNameChanged) 49 | cameraRepository.updateCameraInstanceSync(cameraInstance); 50 | } 51 | }); 52 | } 53 | 54 | public static class Params { 55 | private long cameraId; 56 | private Bitmap thumbnailBitmap; 57 | 58 | public Params(long cameraId, Bitmap thumbnailBitmap) { 59 | this.cameraId = cameraId; 60 | this.thumbnailBitmap = thumbnailBitmap; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/res/layout/player_control_interface.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 10 | 14 | 15 | 24 | 25 | 33 | 34 | 42 | 43 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/data/model/urltemplates/UrlTemplate.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.data.model.urltemplates; 2 | 3 | import static java.lang.annotation.RetentionPolicy.SOURCE; 4 | 5 | import androidx.annotation.IntDef; 6 | 7 | import org.json.JSONArray; 8 | import org.json.JSONException; 9 | import org.json.JSONObject; 10 | 11 | import java.lang.annotation.Retention; 12 | import java.util.HashSet; 13 | import java.util.Set; 14 | 15 | public class UrlTemplate { 16 | 17 | @IntDef 18 | @Retention(SOURCE) 19 | public @interface StreamType { 20 | int MAIN_STREAM = 0; 21 | int SUB_STREAM = 1; 22 | } 23 | 24 | @IntDef 25 | @Retention(SOURCE) 26 | public @interface AdditionalFields { 27 | int Channel = 1; 28 | int Stream = 2; 29 | int ServerUrl = 3; 30 | } 31 | 32 | private String urlTemplateAuth; 33 | private String urlTemplateNoneAuth; 34 | Set additionalFields; 35 | private String mainStreamValue = String.valueOf(StreamType.MAIN_STREAM); 36 | private String subStreamValue = String.valueOf(StreamType.SUB_STREAM); 37 | 38 | public UrlTemplate(String urlAuth, String urlNoneAuth) { 39 | this.urlTemplateAuth = urlAuth; 40 | this.urlTemplateNoneAuth = urlNoneAuth; 41 | this.additionalFields = new HashSet<>(); 42 | } 43 | 44 | public UrlTemplate(JSONObject json) { 45 | this(json.optString("urlAuth", ""), json.optString("urlNoneAuth", "")); 46 | try { 47 | JSONArray array = json.getJSONArray("additionalFields"); 48 | for(int i = 0; i < array.length(); i++) { 49 | this.additionalFields.add(array.getInt(i)); 50 | } 51 | this.mainStreamValue = json.optString("mainStream", this.mainStreamValue); 52 | this.subStreamValue = json.optString("subStream", this.subStreamValue); 53 | } catch (JSONException e) { 54 | e.printStackTrace(); 55 | this.urlTemplateAuth = null; 56 | this.urlTemplateNoneAuth = null; 57 | } 58 | } 59 | 60 | public boolean isCorrect() { 61 | return (!this.urlTemplateAuth.equals("") && !this.urlTemplateNoneAuth.equals("")); 62 | } 63 | 64 | public boolean useField(int field) { 65 | return this.additionalFields.contains(field); 66 | } 67 | 68 | public String getUrlTemplateAuth() { 69 | return urlTemplateAuth; 70 | } 71 | 72 | public String getUrlTemplateNoneAuth() { 73 | return urlTemplateNoneAuth; 74 | } 75 | 76 | public String getMainStreamValue() { 77 | return mainStreamValue; 78 | } 79 | 80 | public String getSubStreamValue() { 81 | return subStreamValue; 82 | } 83 | 84 | public void addField(@AdditionalFields int fieldId) { 85 | this.additionalFields.add(fieldId); 86 | } 87 | } -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/ui/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.ui; 2 | 3 | import android.graphics.drawable.Drawable; 4 | import android.view.MenuItem; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.ImageView; 8 | 9 | import androidx.annotation.CallSuper; 10 | import androidx.appcompat.app.AppCompatActivity; 11 | import androidx.appcompat.widget.Toolbar; 12 | 13 | import javax.inject.Inject; 14 | 15 | import dagger.hilt.android.AndroidEntryPoint; 16 | import dagger.hilt.internal.Preconditions; 17 | import pl.huczeq.rtspplayer.R; 18 | import pl.huczeq.rtspplayer.Settings; 19 | 20 | @AndroidEntryPoint 21 | public abstract class BaseActivity extends AppCompatActivity { 22 | 23 | private Toolbar toolbar; 24 | private ImageView toolbarIcon; 25 | 26 | @Inject 27 | protected Settings settings; 28 | 29 | @Override 30 | public void setContentView(int layoutResID) { 31 | super.setContentView(layoutResID); 32 | initToolbar(); 33 | } 34 | 35 | @Override 36 | public void setContentView(View view) { 37 | super.setContentView(view); 38 | initToolbar(); 39 | } 40 | 41 | @Override 42 | public void setContentView(View view, ViewGroup.LayoutParams params) { 43 | super.setContentView(view, params); 44 | initToolbar(); 45 | } 46 | 47 | private void initToolbar() { 48 | toolbar = findViewById(R.id.toolbar); 49 | if(toolbar != null) 50 | initToolbar(toolbar); 51 | } 52 | 53 | @CallSuper 54 | protected void initToolbar(Toolbar toolbar) { 55 | Preconditions.checkNotNull(toolbar); 56 | setSupportActionBar(toolbar); 57 | if(getTitle() == null) 58 | getSupportActionBar().setTitle(R.string.app_name); 59 | else 60 | getSupportActionBar().setTitle(getTitle()); 61 | toolbarIcon = toolbar.findViewById(R.id.iconToolbar); 62 | } 63 | 64 | public void enableToolbarIcon(Drawable icon, View.OnClickListener onClickListener) { 65 | Preconditions.checkNotNull(icon); 66 | Preconditions.checkNotNull(toolbarIcon); 67 | toolbarIcon.setVisibility(View.VISIBLE); 68 | toolbarIcon.setImageDrawable(icon); 69 | if(onClickListener != null) 70 | toolbarIcon.setOnClickListener(onClickListener); 71 | } 72 | 73 | public void showBackToolbarIcon() { 74 | if(getSupportActionBar() == null) 75 | return; 76 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 77 | getSupportActionBar().setDisplayShowHomeEnabled(true); 78 | } 79 | 80 | @Override 81 | public boolean onOptionsItemSelected(MenuItem item) { 82 | if (item.getItemId() == android.R.id.home) { 83 | finish(); 84 | } 85 | return super.onOptionsItemSelected(item); 86 | } 87 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_app_info.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 19 | 20 | 29 | 30 | 40 | 50 | 51 | 63 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/util/validation/FieldValidator.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.util.validation; 2 | 3 | import androidx.databinding.BaseObservable; 4 | import androidx.databinding.Bindable; 5 | 6 | import java.util.LinkedList; 7 | 8 | import pl.huczeq.rtspplayer.BR; 9 | import pl.huczeq.rtspplayer.util.validation.interfaces.BasicCondition; 10 | import pl.huczeq.rtspplayer.util.validation.interfaces.FieldRule; 11 | import pl.huczeq.rtspplayer.util.validation.interfaces.ValueProvider; 12 | 13 | public class FieldValidator extends BaseObservable { 14 | 15 | private Integer error; 16 | private final LinkedList rules; 17 | private final ValueProvider textProvider; 18 | private final BasicCondition basicCondition; 19 | 20 | private FieldValidator(LinkedList rules, ValueProvider textProvider, BasicCondition basicCondition) { 21 | this.rules = rules; 22 | this.textProvider = textProvider; 23 | this.basicCondition = basicCondition; 24 | } 25 | 26 | public boolean isValid(boolean showError) { 27 | Integer errorCode = checkValidity(); 28 | if(showError){ 29 | if(basicCondition == null || basicCondition.allows()) 30 | setError(errorCode); 31 | } 32 | return errorCode == null; 33 | } 34 | 35 | private Integer checkValidity() { 36 | String value = textProvider.provideValue(); 37 | Integer tErrorCode; 38 | for(FieldRule rule : rules) { 39 | if ((tErrorCode = rule.checkValidity(value)) != null) 40 | return tErrorCode; 41 | } 42 | return null; 43 | } 44 | 45 | @Bindable 46 | public Integer getError() { 47 | return error; 48 | } 49 | 50 | public void setError(Integer error) { 51 | this.error = error; 52 | notifyPropertyChanged(BR.error); 53 | } 54 | 55 | public static Builder builder(ValueProvider textProvider) { 56 | return new Builder(textProvider); 57 | } 58 | 59 | public static class Builder { 60 | private ValueProvider textProvider; 61 | private LinkedList rules = new LinkedList<>(); 62 | private BasicCondition basicCondition; 63 | 64 | public Builder(ValueProvider textProvider) { 65 | this.textProvider = textProvider; 66 | } 67 | 68 | public Builder basicCondition(BasicCondition condition) { 69 | this.basicCondition = condition; 70 | return this; 71 | } 72 | 73 | public Builder rules(FieldRule ...rules) { 74 | for(FieldRule rule : rules) 75 | this.rules.add(rule); 76 | return this; 77 | } 78 | 79 | public FieldValidator build() { 80 | return new FieldValidator(this.rules, this.textProvider, this.basicCondition); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/data/repositories/CameraThumbnailRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.data.repositories; 2 | 3 | import android.graphics.Bitmap; 4 | 5 | import javax.inject.Inject; 6 | import javax.inject.Singleton; 7 | 8 | import io.reactivex.rxjava3.annotations.NonNull; 9 | import io.reactivex.rxjava3.core.SingleObserver; 10 | import io.reactivex.rxjava3.disposables.Disposable; 11 | import io.reactivex.rxjava3.subjects.PublishSubject; 12 | import pl.huczeq.rtspplayer.data.repositories.base.CameraThumbnailRepository; 13 | import pl.huczeq.rtspplayer.data.sources.cache.ThumbnailCache; 14 | import pl.huczeq.rtspplayer.data.sources.local.ThumbnailDiskCache; 15 | 16 | @Singleton 17 | public class CameraThumbnailRepositoryImpl implements CameraThumbnailRepository { 18 | 19 | public static final String TAG = "CameraThumbnailRepository"; 20 | 21 | private final ThumbnailCache thumbnailCache; 22 | private final ThumbnailDiskCache thumbnailDiskCache; 23 | private final PublishSubject thumbnailUpdatedSubject = PublishSubject.create(); 24 | 25 | @Inject 26 | public CameraThumbnailRepositoryImpl(ThumbnailCache thumbnailCache, ThumbnailDiskCache thumbnailDiskCache) { 27 | this.thumbnailCache = thumbnailCache; 28 | this.thumbnailDiskCache = thumbnailDiskCache; 29 | } 30 | 31 | @Override 32 | public PublishSubject getThumbnailUpdatedSubject() { 33 | return this.thumbnailUpdatedSubject; 34 | } 35 | 36 | @Override 37 | public void saveThumbnail(String name, Bitmap bitmap) { 38 | this.thumbnailCache.save(name, bitmap); 39 | this.thumbnailUpdatedSubject.onNext(name); 40 | this.thumbnailDiskCache.saveThumbnail(name, bitmap); 41 | } 42 | 43 | @Override 44 | public Bitmap getThumbnail(String name) { 45 | Bitmap bitmap = this.thumbnailCache.get(name); 46 | if(bitmap == null) 47 | loadThumbnailFromDisk(name); 48 | return bitmap; 49 | } 50 | 51 | @Override 52 | public void deleteThumbnail(String previewImg) { 53 | this.thumbnailDiskCache.deleteThumbnail(previewImg); 54 | } 55 | 56 | protected void loadThumbnailFromDisk(String name) { 57 | thumbnailDiskCache.getThumbnail(name, new SingleObserver() { 58 | @Override 59 | public void onSubscribe(@NonNull Disposable d) {} 60 | 61 | @Override 62 | public void onSuccess(@NonNull Bitmap bitmap) { 63 | CameraThumbnailRepositoryImpl.this.thumbnailCache.save(name, bitmap); 64 | CameraThumbnailRepositoryImpl.this.notifyThumbnailUpdated(name); 65 | } 66 | 67 | @Override 68 | public void onError(@NonNull Throwable e) {} 69 | }); 70 | } 71 | 72 | protected void notifyThumbnailUpdated(String name) { 73 | this.thumbnailUpdatedSubject.onNext(name); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/ui/addeditcamera/CameraPreviewListAdapter.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.ui.addeditcamera; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.TextView; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.recyclerview.widget.RecyclerView; 11 | 12 | import com.google.common.base.Strings; 13 | 14 | import org.jetbrains.annotations.NotNull; 15 | 16 | import java.util.List; 17 | 18 | import pl.huczeq.rtspplayer.R; 19 | import pl.huczeq.rtspplayer.data.model.CameraInstance; 20 | import pl.huczeq.rtspplayer.util.interfaces.IOnListItemSelected; 21 | 22 | public class CameraPreviewListAdapter extends RecyclerView.Adapter { 23 | 24 | private Context context; 25 | private IOnListItemSelected onItemSelectedListener; 26 | private List cameraInstances; 27 | 28 | public CameraPreviewListAdapter(Context context, List cameraInstances, IOnListItemSelected onItemSelectedListener) { 29 | this.context = context; 30 | this.cameraInstances = cameraInstances; 31 | this.onItemSelectedListener = onItemSelectedListener; 32 | } 33 | 34 | @NonNull 35 | @NotNull 36 | @Override 37 | public ViewHolder onCreateViewHolder(@NonNull @NotNull ViewGroup parent, int viewType) { 38 | View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_camera_preview, parent, false); 39 | ViewHolder holder = new ViewHolder(view); 40 | view.setOnClickListener(new View.OnClickListener() { 41 | @Override 42 | public void onClick(View view) { 43 | if(holder.getBindingAdapterPosition() < 0) 44 | return; 45 | onItemSelectedListener.onCameraItemSelected(cameraInstances.get(holder.getBindingAdapterPosition())); 46 | } 47 | }); 48 | return holder; 49 | } 50 | 51 | @Override 52 | public void onBindViewHolder(@NonNull @NotNull ViewHolder holder, int position) { 53 | CameraInstance cameraInstance = cameraInstances.get(position); 54 | holder.tvName.setText(Strings.isNullOrEmpty(cameraInstance.getName())? String.valueOf(position + 1) : cameraInstance.getName()); 55 | holder.tvUrl.setText(cameraInstance.getUrl()); 56 | } 57 | 58 | @Override 59 | public int getItemCount() { 60 | return this.cameraInstances.size(); 61 | } 62 | 63 | protected static class ViewHolder extends RecyclerView.ViewHolder{ 64 | TextView tvName, tvUrl; 65 | 66 | public ViewHolder(@NonNull @NotNull View itemView) { 67 | super(itemView); 68 | this.tvName = itemView.findViewById(R.id.tvName); 69 | this.tvUrl = itemView.findViewById(R.id.tvUrl); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_view_camera.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 15 | 25 | 26 | 35 | 36 | 37 | 52 | 53 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/ui/cameralist/CameraListViewModel.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.ui.cameralist; 2 | 3 | import androidx.lifecycle.LiveData; 4 | import androidx.lifecycle.MutableLiveData; 5 | import androidx.lifecycle.ViewModel; 6 | 7 | import java.util.LinkedList; 8 | import java.util.List; 9 | 10 | import javax.inject.Inject; 11 | 12 | import dagger.hilt.android.lifecycle.HiltViewModel; 13 | import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; 14 | import io.reactivex.rxjava3.annotations.NonNull; 15 | import io.reactivex.rxjava3.observers.DisposableObserver; 16 | import pl.huczeq.rtspplayer.data.model.Camera; 17 | import pl.huczeq.rtspplayer.data.repositories.base.CameraRepository; 18 | import pl.huczeq.rtspplayer.data.repositories.base.CameraThumbnailRepository; 19 | import pl.huczeq.rtspplayer.domain.usecases.DeleteCameraUseCase; 20 | 21 | @HiltViewModel 22 | public class CameraListViewModel extends ViewModel { 23 | 24 | private CameraRepository cameraRepository; 25 | private CameraThumbnailRepository thumbnailRepository; 26 | 27 | private final MutableLiveData> thumbnailsUpdatedList; 28 | private final DisposableObserver thumbnailsUpdateObserver; 29 | 30 | private DeleteCameraUseCase deleteCameraUseCase; 31 | 32 | @Inject 33 | public CameraListViewModel(CameraRepository cameraRepository, CameraThumbnailRepository thumbnailRepository, DeleteCameraUseCase deleteCameraUseCase) { 34 | this.cameraRepository = cameraRepository; 35 | this.thumbnailRepository = thumbnailRepository; 36 | this.deleteCameraUseCase = deleteCameraUseCase; 37 | 38 | this.thumbnailsUpdatedList = new MutableLiveData<>(new LinkedList<>()); 39 | this.thumbnailsUpdateObserver = new DisposableObserver() { 40 | @Override 41 | public void onNext(String thumbnailName) { 42 | thumbnailsUpdatedList.getValue().add(thumbnailName); 43 | thumbnailsUpdatedList.setValue(thumbnailsUpdatedList.getValue()); 44 | } 45 | @Override 46 | public void onError(@NonNull Throwable e) {} 47 | @Override 48 | public void onComplete() {} 49 | }; 50 | this.thumbnailRepository.getThumbnailUpdatedSubject() 51 | .observeOn(AndroidSchedulers.mainThread()) 52 | .subscribe(this.thumbnailsUpdateObserver); 53 | } 54 | 55 | @Override 56 | protected void onCleared() { 57 | super.onCleared(); 58 | if(this.thumbnailsUpdateObserver != null) 59 | this.thumbnailsUpdateObserver.dispose(); 60 | } 61 | 62 | public LiveData> getAllCameras() { 63 | return this.cameraRepository.fetchAllCameras(); 64 | } 65 | 66 | public MutableLiveData> getThumbnailsUpdatedList() { 67 | return thumbnailsUpdatedList; 68 | } 69 | 70 | public void deleteCamera(Camera camera) { 71 | this.deleteCameraUseCase.execute(camera); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/ui/player/view/renderer/FrameShader.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.ui.player.view.renderer; 2 | 3 | import android.opengl.GLES20; 4 | 5 | public class FrameShader extends Shader { 6 | 7 | 8 | public static final String vertexShader = 9 | "#extension GL_OES_EGL_image_external : require\n" + 10 | "uniform mat4 mvpMatrix;\n" + 11 | "uniform mat4 stMatrix;\n" + 12 | "attribute vec2 vertexPosition;\n" + 13 | "attribute vec2 textureCoords;\n" + 14 | "varying vec2 finalTextureCoords;\n" + 15 | "void main() {\n" + 16 | " finalTextureCoords = (stMatrix * vec4(textureCoords,1.0,1.0)).xy;\n" + 17 | " gl_Position = mvpMatrix * vec4(vertexPosition.x, vertexPosition.y, 0, 1.0);\n" + 18 | "}"; 19 | 20 | public static final String fragmentShader = 21 | "#extension GL_OES_EGL_image_external : require\n" + 22 | "precision mediump float;\n" + 23 | "uniform samplerExternalOES texture;\n" + 24 | "uniform vec2 textureScale;\n" + 25 | "varying vec2 finalTextureCoords;\n" + 26 | "void main() {\n" + 27 | " gl_FragColor = texture2D(texture, finalTextureCoords * textureScale);\n" + 28 | "}"; 29 | 30 | private int mvpMatrixUniform; 31 | private int stMatrixUniform; 32 | private int textureUniform; 33 | private int textureScaleUniform; 34 | private int vertexPositionAttrib; 35 | private int textureCoordsAttrib; 36 | 37 | public FrameShader() { 38 | super(vertexShader, fragmentShader); 39 | 40 | this.mvpMatrixUniform = GLES20.glGetUniformLocation(program, "mvpMatrix"); 41 | this.stMatrixUniform = GLES20.glGetUniformLocation(program, "stMatrix"); 42 | this.textureUniform = GLES20.glGetUniformLocation(program, "texture"); 43 | this.textureScaleUniform = GLES20.glGetUniformLocation(program, "textureScale"); 44 | this.vertexPositionAttrib = GLES20.glGetAttribLocation(program, "vertexPosition"); 45 | this.textureCoordsAttrib = GLES20.glGetAttribLocation(program, "textureCoords"); 46 | } 47 | 48 | public void loadMVPMatrix(float[] matrix) { 49 | GLES20.glUniformMatrix4fv(this.mvpMatrixUniform, 1, false, matrix, 0); 50 | } 51 | 52 | public void loadSTMatrix(float[] matrix) { 53 | GLES20.glUniformMatrix4fv(this.stMatrixUniform, 1, false, matrix, 0); 54 | } 55 | public void loadTexture(int i) { 56 | GLES20.glUniform1i(this.textureUniform, i); 57 | } 58 | 59 | public void loadTextureScale(float[] scaleFactor) { 60 | GLES20.glUniform2fv(this.textureScaleUniform, 1, scaleFactor, 0); 61 | } 62 | 63 | public int getVertexPositionAttrib() { 64 | return vertexPositionAttrib; 65 | } 66 | 67 | public int getTextureCoordsAttrib() { 68 | return textureCoordsAttrib; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/src/test/java/pl/huczeq/rtspplayer/domain/cameragenerator/ModelGeneratorForTests.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.domain.cameragenerator; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import pl.huczeq.rtspplayer.data.model.urltemplates.Model; 7 | import pl.huczeq.rtspplayer.data.model.urltemplates.Producer; 8 | import pl.huczeq.rtspplayer.data.model.urltemplates.UrlTemplate; 9 | import pl.huczeq.rtspplayer.domain.model.CameraGroupModel; 10 | import pl.huczeq.rtspplayer.domain.urlgenerator.UrlComponentsProvider; 11 | import pl.huczeq.rtspplayer.domain.urlgenerator.UrlGenerator; 12 | 13 | public class ModelGeneratorForTests { 14 | 15 | public static CameraGroupModel cameraGroupGeneratorWithoutUrlTemplate() { 16 | CameraGroupModel cameraGroupModel = new CameraGroupModel(); 17 | cameraGroupModel.setName("1"); 18 | cameraGroupModel.setProducer(null); 19 | cameraGroupModel.setProducer(null); 20 | cameraGroupModel.setUserName("user"); 21 | cameraGroupModel.setPassword("password"); 22 | cameraGroupModel.setAddressIp("127.0.0.1"); 23 | cameraGroupModel.setPort("554"); 24 | cameraGroupModel.setServerUrl("/Stream"); 25 | cameraGroupModel.setChannel("1"); 26 | cameraGroupModel.setStreamType(UrlTemplate.StreamType.MAIN_STREAM); 27 | cameraGroupModel.setServerUrl("/Stream"); 28 | cameraGroupModel.setUrl("rtsp://user:password@127.0.0.1:554/Stream"); 29 | return cameraGroupModel; 30 | } 31 | 32 | public static CameraGroupModel cameraGroupGeneratorWithUrlTemplate() { 33 | CameraGroupModel cameraGroupModel = new CameraGroupModel(); 34 | cameraGroupModel.setName("1"); 35 | cameraGroupModel.setProducer(generateProducerWithModel()); 36 | Model model = cameraGroupModel.getProducer().getModelList().get(0); 37 | cameraGroupModel.setModel(model); 38 | cameraGroupModel.setUserName("user"); 39 | cameraGroupModel.setPassword("password"); 40 | cameraGroupModel.setAddressIp("127.0.0.1"); 41 | cameraGroupModel.setPort("554"); 42 | cameraGroupModel.setServerUrl("/Stream"); 43 | cameraGroupModel.setChannel("1"); 44 | cameraGroupModel.setStreamType(UrlTemplate.StreamType.MAIN_STREAM); 45 | cameraGroupModel.setServerUrl("/Stream"); 46 | cameraGroupModel.setUrl(UrlGenerator.generate(model.getUrlTemplate(), UrlComponentsProvider.of(cameraGroupModel))); 47 | return cameraGroupModel; 48 | } 49 | 50 | private static Producer generateProducerWithModel() { 51 | UrlTemplate urlTemplate = new UrlTemplate("rtsp://{user}:{password}@{addressip}:{port}{serverurl}", "rtsp://{addressip}:{port}{serverurl}"); 52 | urlTemplate.addField(UrlTemplate.AdditionalFields.ServerUrl); 53 | 54 | Model model = new Model("M", urlTemplate); 55 | Producer producer = new Producer("P"); 56 | List modelList = new ArrayList<>(); 57 | modelList.add(model); 58 | producer.setModelList(modelList); 59 | return producer; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/ui/player/PlayerCameraViewModel.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer.ui.player; 2 | 3 | import android.graphics.Bitmap; 4 | import android.util.Log; 5 | 6 | import androidx.lifecycle.MediatorLiveData; 7 | import androidx.lifecycle.ViewModel; 8 | 9 | import java.util.Objects; 10 | 11 | import javax.inject.Inject; 12 | 13 | import io.reactivex.rxjava3.annotations.NonNull; 14 | import io.reactivex.rxjava3.core.CompletableObserver; 15 | import io.reactivex.rxjava3.disposables.Disposable; 16 | import io.reactivex.rxjava3.observers.DisposableCompletableObserver; 17 | import io.reactivex.rxjava3.observers.DisposableSingleObserver; 18 | import lombok.Builder; 19 | import lombok.Getter; 20 | import pl.huczeq.rtspplayer.data.model.CameraInstance; 21 | import pl.huczeq.rtspplayer.data.repositories.base.CameraRepository; 22 | import pl.huczeq.rtspplayer.data.repositories.base.CameraThumbnailRepository; 23 | import pl.huczeq.rtspplayer.domain.usecases.LoadCameraUseCase; 24 | import pl.huczeq.rtspplayer.domain.usecases.SaveCameraThumbnailUseCase; 25 | 26 | public class PlayerCameraViewModel extends ViewModel { 27 | 28 | private final MediatorLiveData streamParams = new MediatorLiveData<>(); 29 | private SaveCameraThumbnailUseCase saveCameraThumbnailUseCase; 30 | 31 | @Inject 32 | public PlayerCameraViewModel(SaveCameraThumbnailUseCase saveCameraThumbnailUseCase) { 33 | this.saveCameraThumbnailUseCase = saveCameraThumbnailUseCase; 34 | } 35 | 36 | @Override 37 | protected void onCleared() { 38 | super.onCleared(); 39 | this.saveCameraThumbnailUseCase.dispose(); 40 | } 41 | 42 | public void setStreamParams(Params newParams) { 43 | Objects.requireNonNull(newParams); 44 | 45 | if (this.streamParams.getValue() != null && newParams.equals(this.streamParams.getValue())) 46 | return; 47 | 48 | streamParams.setValue(newParams); 49 | } 50 | 51 | public void saveCameraThumbnail(Bitmap bitmap) { 52 | this.saveCameraThumbnailUseCase.execute(new SaveCameraThumbnailUseCase.Params(this.streamParams.getValue().cameraId, bitmap), new DisposableCompletableObserver() { 53 | @Override 54 | public void onComplete() {} 55 | 56 | @Override 57 | public void onError(@NonNull Throwable e) { 58 | e.printStackTrace(); 59 | } 60 | }); 61 | } 62 | 63 | public Params getStreamParams() { 64 | return this.streamParams.getValue(); 65 | } 66 | 67 | @Getter 68 | @Builder 69 | public static class Params { 70 | private String url; 71 | private boolean forceTcpEnabled; 72 | private Long cameraId; 73 | 74 | public boolean equals(Params params) { 75 | return this.cameraId != null && 76 | Objects.equals(this.cameraId, params.cameraId) && 77 | this.cameraId > 0 78 | || 79 | Objects.equals(this.url, params.url) && 80 | this.forceTcpEnabled == params.forceTcpEnabled; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/java/pl/huczeq/rtspplayer/AppThemeHelper.java: -------------------------------------------------------------------------------- 1 | package pl.huczeq.rtspplayer; 2 | 3 | import android.app.Activity; 4 | import android.app.Application; 5 | import android.content.Context; 6 | import android.os.Bundle; 7 | 8 | import androidx.annotation.NonNull; 9 | import androidx.annotation.Nullable; 10 | import androidx.appcompat.app.AppCompatDelegate; 11 | 12 | import com.google.android.material.color.DynamicColors; 13 | 14 | import java.util.ArrayList; 15 | 16 | import javax.inject.Inject; 17 | import javax.inject.Singleton; 18 | 19 | import dagger.hilt.android.qualifiers.ApplicationContext; 20 | 21 | @Singleton 22 | public class AppThemeHelper { 23 | 24 | private final Settings settings; 25 | 26 | private final ArrayList activities = new ArrayList<>(); 27 | 28 | @Inject 29 | public AppThemeHelper(@ApplicationContext Context context, Settings settings) { 30 | this.settings = settings; 31 | RtspPlayerApp app = RtspPlayerApp.get(context); 32 | app.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() { 33 | @Override 34 | public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle bundle) { 35 | activities.add(activity); 36 | if(settings.dynamicColorsEnabled()) 37 | DynamicColors.applyToActivityIfAvailable(activity); 38 | } 39 | 40 | @Override 41 | public void onActivityDestroyed(@NonNull Activity activity) { 42 | activities.remove(activity); 43 | } 44 | 45 | @Override 46 | public void onActivityStarted(@NonNull Activity activity) {} 47 | @Override 48 | public void onActivityResumed(@NonNull Activity activity) {} 49 | @Override 50 | public void onActivityPaused(@NonNull Activity activity) {} 51 | @Override 52 | public void onActivityStopped(@NonNull Activity activity) {} 53 | @Override 54 | public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle bundle) {} 55 | }); 56 | } 57 | 58 | public void applyDarkLightTheme() { 59 | int newNightMode = AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM; 60 | switch (settings.getTheme()) { 61 | case Settings.Theme.DARK: 62 | newNightMode = AppCompatDelegate.MODE_NIGHT_YES; 63 | break; 64 | case Settings.Theme.LIGHT: 65 | newNightMode = AppCompatDelegate.MODE_NIGHT_NO; 66 | break; 67 | case Settings.Theme.FOLLOW_SYSTEM: 68 | break; 69 | } 70 | if(newNightMode != AppCompatDelegate.getDefaultNightMode()) 71 | AppCompatDelegate.setDefaultNightMode(newNightMode); 72 | } 73 | 74 | public void updateActivitiesTheme() { 75 | for(Activity activity : activities) { 76 | activity.setTheme(R.style.AppTheme); 77 | activity.recreate(); 78 | if(settings.dynamicColorsEnabled()) 79 | DynamicColors.applyToActivityIfAvailable(activity); 80 | } 81 | } 82 | } --------------------------------------------------------------------------------