diff --git a/app/build.gradle b/app/build.gradle index d8bdeac..5a4ac20 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -46,4 +46,12 @@ dependencies { testImplementation 'junit:junit:4.+' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + + //GSYVideoPlayer + implementation 'com.shuyu:gsyVideoPlayer-java:8.1.2' + //是否需要ExoPlayer模式 + implementation 'com.shuyu:GSYVideoPlayer-exo2:8.1.2' + //更多ijk的编码支持 + implementation 'com.shuyu:gsyVideoPlayer-ex_so:8.1.2' + } \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 481bb43..3f6b7fa 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -18,4 +18,23 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile + + +-keep class com.shuyu.gsyvideoplayer.video.** { *; } +-dontwarn com.shuyu.gsyvideoplayer.video.** +-keep class com.shuyu.gsyvideoplayer.video.base.** { *; } +-dontwarn com.shuyu.gsyvideoplayer.video.base.** +-keep class com.shuyu.gsyvideoplayer.utils.** { *; } +-dontwarn com.shuyu.gsyvideoplayer.utils.** +-keep class tv.danmaku.ijk.** { *; } +-dontwarn tv.danmaku.ijk.** + +-keep public class * extends android.view.View{ + *** get*(); + void set*(***); + public (android.content.Context); + public (android.content.Context, java.lang.Boolean); + public (android.content.Context, android.util.AttributeSet); + public (android.content.Context, android.util.AttributeSet, int); +} \ No newline at end of file diff --git a/app/src/main/java/it/danieleverducci/ojo/Settings.java b/app/src/main/java/it/danieleverducci/ojo/Settings.java index d145338..30469ae 100644 --- a/app/src/main/java/it/danieleverducci/ojo/Settings.java +++ b/app/src/main/java/it/danieleverducci/ojo/Settings.java @@ -13,6 +13,7 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.List; +import java.util.function.Predicate; import it.danieleverducci.ojo.entities.Camera; @@ -49,6 +50,16 @@ public List getCameras() { return cameras; } + public List getEnabledCameras() { + List result = new ArrayList<>(); + for (int i = 0; i < cameras.size(); i++) { + if (cameras.get(i).getEnable() == 1) { + result.add(cameras.get(i)); + } + } + return result; + } + public void setCameras(List cameras) { this.cameras = cameras; } diff --git a/app/src/main/java/it/danieleverducci/ojo/SharedPreferencesManager.java b/app/src/main/java/it/danieleverducci/ojo/SharedPreferencesManager.java index 02ab136..ba51212 100644 --- a/app/src/main/java/it/danieleverducci/ojo/SharedPreferencesManager.java +++ b/app/src/main/java/it/danieleverducci/ojo/SharedPreferencesManager.java @@ -3,16 +3,48 @@ import android.content.Context; import android.content.SharedPreferences; +import it.danieleverducci.ojo.ui.videoplayer.VideoLibEnum; + public class SharedPreferencesManager { + private static final String SP_FILE = "sp_file_name";//sharedPreference's name + private static final String SP_ROTATION_ENABLED = "rot_en"; + private static final String USE_WHICH_VIDEO_LIB = "USE_WHICH_VIDEO_LIB"; public static void saveRotationEnabled(Context ctx, boolean enabled) { - SharedPreferences sharedPref = ctx.getSharedPreferences(SP_ROTATION_ENABLED, Context.MODE_PRIVATE); + SharedPreferences sharedPref = ctx.getSharedPreferences(SP_FILE, Context.MODE_PRIVATE); sharedPref.edit().putBoolean(SP_ROTATION_ENABLED, enabled).apply(); } public static boolean loadRotationEnabled(Context ctx) { - SharedPreferences sharedPref = ctx.getSharedPreferences(SP_ROTATION_ENABLED, Context.MODE_PRIVATE); + SharedPreferences sharedPref = ctx.getSharedPreferences(SP_FILE, Context.MODE_PRIVATE); return sharedPref.getBoolean(SP_ROTATION_ENABLED, false); } + + /** + * 1:exo + * 2:vlc + * 3:ijk + * 4:system + * + * @param ctx + * @param videoLibEnum + */ + public static void saveUseWhichLib(Context ctx, VideoLibEnum videoLibEnum) { + SharedPreferences sharedPref = ctx.getSharedPreferences(SP_FILE, Context.MODE_PRIVATE); + sharedPref.edit().putInt(USE_WHICH_VIDEO_LIB, videoLibEnum.i).apply(); + } + + /** + * @param ctx + * @return + * 1:exo + * 2:vlc + * 3:ijk + * 4:system + */ + public static int useWhichLib(Context ctx) { + SharedPreferences sharedPref = ctx.getSharedPreferences(SP_FILE, Context.MODE_PRIVATE); + return sharedPref.getInt(USE_WHICH_VIDEO_LIB, 1); + } } diff --git a/app/src/main/java/it/danieleverducci/ojo/entities/Camera.java b/app/src/main/java/it/danieleverducci/ojo/entities/Camera.java index cade761..71c38ea 100644 --- a/app/src/main/java/it/danieleverducci/ojo/entities/Camera.java +++ b/app/src/main/java/it/danieleverducci/ojo/entities/Camera.java @@ -6,6 +6,7 @@ public class Camera implements Serializable { private static final long serialVersionUID = -3837361587400158910L; private String name; private String rtspUrl; + private int enable =1; //启用: 1 ; 关闭: 0 public Camera(String name, String rtspUrl) { this.name = name; @@ -27,4 +28,16 @@ public String getName() { public String getRtspUrl() { return rtspUrl; } + + public int getEnable() { + return enable; + } + + /** + * + * @param enable 1:enable; 0:disable + */ + public void setEnable(int enable) { + this.enable = enable; + } } diff --git a/app/src/main/java/it/danieleverducci/ojo/ui/MainActivity.java b/app/src/main/java/it/danieleverducci/ojo/ui/MainActivity.java index f0df45c..7f0adb2 100644 --- a/app/src/main/java/it/danieleverducci/ojo/ui/MainActivity.java +++ b/app/src/main/java/it/danieleverducci/ojo/ui/MainActivity.java @@ -8,9 +8,11 @@ import android.util.Log; import android.view.View; -import androidx.fragment.app.Fragment; import androidx.navigation.NavController; import androidx.navigation.Navigation; +import androidx.navigation.fragment.NavHostFragment; + +import com.shuyu.gsyvideoplayer.GSYVideoManager; import it.danieleverducci.ojo.R; import it.danieleverducci.ojo.SharedPreferencesManager; @@ -35,7 +37,11 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(binding.getRoot()); // Show FAB only on first fragment - navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main); + NavHostFragment navHostFragment = + (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment_content_main); + assert navHostFragment != null; + navController = navHostFragment.getNavController(); + navController.addOnDestinationChangedListener((controller, destination, arguments) -> { if (destination.getId() == R.id.HomeFragment) binding.fab.show(); @@ -59,7 +65,29 @@ public void setOnBackButtonPressedListener(OnBackButtonPressedListener onBackBut public void onBackPressed() { if (this.onBackButtonPressedListener != null && this.onBackButtonPressedListener.onBackPressed()) return; + if (GSYVideoManager.backFromWindowFull(this)) { + return; + } super.onBackPressed(); + + } + + @Override + protected void onPause() { + super.onPause(); + GSYVideoManager.onPause(); + } + + @Override + protected void onResume() { + super.onResume(); + GSYVideoManager.onResume(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + GSYVideoManager.releaseAllVideos(); } public void navigateToFragment(int actionId) { diff --git a/app/src/main/java/it/danieleverducci/ojo/ui/SettingsFragment.java b/app/src/main/java/it/danieleverducci/ojo/ui/SettingsFragment.java index 3dfae32..ee48182 100644 --- a/app/src/main/java/it/danieleverducci/ojo/ui/SettingsFragment.java +++ b/app/src/main/java/it/danieleverducci/ojo/ui/SettingsFragment.java @@ -15,6 +15,7 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.RadioGroup; import java.util.List; @@ -24,6 +25,7 @@ import it.danieleverducci.ojo.databinding.FragmentSettingsItemListBinding; import it.danieleverducci.ojo.entities.Camera; import it.danieleverducci.ojo.ui.adapters.SettingsRecyclerViewAdapter; +import it.danieleverducci.ojo.ui.videoplayer.VideoLibEnum; import it.danieleverducci.ojo.utils.ItemMoveCallback; /** @@ -35,7 +37,7 @@ public class SettingsFragment extends Fragment { private Settings settings; @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { binding = FragmentSettingsItemListBinding.inflate(inflater, container, false); @@ -70,6 +72,33 @@ public boolean onMenuItemClick(MenuItem item) { return false; } }); + + binding.radioGroup.clearCheck(); + int whichlib = SharedPreferencesManager.useWhichLib(this.getActivity()); + if (whichlib == 1) { + binding.exoR.setChecked(true); + } else if (whichlib == 2) + binding.vlcR.setChecked(true); + else if (whichlib == 3) + binding.ijkR.setChecked(true); + else if (whichlib ==4) + binding.sysR.setChecked(true); + + binding.radioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(RadioGroup group, int checkedId) { + if (checkedId == R.id.exo_r) { + SharedPreferencesManager.saveUseWhichLib(requireContext(), VideoLibEnum.EXO); + } else if (checkedId == R.id.vlc_r) { + SharedPreferencesManager.saveUseWhichLib(requireContext(), VideoLibEnum.VLC); + } else if (checkedId == R.id.ijk_r) { + SharedPreferencesManager.saveUseWhichLib(requireContext(), VideoLibEnum.IJK); + }else if (checkedId==R.id.sys_r){ + SharedPreferencesManager.saveUseWhichLib(requireContext(), VideoLibEnum.SYSTEM); + } + } + }); + } @Override diff --git a/app/src/main/java/it/danieleverducci/ojo/ui/StreamUrlFragment.java b/app/src/main/java/it/danieleverducci/ojo/ui/StreamUrlFragment.java index 75a2a27..4809965 100644 --- a/app/src/main/java/it/danieleverducci/ojo/ui/StreamUrlFragment.java +++ b/app/src/main/java/it/danieleverducci/ojo/ui/StreamUrlFragment.java @@ -29,12 +29,12 @@ public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Load existing settings (if any) - settings = Settings.fromDisk(getContext()); + settings = Settings.fromDisk(requireContext()); } @Override public View onCreateView( - LayoutInflater inflater, ViewGroup container, + @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState ) { @@ -47,7 +47,7 @@ public View onCreateView( Camera c = settings.getCameras().get(this.selectedCamera); binding.streamName.setText(c.getName()); - binding.streamName.setHint(getContext().getString(R.string.stream_list_default_camera_name).replace("{camNo}", (this.selectedCamera+1)+"")); + binding.streamName.setHint(requireContext().getString(R.string.stream_list_default_camera_name).replace("{camNo}", (this.selectedCamera+1)+"")); binding.streamUrl.setText(c.getRtspUrl()); } diff --git a/app/src/main/java/it/danieleverducci/ojo/ui/SurveillanceFragment.java b/app/src/main/java/it/danieleverducci/ojo/ui/SurveillanceFragment.java index a6141a8..444b06c 100644 --- a/app/src/main/java/it/danieleverducci/ojo/ui/SurveillanceFragment.java +++ b/app/src/main/java/it/danieleverducci/ojo/ui/SurveillanceFragment.java @@ -1,37 +1,36 @@ package it.danieleverducci.ojo.ui; -import android.content.Context; -import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.util.Log; import android.view.LayoutInflater; -import android.view.SurfaceHolder; -import android.view.SurfaceView; import android.view.View; import android.view.ViewGroup; -import android.view.ViewTreeObserver; import android.view.Window; import android.view.WindowInsets; import android.view.WindowInsetsController; import android.widget.LinearLayout; +import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; -import org.videolan.libvlc.IVLCVout; -import org.videolan.libvlc.LibVLC; -import org.videolan.libvlc.Media; -import org.videolan.libvlc.MediaPlayer; +import com.shuyu.gsyvideoplayer.player.IjkPlayerManager; +import com.shuyu.gsyvideoplayer.player.PlayerFactory; +import com.shuyu.gsyvideoplayer.player.SystemPlayerManager; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import it.danieleverducci.ojo.R; import it.danieleverducci.ojo.Settings; +import it.danieleverducci.ojo.SharedPreferencesManager; import it.danieleverducci.ojo.databinding.FragmentSurveillanceBinding; import it.danieleverducci.ojo.entities.Camera; +import it.danieleverducci.ojo.ui.videoplayer.BaseCameraView; +import it.danieleverducci.ojo.ui.videoplayer.vlc.VlcCameraView; +import it.danieleverducci.ojo.ui.videoplayer.gsy.GsyCameraView; +import it.danieleverducci.ojo.ui.videoplayer.VideoLibEnum; import it.danieleverducci.ojo.utils.DpiUtils; +import tv.danmaku.ijk.media.exo2.Exo2PlayerManager; /** * Some streams to test: @@ -39,19 +38,12 @@ * rtsp://demo:demo@ipvmdemo.dyndns.org:5541/onvif-media/media.amp?profile=profile_1_h264&sessiontimeout=60&streamtype=unicast */ public class SurveillanceFragment extends Fragment { + public static VideoLibEnum videoLibEnum = VideoLibEnum.EXO; - final static private String TAG = "SurveillanceFragment"; - final static private String[] VLC_OPTIONS = new String[]{ - "--aout=opensles", - //"--audio-time-stretch", // time stretching - //"-vvv", // verbosity - "--avcodec-codec=h264", - //"--file-logging", - //"--logfile=vlc-log.txt" - }; + public final static String TAG = "SurveillanceFragment"; private FragmentSurveillanceBinding binding; - private List cameraViews = new ArrayList<>(); + private final List cameraViews = new ArrayList<>(); private boolean fullscreenCameraView = false; private LinearLayout.LayoutParams cameraViewLayoutParams; private LinearLayout.LayoutParams rowLayoutParams; @@ -59,16 +51,16 @@ public class SurveillanceFragment extends Fragment { @Override public View onCreateView( - LayoutInflater inflater, ViewGroup container, + @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState ) { - int viewMargin = DpiUtils.DpToPixels(container.getContext(), 2); + int viewMargin = DpiUtils.DpToPixels(requireContext(), 2); cameraViewLayoutParams = new LinearLayout.LayoutParams( 0, LinearLayout.LayoutParams.MATCH_PARENT, 1.0f ); - cameraViewLayoutParams.setMargins(viewMargin,viewMargin,viewMargin,viewMargin); + cameraViewLayoutParams.setMargins(viewMargin, viewMargin, viewMargin, viewMargin); rowLayoutParams = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, @@ -82,12 +74,34 @@ public View onCreateView( return binding.getRoot(); } + @Override + public void onStart() { + super.onStart(); + setUseWhichVideoPlayer(); + } + + private void setUseWhichVideoPlayer() { + int whichlib = SharedPreferencesManager.useWhichLib(requireContext()); + if (whichlib == VideoLibEnum.EXO.i) { + videoLibEnum = VideoLibEnum.EXO; + PlayerFactory.setPlayManager(Exo2PlayerManager.class); + } else if (whichlib == VideoLibEnum.VLC.i) { + videoLibEnum = VideoLibEnum.VLC; + } else if (whichlib == VideoLibEnum.IJK.i) { + videoLibEnum = VideoLibEnum.IJK; + PlayerFactory.setPlayManager(IjkPlayerManager.class); + } else if (whichlib == VideoLibEnum.SYSTEM.i) { + videoLibEnum = VideoLibEnum.SYSTEM; + PlayerFactory.setPlayManager(SystemPlayerManager.class); + } + } + @Override public void onResume() { super.onResume(); // Leanback mode (fullscreen) - Window window = getActivity().getWindow(); + Window window = requireActivity().getWindow(); if (window != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { final WindowInsetsController controller = window.getInsetsController(); @@ -109,15 +123,15 @@ public void onResume() { addAllCameras(); // Start playback for all streams - for (CameraView cv : cameraViews) { + for (BaseCameraView cv : cameraViews) { cv.startPlayback(); } // Register for back pressed events - ((MainActivity)getActivity()).setOnBackButtonPressedListener(new OnBackButtonPressedListener() { + ((MainActivity) requireActivity()).setOnBackButtonPressedListener(new OnBackButtonPressedListener() { @Override public boolean onBackPressed() { - if(fullscreenCameraView && cameraViews.size() > 1) { + if (fullscreenCameraView && cameraViews.size() > 1) { fullscreenCameraView = false; showAllCameras(); return true; @@ -132,7 +146,7 @@ public void onPause() { super.onPause(); // Disable Leanback mode (fullscreen) - Window window = getActivity().getWindow(); + Window window = requireActivity().getWindow(); if (window != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { final WindowInsetsController controller = window.getInsetsController(); @@ -150,28 +164,28 @@ public void onPause() { private void addAllCameras() { - Settings settings = Settings.fromDisk(getContext()); - List cc = settings.getCameras(); + Settings settings = Settings.fromDisk(requireActivity()); + List cc = settings.getEnabledCameras(); int[] gridSize = calcGridDimensionsBasedOnNumberOfElements(cc.size()); int camIdx = 0; for (int r = 0; r < gridSize[0]; r++) { // Create row and add to row container - LinearLayout row = new LinearLayout(getContext()); + LinearLayout row = new LinearLayout(getContext());//几行 binding.gridRowContainer.addView(row, rowLayoutParams); // Add camera viewers to the row for (int c = 0; c < gridSize[1]; c++) { - if ( camIdx < cc.size() ) { + if (camIdx < cc.size()) { Camera cam = cc.get(camIdx); - CameraView cv = addCameraView(cam, row); + BaseCameraView cv = addCameraView(cam, row);//几列 cv.startPlayback(); - cv.setOnClickListener(new View.OnClickListener() { + cv.fullScreen(new BaseCameraView.FullEvent() { @Override - public void onClick(View v) { + public void fullOrNot(BaseCameraView baseCameraView) { // Toggle single/multi camera views fullscreenCameraView = !fullscreenCameraView; if (fullscreenCameraView) { - hideAllCameraViewsButNot(v); + hideAllCameraViewsButNot(baseCameraView); } else { showAllCameras(); } @@ -190,7 +204,7 @@ public void onClick(View v) { private void disposeAllCameras() { // Destroy players, libs etc - for (CameraView cv : cameraViews) { + for (BaseCameraView cv : cameraViews) { cv.destroy(); } cameraViews.clear(); @@ -198,23 +212,36 @@ private void disposeAllCameras() { binding.gridRowContainer.removeAllViews(); } - protected void hideAllCameraViewsButNot(View cameraView) { + public void hideAllCameraViewsButNot(BaseCameraView baseCameraView) { + View cameraView; + if (baseCameraView.kind == VideoLibEnum.VLC) + cameraView = baseCameraView.surfaceView; + else + cameraView = baseCameraView.gsyVideoPlayer; + + for (BaseCameraView cm : cameraViews) {//stop other VideoView + if (cm != baseCameraView) { + cm.stop(); + } + } + for (int i = 0; i < binding.gridRowContainer.getChildCount(); i++) { LinearLayout row = (LinearLayout) binding.gridRowContainer.getChildAt(i); boolean emptyRow = true; for (int j = 0; j < row.getChildCount(); j++) { View cam = row.getChildAt(j); - if (cameraView == cam) + if (cameraView == cam) { emptyRow = false; - else + } else { cam.setLayoutParams(hiddenLayoutParams); + } } if (emptyRow) row.setLayoutParams(hiddenLayoutParams); } } - protected void showAllCameras() { + public void showAllCameras() { for (int i = 0; i < binding.gridRowContainer.getChildCount(); i++) { LinearLayout row = (LinearLayout) binding.gridRowContainer.getChildAt(i); row.setLayoutParams(rowLayoutParams); @@ -222,18 +249,38 @@ protected void showAllCameras() { View cam = row.getChildAt(j); cam.setLayoutParams(cameraViewLayoutParams); } + for (BaseCameraView cameraView : cameraViews) { + cameraView.startPlayback(); + } } } - private CameraView addCameraView(Camera camera, LinearLayout rowContainer) { - CameraView cv = new CameraView( - getContext(), - camera - ); - + /** + * 生成vlc版本的视频播放 + */ + private VlcCameraView genVlc(Camera camera, LinearLayout rowContainer) { + VlcCameraView cv = new VlcCameraView(requireActivity(), camera); // Add to layout rowContainer.addView(cv.surfaceView, cameraViewLayoutParams); + return cv; + } + /** + * 生成gsy版本的视频播放 + */ + private BaseCameraView genGsy(Camera camera, LinearLayout rowContainer) { + GsyCameraView cv = new GsyCameraView(requireActivity(), camera); + rowContainer.addView(cv.gsyVideoPlayer, cameraViewLayoutParams); + return cv; + } + private BaseCameraView addCameraView(Camera camera, LinearLayout rowContainer) { + BaseCameraView cv; + if (videoLibEnum.i != VideoLibEnum.VLC.i) { + cv = genGsy(camera, rowContainer); + } else { + cv = genVlc(camera, rowContainer); + } + cv.kind = videoLibEnum; cameraViews.add(cv); return cv; } @@ -243,6 +290,7 @@ private CameraView addCameraView(Camera camera, LinearLayout rowContainer) { * Es: to display 3 elements is needed a 4-element grid, with 2 elements per side (a 2x2 grid) * Es: to display 6 elements is needed a 9-element grid, with 3 elements per side (a 2x3 grid) * Es: to display 7 elements is needed a 9-element grid, with 3 elements per side (a 3x3 grid) + * * @param elements */ private int[] calcGridDimensionsBasedOnNumberOfElements(int elements) { @@ -253,82 +301,6 @@ private int[] calcGridDimensionsBasedOnNumberOfElements(int elements) { if (rows * cols >= elements) break; rows += 1; } - int[] dimensions = {rows, cols}; - return dimensions; - } - - /** - * Contains all entities (views and java entities) related to a camera stream viewer - */ - private class CameraView { - protected SurfaceView surfaceView; - protected MediaPlayer mediaPlayer; - protected IVLCVout ivlcVout; - protected Camera camera; - protected LibVLC libvlc; - - public CameraView(Context context, Camera camera) { - this.camera = camera; - this.libvlc = new LibVLC(context, new ArrayList<>(Arrays.asList(VLC_OPTIONS))); - - surfaceView = new SurfaceView(context); - surfaceView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - - } - }); - SurfaceHolder holder = surfaceView.getHolder(); - - holder.setKeepScreenOn(true); - - // Create media player - mediaPlayer = new MediaPlayer(libvlc); - - // Set up video output - ivlcVout = mediaPlayer.getVLCVout(); - ivlcVout.setVideoView(surfaceView); - ivlcVout.attachViews(); - - // Load media and start playing - Media m = new Media(libvlc, Uri.parse(camera.getRtspUrl())); - mediaPlayer.setMedia(m); - - // Register for view resize events - final ViewTreeObserver observer= surfaceView.getViewTreeObserver(); - observer.addOnGlobalLayoutListener(() -> { - // Set rendering size - ivlcVout.setWindowSize(surfaceView.getWidth(), surfaceView.getHeight()); - }); - } - - public void setOnClickListener(View.OnClickListener listener) { - surfaceView.setOnClickListener(listener); - } - - /** - * Starts the playback. - */ - public void startPlayback() { - mediaPlayer.play(); - } - - /** - * Destroys the object and frees the memory - */ - public void destroy() { - if (libvlc == null) { - Log.e(TAG, this.toString() + " already destroyed"); - return; - } - - mediaPlayer.stop(); - final IVLCVout vout = mediaPlayer.getVLCVout(); - vout.detachViews(); - libvlc.release(); - libvlc = null; - mediaPlayer.release(); - mediaPlayer = null; - } + return new int[]{rows, cols}; } } \ No newline at end of file diff --git a/app/src/main/java/it/danieleverducci/ojo/ui/adapters/SettingsRecyclerViewAdapter.java b/app/src/main/java/it/danieleverducci/ojo/ui/adapters/SettingsRecyclerViewAdapter.java index e27bf46..4c8ac65 100644 --- a/app/src/main/java/it/danieleverducci/ojo/ui/adapters/SettingsRecyclerViewAdapter.java +++ b/app/src/main/java/it/danieleverducci/ojo/ui/adapters/SettingsRecyclerViewAdapter.java @@ -1,11 +1,13 @@ package it.danieleverducci.ojo.ui.adapters; +import androidx.appcompat.widget.SwitchCompat; import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.widget.CompoundButton; import android.widget.TextView; import it.danieleverducci.ojo.R; @@ -46,9 +48,10 @@ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { public void onBindViewHolder(final ViewHolder holder, int position) { String cameraName = mValues.get(position).getName(); if (cameraName == null || cameraName.length() == 0) - cameraName = holder.name.getContext().getString(R.string.stream_list_default_camera_name).replace("{camNo}", (position+1)+""); + cameraName = holder.name.getContext().getString(R.string.stream_list_default_camera_name).replace("{camNo}", (position + 1) + ""); holder.name.setText(cameraName); holder.url.setText(mValues.get(position).getRtspUrl()); + holder.enableSwitch.setChecked(mValues.get(position).getEnable() == 1); holder.root.setOnClickListener(new View.OnClickListener() { @Override @@ -56,13 +59,18 @@ public void onClick(View view) { clickListener.onItemClick(holder.getBindingAdapterPosition()); } }); - - holder.deleteButton.setOnClickListener(new View.OnClickListener() { + setItemClick(new View.OnClickListener() { @Override public void onClick(View view) { mValues.remove(holder.getBindingAdapterPosition()); notifyItemRemoved(holder.getBindingAdapterPosition()); } + }, holder.deleteButton); + holder.enableSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + mValues.get(holder.getBindingAdapterPosition()).setEnable(isChecked ? 1 : 0); + } }); } @@ -101,6 +109,7 @@ public void onRowClear(RecyclerView.ViewHolder myViewHolder) { public void setOnDragListener(OnDragListener dragListener) { this.dragListener = dragListener; } + public void setOnClickListener(OnClickListener clickListener) { this.clickListener = clickListener; } @@ -115,6 +124,7 @@ public class ViewHolder extends RecyclerView.ViewHolder { public TextView url; public View deleteButton; public View dragHandle; + public SwitchCompat enableSwitch; public ViewHolder(FragmentSettingsItemBinding binding) { super(binding.getRoot()); @@ -124,6 +134,7 @@ public ViewHolder(FragmentSettingsItemBinding binding) { this.url = binding.cameraUrl; this.deleteButton = binding.cameraDelete; this.dragHandle = binding.cameraDragHandle; + this.enableSwitch = binding.enableS; } } @@ -131,6 +142,14 @@ public interface OnDragListener { void onItemDrag(ViewHolder vh); } + public void setItemClick(View.OnClickListener listener, View... view) { + if (view.length > 0) { + for (View value : view) { + value.setOnClickListener(listener); + } + } + } + public interface OnClickListener { void onItemClick(int pos); } diff --git a/app/src/main/java/it/danieleverducci/ojo/ui/videoplayer/BaseCameraView.java b/app/src/main/java/it/danieleverducci/ojo/ui/videoplayer/BaseCameraView.java new file mode 100644 index 0000000..94d53e6 --- /dev/null +++ b/app/src/main/java/it/danieleverducci/ojo/ui/videoplayer/BaseCameraView.java @@ -0,0 +1,45 @@ +package it.danieleverducci.ojo.ui.videoplayer; + +import android.view.SurfaceView; +import android.view.View; + +import androidx.annotation.Nullable; +import androidx.fragment.app.FragmentActivity; + +import com.shuyu.gsyvideoplayer.video.base.GSYVideoPlayer; + +import it.danieleverducci.ojo.entities.Camera; + +public abstract class BaseCameraView { + public Camera camera; + public SurfaceView surfaceView; + public GSYVideoPlayer gsyVideoPlayer = null; + public VideoLibEnum kind; + + public BaseCameraView(FragmentActivity context, Camera camera) { + this.camera = camera; + } + + /** + * Starts the playback. + */ + public abstract void startPlayback(); + + public abstract void pause(); + + public abstract void resume(); + + public abstract void stop(); + + /** + * Destroys the object and frees the memory + */ + public abstract void destroy(); + + public abstract void fullScreen(@Nullable FullEvent fullEvent); + + public interface FullEvent { + void fullOrNot(BaseCameraView cameraView); + } + +} diff --git a/app/src/main/java/it/danieleverducci/ojo/ui/videoplayer/VideoLibEnum.java b/app/src/main/java/it/danieleverducci/ojo/ui/videoplayer/VideoLibEnum.java new file mode 100644 index 0000000..82f0f41 --- /dev/null +++ b/app/src/main/java/it/danieleverducci/ojo/ui/videoplayer/VideoLibEnum.java @@ -0,0 +1,29 @@ +package it.danieleverducci.ojo.ui.videoplayer; + +public enum VideoLibEnum { + EXO(1), VLC(2), IJK(3), SYSTEM(4); + + public int i = 1; + + VideoLibEnum(int i) { + this.i = i; + } + + public static int parse(VideoLibEnum videoLibEnum) { + return videoLibEnum.i; + } + + public static VideoLibEnum getEnum(int i) { + switch (i) { + case 2: + return VLC; + case 3: + return IJK; + case 4: + return SYSTEM; + case 1: + default: + return EXO; + } + } +} diff --git a/app/src/main/java/it/danieleverducci/ojo/ui/videoplayer/gsy/CustomManager.java b/app/src/main/java/it/danieleverducci/ojo/ui/videoplayer/gsy/CustomManager.java new file mode 100644 index 0000000..abb364f --- /dev/null +++ b/app/src/main/java/it/danieleverducci/ojo/ui/videoplayer/gsy/CustomManager.java @@ -0,0 +1,194 @@ +package it.danieleverducci.ojo.ui.videoplayer.gsy; + +import android.app.Activity; +import android.content.Context; +import android.text.TextUtils; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; + +import com.shuyu.gsyvideoplayer.GSYVideoBaseManager; +import com.shuyu.gsyvideoplayer.player.IPlayerManager; +import com.shuyu.gsyvideoplayer.player.IjkPlayerManager; +import com.shuyu.gsyvideoplayer.utils.CommonUtil; +import com.shuyu.gsyvideoplayer.utils.GSYVideoType; +import com.shuyu.gsyvideoplayer.video.base.GSYVideoPlayer; + +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + +import static com.shuyu.gsyvideoplayer.utils.CommonUtil.hideNavKey; + +import static it.danieleverducci.ojo.ui.videoplayer.gsy.GsyConfig.options; + +import it.danieleverducci.ojo.R; + +/** + * 多个播放的管理器 + */ + +public class CustomManager extends GSYVideoBaseManager { + public static final Random random = new Random(); + + public static final int SMALL_ID = R.id.custom_small_id; + + public static final int FULLSCREEN_ID = R.id.custom_full_id; + + public static String TAG = "GSYVideoManager"; + + private static Map sMap = new HashMap<>(); + + + public CustomManager() { + init(); + setOptionModelList(options()); + } + + @Override + protected IPlayerManager getPlayManager() { + return new IjkPlayerManager(); + } + + /** + * 退出全屏,主要用于返回键 + * + * @return 返回是否全屏 + */ + @SuppressWarnings("ResourceType") + public static boolean backFromWindowFull(Context context, String key) { + boolean backFrom = false; + ViewGroup vp = (ViewGroup) (CommonUtil.scanForActivity(context)).findViewById(Window.ID_ANDROID_CONTENT); + View oldF = vp.findViewById(FULLSCREEN_ID); + if (oldF != null) { + backFrom = true; + hideNavKey(context); + if (getCustomManager(key).lastListener() != null) { + getCustomManager(key).lastListener().onBackFullscreen(); + } + } + return backFrom; + } + + /** + * 页面销毁了记得调用是否所有的video + */ + public static void releaseAllVideos(String key) { + if (getCustomManager(key).listener() != null) { + getCustomManager(key).listener().onCompletion(); + } + getCustomManager(key).releaseMediaPlayer(); + } + + + /** + * 暂停播放 + */ + public void onPause(String key) { + if (getCustomManager(key).listener() != null) { + getCustomManager(key).listener().onVideoPause(); + } + } + + /** + * 恢复播放 + */ + public void onResume(String key) { + if (getCustomManager(key).listener() != null) { + getCustomManager(key).listener().onVideoResume(); + } + } + + + /** + * 恢复暂停状态 + * + * @param seek 是否产生seek动作,直播设置为false + */ + public void onResume(String key, boolean seek) { + if (getCustomManager(key).listener() != null) { + getCustomManager(key).listener().onVideoResume(seek); + } + } + + + /** + * 单例管理器 + */ + public static synchronized Map instance() { + return sMap; + } + + /** + * 单例管理器 + */ + public static synchronized CustomManager getCustomManager(String key) { + if (TextUtils.isEmpty(key)) { + throw new IllegalStateException("key not be empty"); + } + CustomManager customManager = sMap.get(key); + if (customManager == null) { + customManager = new CustomManager(); + sMap.put(key, customManager); + } + return customManager; + } + + public static void onPauseAll() { + if (sMap.size() > 0) { + for (Map.Entry header : sMap.entrySet()) { + header.getValue().onPause(header.getKey()); + } + } + } + + public static void onResumeAll() { + if (sMap.size() > 0) { + for (Map.Entry header : sMap.entrySet()) { + header.getValue().onResume(header.getKey()); + } + } + } + + /** + * 恢复暂停状态 + * + * @param seek 是否产生seek动作 + */ + public static void onResumeAll(boolean seek) { + if (sMap.size() > 0) { + for (Map.Entry header : sMap.entrySet()) { + header.getValue().onResume(header.getKey(), seek); + } + } + } + + public static void clearAllVideo() { + if (sMap.size() > 0) { + for (Map.Entry header : sMap.entrySet()) { + CustomManager.releaseAllVideos(header.getKey()); + } + } + sMap.clear(); + } + + public static void removeManager(String key) { + sMap.remove(key); + } + + /** + * 当前是否全屏状态 + * + * @return 当前是否全屏状态, true代表是。 + */ + @SuppressWarnings("ResourceType") + public static boolean isFullState(Activity activity) { + ViewGroup vp = (ViewGroup) (CommonUtil.scanForActivity(activity)).findViewById(Window.ID_ANDROID_CONTENT); + final View full = vp.findViewById(FULLSCREEN_ID); + GSYVideoPlayer gsyVideoPlayer = null; + if (full != null) { + gsyVideoPlayer = (GSYVideoPlayer) full; + } + return gsyVideoPlayer != null; + } +} diff --git a/app/src/main/java/it/danieleverducci/ojo/ui/videoplayer/gsy/GsyCameraView.java b/app/src/main/java/it/danieleverducci/ojo/ui/videoplayer/gsy/GsyCameraView.java new file mode 100644 index 0000000..a6a2883 --- /dev/null +++ b/app/src/main/java/it/danieleverducci/ojo/ui/videoplayer/gsy/GsyCameraView.java @@ -0,0 +1,85 @@ +package it.danieleverducci.ojo.ui.videoplayer.gsy; + +import static it.danieleverducci.ojo.ui.videoplayer.gsy.CustomManager.random; + +import android.view.View; + +import androidx.annotation.Nullable; +import androidx.fragment.app.FragmentActivity; + +import com.shuyu.gsyvideoplayer.builder.GSYVideoOptionBuilder; + +import java.util.UUID; + +import it.danieleverducci.ojo.R; +import it.danieleverducci.ojo.entities.Camera; +import it.danieleverducci.ojo.ui.videoplayer.BaseCameraView; +import it.danieleverducci.ojo.ui.videoplayer.VideoLibEnum; + +public class GsyCameraView extends BaseCameraView implements View.OnClickListener { + private FullEvent fullEvent = null; + + + public GsyCameraView(FragmentActivity context, Camera camera) { + super(context, camera); + kind = VideoLibEnum.EXO; + gsyVideoPlayer = new MultiSampleVideo(context); + //gsyVideoPlayer.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT, 1.0f)); + GSYVideoOptionBuilder gsyVideoOption = new GSYVideoOptionBuilder(); + gsyVideoOption + .setIsTouchWiget(false) + .setRotateViewAuto(true) + .setLockLand(true) + .setAutoFullWithSize(false) + .setShowFullAnimation(false) + .setNeedLockFull(true) + .setUrl(camera.getRtspUrl()) + .setCacheWithPlay(false) + .build(gsyVideoPlayer); + gsyVideoPlayer.setPlayTag(UUID.randomUUID().toString()); + gsyVideoPlayer.setPlayPosition(random.nextInt()); + + } + + @Override + public void startPlayback() { + gsyVideoPlayer.startPlayLogic(); + } + + @Override + public void pause() { + gsyVideoPlayer.onVideoPause(); + } + + @Override + public void resume() { + gsyVideoPlayer.onVideoResume(); + } + + @Override + public void stop() { + gsyVideoPlayer.release(); + } + + @Override + public void destroy() { + gsyVideoPlayer.release(); + } + + @Override + public void fullScreen(@Nullable FullEvent fullEvent) { + this.fullEvent = fullEvent; + gsyVideoPlayer.getBackButton().setOnClickListener(this); + gsyVideoPlayer.getFullscreenButton().setOnClickListener(this); + } + + @Override + public void onClick(View v) { + int id = v.getId(); + if (id == R.id.fullscreen) { + if (fullEvent != null) { + fullEvent.fullOrNot(GsyCameraView.this); + } + } + } +} diff --git a/app/src/main/java/it/danieleverducci/ojo/ui/videoplayer/gsy/GsyConfig.java b/app/src/main/java/it/danieleverducci/ojo/ui/videoplayer/gsy/GsyConfig.java new file mode 100644 index 0000000..8ce0c42 --- /dev/null +++ b/app/src/main/java/it/danieleverducci/ojo/ui/videoplayer/gsy/GsyConfig.java @@ -0,0 +1,71 @@ +package it.danieleverducci.ojo.ui.videoplayer.gsy; + +import com.shuyu.gsyvideoplayer.model.VideoOptionModel; + +import java.util.ArrayList; +import java.util.List; + +import tv.danmaku.ijk.media.player.IjkMediaPlayer; + +public class GsyConfig { + /** + * 优化参数 + * @return + */ + public static List options() { + //更多优化 + List list = new ArrayList<>(); + VideoOptionModel videoOptionMode01 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "fast", 1);//不额外优化 + list.add(videoOptionMode01); + VideoOptionModel videoOptionMode02 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "probesize", 200);// 播放前的探测Size,默认是1M(10240), 改小一点会出画面更快 + list.add(videoOptionMode02); + VideoOptionModel videoOptionMode03 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "flush_packets", 1);//每处理一个packet之后刷新io上下文 + list.add(videoOptionMode03); +//pause output until enough packets have been read after stalling + VideoOptionModel videoOptionMode04 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", 0);//是否开启缓冲 0关闭。一般直播项目会开启,达到秒开的效果,不过带来了播放丢帧卡顿的体验 + list.add(videoOptionMode04); +//drop frames when cpu is too slow:0-120 + VideoOptionModel videoOptionMode05 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", 1);//跳帧处理,放CPU处理较慢时,进行跳帧处理,保证播放流程,画面和声音同步 + list.add(videoOptionMode05); +//automatically start playing on prepared + VideoOptionModel videoOptionMode06 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", 1); + list.add(videoOptionMode06); + VideoOptionModel videoOptionMode07 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_CODEC, "skip_loop_filter", 48);//设置是否开启环路过滤: 0开启,画面质量高,解码开销大,48关闭,画面质量差点,解码开销小 + list.add(videoOptionMode07); +//max buffer size should be pre-read:默认为15*1024*1024 + VideoOptionModel videoOptionMode11 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max-buffer-size", 0);//最大缓冲大小,单位kb + list.add(videoOptionMode11); + VideoOptionModel videoOptionMode12 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "min-frames", 2);//默认最小帧数2 + list.add(videoOptionMode12); + VideoOptionModel videoOptionMode13 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max_cached_duration", 30);//最大缓存时长 + list.add(videoOptionMode13); +//input buffer:don't limit the input buffer size (useful with realtime streams) + VideoOptionModel videoOptionMode14 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "infbuf", 1);//是否限制输入缓存数 + list.add(videoOptionMode14); + VideoOptionModel videoOptionMode15 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "fflags", "nobuffer"); + list.add(videoOptionMode15); + VideoOptionModel videoOptionMode16 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "rtsp_transport", "tcp");//tcp传输数据 + list.add(videoOptionMode16); + VideoOptionModel videoOptionMode17 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "analyzedmaxduration", 100);//分析码流时长:默认1024*1000 + list.add(videoOptionMode17); + VideoOptionModel videoOptionModel18 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "dns_cache_clear", 1); + list.add(videoOptionModel18); + VideoOptionModel videoOptionModel19 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "dns_cache_timeout", -1); + list.add(videoOptionModel19); + VideoOptionModel videoOptionModel20 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "analyzeduration", 1);//设置播放前的探索时间 1:达到首屏秒开效果 + list.add(videoOptionModel20); + VideoOptionModel videoOptionModel21 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max-fps", 30); + list.add(videoOptionModel21); + VideoOptionModel videoOptionModel22 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "reconnect", 2);//播放重连次数 + list.add(videoOptionModel22); + VideoOptionModel videoOptionModel23 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-hevc", 1);//支持硬解 1:开启 0:关闭 + list.add(videoOptionModel23); + /*VideoOptionModel videoOptionModel24 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "fps", 24);//视频帧率 + list.add(videoOptionModel24);*/ + VideoOptionModel videoOptionModel25 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 1);//开启硬解//0:代表关闭;1:代表开启 + list.add(videoOptionModel25); + VideoOptionModel videoOptionModel26 = new VideoOptionModel(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-handle-resolution-change", 0);//处理分辨率变化 + list.add(videoOptionModel26); + return list; + } +} diff --git a/app/src/main/java/it/danieleverducci/ojo/ui/videoplayer/gsy/MultiSampleVideo.java b/app/src/main/java/it/danieleverducci/ojo/ui/videoplayer/gsy/MultiSampleVideo.java new file mode 100644 index 0000000..0ada2e3 --- /dev/null +++ b/app/src/main/java/it/danieleverducci/ojo/ui/videoplayer/gsy/MultiSampleVideo.java @@ -0,0 +1,137 @@ +package it.danieleverducci.ojo.ui.videoplayer.gsy; + +import android.content.Context; +import android.graphics.Point; +import android.media.AudioManager; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.widget.ImageView; + +import com.shuyu.gsyvideoplayer.utils.Debuger; +import com.shuyu.gsyvideoplayer.video.StandardGSYVideoPlayer; +import com.shuyu.gsyvideoplayer.video.base.GSYBaseVideoPlayer; +import com.shuyu.gsyvideoplayer.video.base.GSYVideoViewBridge; + +import it.danieleverducci.ojo.R; + + +/** + * 多个同时播放的播放控件 + */ + +public class MultiSampleVideo extends StandardGSYVideoPlayer { + + private final static String TAG = "MultiSampleVideo"; + + ImageView mCoverImage; + + String mCoverOriginUrl; + + int mDefaultRes; + + public MultiSampleVideo(Context context, Boolean fullFlag) { + super(context, fullFlag); + } + + public MultiSampleVideo(Context context) { + super(context); + } + + public MultiSampleVideo(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void init(Context context) { + super.init(context); + mCoverImage = (ImageView) findViewById(R.id.thumbImage); + if (mThumbImageViewLayout != null && + (mCurrentState == -1 || mCurrentState == CURRENT_STATE_NORMAL || mCurrentState == CURRENT_STATE_ERROR)) { + mThumbImageViewLayout.setVisibility(VISIBLE); + } + onAudioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() { + @Override + public void onAudioFocusChange(int focusChange) { + switch (focusChange) { + case AudioManager.AUDIOFOCUS_GAIN: + break; + case AudioManager.AUDIOFOCUS_LOSS: + //todo 判断如果不是外界造成的就不处理 + break; + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: + //todo 判断如果不是外界造成的就不处理 + break; + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: + break; + } + } + }; + } + + @Override + public GSYVideoViewBridge getGSYVideoManager() { + CustomManager m = CustomManager.getCustomManager(getKey()); + m.initContext(getContext().getApplicationContext()); + return CustomManager.getCustomManager(getKey()); + } + + @Override + protected boolean backFromFull(Context context) { + return CustomManager.backFromWindowFull(context, getKey()); + } + + @Override + protected void releaseVideos() { + CustomManager.releaseAllVideos(getKey()); + } + + + @Override + protected int getFullId() { + return CustomManager.FULLSCREEN_ID; + } + + @Override + protected int getSmallId() { + return CustomManager.SMALL_ID; + } + + + @Override + public int getLayoutId() { + return R.layout.video_layout_cover; + } + + public void loadCoverImage(String url, int res) { + mCoverOriginUrl = url; + mDefaultRes = res; + } + + @Override + public GSYBaseVideoPlayer startWindowFullscreen(Context context, boolean actionBar, boolean statusBar) { + GSYBaseVideoPlayer gsyBaseVideoPlayer = super.startWindowFullscreen(context, actionBar, statusBar); + MultiSampleVideo multiSampleVideo = (MultiSampleVideo) gsyBaseVideoPlayer; + multiSampleVideo.loadCoverImage(mCoverOriginUrl, mDefaultRes); + return multiSampleVideo; + } + + + @Override + public GSYBaseVideoPlayer showSmallVideo(Point size, boolean actionBar, boolean statusBar) { + //下面这里替换成你自己的强制转化 + MultiSampleVideo multiSampleVideo = (MultiSampleVideo) super.showSmallVideo(size, actionBar, statusBar); + multiSampleVideo.mStartButton.setVisibility(GONE); + multiSampleVideo.mStartButton = null; + return multiSampleVideo; + } + + public String getKey() { + if (mPlayPosition == -22) { + Debuger.printfError(getClass().getSimpleName() + " used getKey() " + "******* PlayPosition never set. ********"); + } + if (TextUtils.isEmpty(mPlayTag)) { + Debuger.printfError(getClass().getSimpleName() + " used getKey() " + "******* PlayTag never set. ********"); + } + return TAG + mPlayPosition + mPlayTag; + } +} \ No newline at end of file diff --git a/app/src/main/java/it/danieleverducci/ojo/ui/videoplayer/vlc/VlcCameraView.java b/app/src/main/java/it/danieleverducci/ojo/ui/videoplayer/vlc/VlcCameraView.java new file mode 100644 index 0000000..706c9a0 --- /dev/null +++ b/app/src/main/java/it/danieleverducci/ojo/ui/videoplayer/vlc/VlcCameraView.java @@ -0,0 +1,113 @@ +package it.danieleverducci.ojo.ui.videoplayer.vlc; + +import android.net.Uri; +import android.util.Log; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.ViewTreeObserver; + +import androidx.annotation.Nullable; +import androidx.fragment.app.FragmentActivity; + +import org.videolan.libvlc.IVLCVout; +import org.videolan.libvlc.LibVLC; +import org.videolan.libvlc.Media; +import org.videolan.libvlc.MediaPlayer; + +import it.danieleverducci.ojo.entities.Camera; +import it.danieleverducci.ojo.ui.SurveillanceFragment; +import it.danieleverducci.ojo.ui.videoplayer.BaseCameraView; +import it.danieleverducci.ojo.ui.videoplayer.VideoLibEnum; + +/** + * Contains all entities (views and java entities) related to a camera stream viewer + */ +public class VlcCameraView extends BaseCameraView { + public MediaPlayer mediaPlayer; + public IVLCVout ivlcVout; + public LibVLC libvlc; + + public VlcCameraView(FragmentActivity context, Camera camera) { + super(context, camera); + surfaceView = new SurfaceView(context); + kind = VideoLibEnum.VLC; + this.libvlc = VlcConfig.getInstance().getLibVlc(context); + SurfaceHolder holder = surfaceView.getHolder(); + holder.setKeepScreenOn(true); + // Create media player + mediaPlayer = new MediaPlayer(libvlc); + + // Set up video output + ivlcVout = mediaPlayer.getVLCVout(); + ivlcVout.setVideoView(surfaceView); + ivlcVout.attachViews(); + + // Load media and start playing + Media m = new Media(libvlc, Uri.parse(camera.getRtspUrl())); + m.setHWDecoderEnabled(true, false); + mediaPlayer.setMedia(m); + + // Register for view resize events + final ViewTreeObserver observer = surfaceView.getViewTreeObserver(); + observer.addOnGlobalLayoutListener(() -> { + // Set rendering size + ivlcVout.setWindowSize(surfaceView.getWidth(), surfaceView.getHeight()); + }); + } + + /** + * Starts the playback. + */ + @Override + public void startPlayback() { + mediaPlayer.play(); + } + + @Override + public void pause() { + mediaPlayer.pause(); + } + + @Override + public void resume() { + startPlayback(); + } + + @Override + public void stop() { + destroy(); + } + + /** + * Destroys the object and frees the memory + */ + @Override + public void destroy() { + if (libvlc == null) { + Log.e(SurveillanceFragment.TAG, this.toString() + " already destroyed"); + return; + } + + mediaPlayer.stop(); + final IVLCVout vout = mediaPlayer.getVLCVout(); + vout.detachViews(); + libvlc.release(); + libvlc = null; + mediaPlayer.release(); + mediaPlayer = null; + } + + @Override + public void fullScreen(@Nullable FullEvent fullEvent) { + surfaceView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (fullEvent != null) { + fullEvent.fullOrNot(VlcCameraView.this); + } + } + }); + } + +} diff --git a/app/src/main/java/it/danieleverducci/ojo/ui/videoplayer/vlc/VlcConfig.java b/app/src/main/java/it/danieleverducci/ojo/ui/videoplayer/vlc/VlcConfig.java new file mode 100644 index 0000000..08d637d --- /dev/null +++ b/app/src/main/java/it/danieleverducci/ojo/ui/videoplayer/vlc/VlcConfig.java @@ -0,0 +1,41 @@ +package it.danieleverducci.ojo.ui.videoplayer.vlc; + +import android.content.Context; + +import org.videolan.libvlc.LibVLC; +import org.videolan.libvlc.util.VLCUtil; + +import java.util.ArrayList; +import java.util.Arrays; + +public class VlcConfig { + private static volatile VlcConfig config = null; + public LibVLC libvlc = null; + + public final static String[] VLC_OPTIONS = new String[]{ + "--aout=opensles", + //"--realrtsp-caching=1000", + //"--audio-time-stretch", // time stretching + //"-vvv", // verbosity + "--avcodec-codec=h264", + //"--file-logging", + //"--logfile=vlc-log.txt" + }; + + private VlcConfig() { + } + + public LibVLC getLibVlc(Context context) { + if (this.libvlc == null) + this.libvlc = new LibVLC(context, new ArrayList<>(Arrays.asList(VlcConfig.VLC_OPTIONS))); + return this.libvlc; + } + + public static VlcConfig getInstance() { + if (config == null) { + config = new VlcConfig(); + } + return config; + } + +} diff --git a/app/src/main/res/drawable/corner_drawable_small.xml b/app/src/main/res/drawable/corner_drawable_small.xml new file mode 100644 index 0000000..af7febb --- /dev/null +++ b/app/src/main/res/drawable/corner_drawable_small.xml @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml index 4f68632..3e2e4e5 100644 --- a/app/src/main/res/layout/content_main.xml +++ b/app/src/main/res/layout/content_main.xml @@ -5,7 +5,7 @@ android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"> - + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_settings_item.xml b/app/src/main/res/layout/fragment_settings_item.xml index c3bb545..4f2ce27 100644 --- a/app/src/main/res/layout/fragment_settings_item.xml +++ b/app/src/main/res/layout/fragment_settings_item.xml @@ -3,17 +3,21 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_marginStart="4dp" + android:layout_marginTop="8dp" + android:layout_marginEnd="4dp" + android:background="@drawable/corner_drawable_small" + android:orientation="horizontal" android:paddingTop="@dimen/text_margin" - android:paddingBottom="@dimen/text_margin" - android:orientation="horizontal"> + android:paddingBottom="@dimen/text_margin"> + app:tint="@color/design_default_color_primary" /> + android:text="@tools:sample/lorem/random" + android:textAppearance="?attr/textAppearanceListItem" + android:textColor="@color/design_default_color_primary" /> + android:text="@tools:sample/lorem/random" + android:textAppearance="?attr/textAppearanceListItemSecondary" /> + + @@ -54,9 +64,10 @@ android:id="@+id/camera_drag_handle" android:layout_width="wrap_content" android:layout_height="match_parent" - android:padding="10dp" android:layout_weight="0" + android:padding="10dp" app:srcCompat="@drawable/ic_drag_handle" app:tint="@color/purple_200" /> + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_settings_item_list.xml b/app/src/main/res/layout/fragment_settings_item_list.xml index cc757cc..117e37d 100644 --- a/app/src/main/res/layout/fragment_settings_item_list.xml +++ b/app/src/main/res/layout/fragment_settings_item_list.xml @@ -3,24 +3,63 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" + android:background="@color/background_main" android:layout_height="match_parent" android:orientation="vertical"> + app:title="@string/app_name" /> + + + + + + + + + + + + diff --git a/app/src/main/res/layout/video_layout_cover.xml b/app/src/main/res/layout/video_layout_cover.xml new file mode 100644 index 0000000..e74cf93 --- /dev/null +++ b/app/src/main/res/layout/video_layout_cover.xml @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 142cb26..b817d03 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -25,5 +25,8 @@ Questa app è rilasciata sotto licenza GNU GENERAL PUBLIC LICENSE v3+. Puoi ottenerne una copia qui: https://raw.githubusercontent.com/penguin86/ojo/master/LICENSE Puoi trovare il codice sorgente al repository: https://github.com/penguin86/ojo Questa app è resa possibile dal magnifico lavoro dei team vlc and vlc-android! Per saperne di più o ottenere il codice sorgente: https://code.videolan.org/videolan/vlc-android + abilitare + Questa app è resa possibile da GSYVideoPlayer! Puoi saperne di più o ottenere il codice sorgente su https://github.com/CarGuo/GSYVideoPlayer + \ No newline at end of file diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml new file mode 100644 index 0000000..cbe9b59 --- /dev/null +++ b/app/src/main/res/values-zh/strings.xml @@ -0,0 +1,29 @@ + + Ojo + + First Fragment + Second Fragment + + 未命名相机 {camNo} + + rtsp://username:password@192.168.1.123:554 + 相机名称 + 名称 + 保存 + 无效的 RTSP 地址 + 取消 + 保存配置时出错 + 请输入您相机的 RTSP 流。请注意,该 URL 因相机而异:您可以在相机的设置或用户手册中找到完整的 URL。 + 允许屏幕旋转 + 仅横屏 + 信息 + 添加 + + 关于 Ojo + Created by Daniele Verducci. + 此应用程序在 GNU GENERAL PUBLIC LICENSE v3+ 下获得许可。您可以在此处获取副本:https://raw.githubusercontent.com/penguin86/ojo/master/LICENSE + 源码可以在github仓库获取:https://github.com/penguin86/ojo + 这个应用程序是通过 vlc 和 vlc-android 团队的努力实现的!您可以在 https://code.videolan.org/videolan/vlc-android 了解更多或获取源代码 + 启用 + 这个应用程序是通过 GSYVideoPlayer 实现的!您可以在 https://github.com/CarGuo/GSYVideoPlayer 了解更多或获取源代码 + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index e487941..5cb0315 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -5,4 +5,7 @@ #FF3700B3 #FF000000 #FFFFFFFF + #ededed + #F5F6F9 + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index cf00d3f..0afc085 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -1,4 +1,4 @@ - 16dp + 36dp 16dp \ No newline at end of file diff --git a/app/src/main/res/values/id.xml b/app/src/main/res/values/id.xml new file mode 100644 index 0000000..3b52790 --- /dev/null +++ b/app/src/main/res/values/id.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e222c51..6996132 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -24,4 +24,6 @@ This application is licensed under the GNU GENERAL PUBLIC LICENSE v3+. You can obtain a copy here: https://raw.githubusercontent.com/penguin86/ojo/master/LICENSE The source code can be obtained at the github repository: https://github.com/penguin86/ojo This app is made possible by the gourgeous vlc and vlc-android teams effort! You can know more or obtain the source code at https://code.videolan.org/videolan/vlc-android + enable + This app is made possible by the GSYVideoPlayer ! You can know more or obtain the source code at https://github.com/CarGuo/GSYVideoPlayer \ No newline at end of file