├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── chezi008 │ │ └── videosurveillance │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── chezi008 │ │ │ └── videosurveillance │ │ │ ├── H264ReadRunable.java │ │ │ ├── VideoGridLayoutManager.java │ │ │ ├── adapter │ │ │ └── ContainerAdapter.java │ │ │ ├── base │ │ │ ├── BaseActivity.java │ │ │ └── BaseUi.java │ │ │ ├── bean │ │ │ └── VideoBean.java │ │ │ ├── constant │ │ │ └── FileConstant.java │ │ │ ├── ui │ │ │ ├── FullScreenActivity.java │ │ │ ├── MainActivity.java │ │ │ ├── PqVideoContainerActivity.java │ │ │ └── VideoContainerActivity.java │ │ │ ├── utils │ │ │ ├── DensityUtils.java │ │ │ └── PermissionTools.java │ │ │ └── viedeoview │ │ │ ├── PlayerViewState.java │ │ │ ├── PqVideoDecoder.java │ │ │ ├── PqVideoPlayer.java │ │ │ ├── PqVideoPlayerZoomIn.java │ │ │ ├── VideoContainer.java │ │ │ ├── VideoRecycler.java │ │ │ └── VideoTextureView.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_full_screen.xml │ │ ├── activity_main.xml │ │ ├── activity_pq_video_container.xml │ │ ├── activity_video_container.xml │ │ └── item_container.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── 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 │ │ ├── vp_ic_full_screen.png │ │ ├── vp_ic_res_add.png │ │ └── vp_ic_stop_play.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── ids.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── chezi008 │ └── videosurveillance │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradlew ├── gradlew.bat ├── settings.gradle ├── 效果图2.gif └── 流程图.png /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VideoSurveillance 2 | 视频监控,四宫格视图。双击放大,视频无缝衔接 3 | ## Android 使用MediaCodec实现视频的无缝切换 4 | 5 | ### 一、功能说明: 6 | #### 在**不同控件之间**实现视频的无缝切换。不会黑屏,也不需要重新创建解码器。 7 | 8 | 百度上面很多视频播放都是利用MediaPlayer+显示视图(SurfaceView、TextureView)进行本地或者网络视频的播放。那么利用MediaCodec对视频流进行硬解码的小伙伴该如何在不同的控件之间无缝切换呢?是不是TextureView的生命周期很难控制? 9 | 10 | ### 二、实现方案 11 | ![流程图.png](http://upload-images.jianshu.io/upload_images/419652-a35d4a7b26b24b55.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 12 | 13 | ### 三、实现效果 14 | ![效果图.gif](https://github.com/chezi008/VideoSurveillance/blob/master/%E6%95%88%E6%9E%9C%E5%9B%BE2.gif) 15 | 16 | ### 四、TextureView 17 | 18 | #### 4.1 与SurfaceTexture的关系 19 | TextureView与SurfaceTexture构成了组合关系,可见SurfaceTexture的确是由TextureView给『包办』了。在程序世界中,一个对象被『包办』无非是指: 20 | 21 | (1)这个对象什么时候被创建? 22 | 23 | (2)这个对象如何被创建? 24 | 25 | (3)这个对象的产生带来了哪些变化,或者说程序自从有了它有哪些不同?​ 26 | 27 | (4)这个对象什么时候被销毁?​ 28 | 29 | 之所以对​SurfaceTexture这个对象要大动笔墨,因为它是整个显示框架的『连接者』。 30 | 31 | #### 4.2 生命周期控制 32 | 1. 切换至后台的时候会调用onSurfaceTextureDestroyed,从后台切换回来会调用onSurfaceTextureAvailable。 33 | 2. TextureView的ViewGroup remove TextureView的时候会调用onSurfaceTextureDestroyed方法。相同,TextureView的ViewGroup add TextureView的时候会调用onSurfaceTextureAvailable。**这些都是建立在视图可见的基础上**,如果视图不可见,add也不会调用onSurfaceTextureAvailable方法,remove也不会调用onSurfaceTextureDestroyed方法。 34 | 3. 当TextureView设置为Gone的时候,并不会调用onSurfaceTextureDestroyed方法法。 35 | 36 | #### 4.3设置SurfaceView 37 | 是不是遇到过在播放视频返回后台再回来,发现TextureView显示视图是一片黑色?监听TextureView的生命周期你会发现,返回后台是调用了销毁方法的。那你就会问销毁之后岂不是有需要重新创建?重新创建会引来更多的问题,解码去也需要重新初始化。所以我们只能另寻他法,下面方法就是无缝切换的核心部分。 38 | 39 | ``` 40 | @Override 41 | public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 42 | Log.d(TAG, "onSurfaceTextureDestroyed: "); 43 | mSurfaceTexture = surface; 44 | return false; 45 | } 46 | ``` 47 | 在销毁方法中我们注意,有一个返回参数。官方的解释如下 48 | ``` 49 | Invoked when the specified SurfaceTexture is about to be destroyed. If returns true, no rendering should happen inside the surface texture after this method is invoked. If returns false, the client needs to call release(). Most applications should return true. 50 | 51 | 大致的意思是如果返回ture,SurfaceTexture会自动销毁,如果返回false,SurfaceTexture不会销毁,需要用户手动调用release()进行销毁。 52 | ``` 53 | 现在恍然大悟了吧,我们在销毁的时候返回false,并保存SurfaceTexture对象,然后从后台返回界面的时候在onSurfaceTextureAvailable()方法中,调用setSurfaceTexture(mSurfaceTexture)方法,这样就会恢复之前的画面了。 54 | ``` 55 | @Override 56 | public void onSurfaceTextureAvailable(SurfaceTexture surface, 57 | int width, int height) { 58 | Log.d(TAG, "onSurfaceTextureAvailable: "); 59 | if(mSurfaceTexture!=null){ 60 | mTextureView.setSurfaceTexture(mSurfaceTexture); 61 | } 62 | } 63 | ``` 64 | 65 | ### 五、可拖拽效果 66 | 使用ItemTouchHelper轻松实现RecyclerView拖拽排序和滑动删除 67 | [传送门](http://chuansong.me/n/400690551872) 68 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 26 5 | defaultConfig { 6 | applicationId "com.chezi008.videosurveillance" 7 | minSdkVersion 18 8 | targetSdkVersion 26 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation fileTree(include: ['*.jar'], dir: 'libs') 23 | implementation 'com.android.support:appcompat-v7:26.0.2' 24 | implementation 'com.android.support.constraint:constraint-layout:1.0.2' 25 | testImplementation 'junit:junit:4.12' 26 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 27 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 28 | implementation 'com.android.support:recyclerview-v7:26.0.2' 29 | } 30 | -------------------------------------------------------------------------------- /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 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/chezi008/videosurveillance/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.chezi008.videosurveillance; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.chezi008.videosurveillance", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/chezi008/videosurveillance/H264ReadRunable.java: -------------------------------------------------------------------------------- 1 | package com.chezi008.videosurveillance; 2 | 3 | import android.util.Log; 4 | 5 | import com.chezi008.videosurveillance.constant.FileConstant; 6 | import com.chezi008.videosurveillance.viedeoview.PqVideoDecoder; 7 | 8 | import java.io.DataInputStream; 9 | import java.io.FileInputStream; 10 | import java.io.FileNotFoundException; 11 | import java.io.IOException; 12 | 13 | /** 14 | * 描述:用来将本地h264数据分割成一帧一帧的数据 15 | * 作者:chezi008 on 2017/6/29 16:50 16 | * 邮箱:chezi008@163.com 17 | */ 18 | 19 | public class H264ReadRunable implements Runnable { 20 | private static final int READ_BUFFER_SIZE = 1024 * 5; 21 | private static final int BUFFER_SIZE = 1024 * 1024; 22 | 23 | private String TAG = getClass().getSimpleName(); 24 | private H264ReadListener h264ReadListener; 25 | private DataInputStream mInputStream; 26 | 27 | public void setH264ReadListener(H264ReadListener h264ReadListener) { 28 | this.h264ReadListener = h264ReadListener; 29 | } 30 | 31 | private byte[] buffer; 32 | 33 | @Override 34 | public void run() { 35 | try { 36 | Log.d(TAG, "run: " + FileConstant.h264FilePath); 37 | mInputStream = new DataInputStream(new FileInputStream(FileConstant.h264FilePath)); 38 | buffer = new byte[BUFFER_SIZE]; 39 | 40 | int readLength; 41 | int naluIndex = 0; 42 | int offset = 0; 43 | int bufferLength = 0; 44 | 45 | while ((readLength = mInputStream.read(buffer, offset, READ_BUFFER_SIZE)) > 0) { 46 | 47 | bufferLength += readLength; 48 | offset = bufferLength; 49 | for (int i = 5; i < bufferLength - 4; i++) { 50 | if (buffer[i] == 0x00 && 51 | buffer[i + 1] == 0x00 && 52 | buffer[i + 2] == 0x00 && 53 | buffer[i + 3] == 0x01) { 54 | naluIndex = i; 55 | // Log.d(TAG, "run: naluIndex:"+naluIndex); 56 | byte[] naluBuffer = new byte[naluIndex]; 57 | System.arraycopy(buffer,0,naluBuffer,0,naluIndex); 58 | h264ReadListener.onFrameData(naluBuffer); 59 | offset = bufferLength-naluIndex; 60 | bufferLength -=naluIndex; 61 | System.arraycopy(buffer,naluIndex,buffer,0,offset); 62 | i = 5; 63 | Thread.sleep(50); 64 | } 65 | } 66 | 67 | } 68 | h264ReadListener.onStopRead(); 69 | } catch (FileNotFoundException e) { 70 | e.printStackTrace(); 71 | } catch (IOException e) { 72 | e.printStackTrace(); 73 | } catch (InterruptedException e) { 74 | e.printStackTrace(); 75 | } 76 | } 77 | 78 | public interface H264ReadListener { 79 | void onFrameData(byte[] datas); 80 | 81 | void onStopRead(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/java/com/chezi008/videosurveillance/VideoGridLayoutManager.java: -------------------------------------------------------------------------------- 1 | package com.chezi008.videosurveillance; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.GridLayoutManager; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.util.AttributeSet; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | /** 11 | * @author :chezi008 on 2017/12/28 11:44 12 | * @description : 13 | * @email :chezi008@163.com 14 | */ 15 | 16 | public class VideoGridLayoutManager extends GridLayoutManager{ 17 | public VideoGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 18 | super(context, attrs, defStyleAttr, defStyleRes); 19 | } 20 | 21 | public VideoGridLayoutManager(Context context, int spanCount) { 22 | super(context, spanCount); 23 | } 24 | 25 | public VideoGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) { 26 | super(context, spanCount, orientation, reverseLayout); 27 | } 28 | 29 | @Override 30 | public boolean canScrollVertically() { 31 | // super.canScrollVertically() 32 | return false; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/chezi008/videosurveillance/adapter/ContainerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.chezi008.videosurveillance.adapter; 2 | 3 | import android.support.constraint.ConstraintLayout; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.util.Log; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.TextView; 10 | 11 | import com.chezi008.videosurveillance.R; 12 | import com.chezi008.videosurveillance.bean.VideoBean; 13 | import com.chezi008.videosurveillance.viedeoview.PqVideoPlayer; 14 | 15 | import java.util.List; 16 | 17 | /** 18 | * @author :chezi008 on 2017/12/27 18:10 19 | * @description : 20 | * @email :chezi008@163.com 21 | */ 22 | 23 | public class ContainerAdapter extends RecyclerView.Adapter { 24 | private String TAG = getClass().getSimpleName(); 25 | private PqVideoPlayer.VideoPlayerListener videoPlayerListener; 26 | private List mData; 27 | 28 | private ConstraintLayout.LayoutParams layoutParams; 29 | 30 | public ContainerAdapter(List datas) { 31 | this.mData = datas; 32 | } 33 | 34 | @Override 35 | public ContainerHoler onCreateViewHolder(ViewGroup parent, int viewType) { 36 | View rootView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_container, parent, false); 37 | ContainerHoler containerHoler = new ContainerHoler(rootView); 38 | layoutParams = new ConstraintLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT); 39 | layoutParams.leftToLeft = ConstraintLayout.LayoutParams.PARENT_ID; 40 | layoutParams.rightToRight = ConstraintLayout.LayoutParams.PARENT_ID; 41 | layoutParams.topToTop = ConstraintLayout.LayoutParams.PARENT_ID; 42 | layoutParams.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID; 43 | return containerHoler; 44 | } 45 | 46 | @Override 47 | public void onBindViewHolder(final ContainerHoler holder, int position) { 48 | // final VideoBean videoBean = mData.get(position); 49 | // holder.pqVideoPlayer.setId(videoBean.getId()); 50 | // holder.pqVideoPlayer.setFocus(videoBean.isSelect()); 51 | // holder.pqVideoPlayer.setVideoPlayerListener(videoPlayerListener); 52 | // holder.pqVideoPlayer.setPqVideoOnclickListener(new PqVideoPlayer.PqVideoOnclickListener() { 53 | // @Override 54 | // public void onClick() { 55 | // refreshSelect(videoBean); 56 | // } 57 | // }); 58 | final PqVideoPlayer pqVideoPlayer = mData.get(position); 59 | pqVideoPlayer.setVideoPlayerListener(videoPlayerListener); 60 | pqVideoPlayer.setPqVideoOnclickListener(new PqVideoPlayer.PqVideoOnclickListener() { 61 | @Override 62 | public void onClick() { 63 | refreshSelect(pqVideoPlayer); 64 | } 65 | }); 66 | holder.constraintLayout.addView(pqVideoPlayer,0,layoutParams); 67 | 68 | } 69 | 70 | private void refreshSelect(PqVideoPlayer videoBean) { 71 | Log.d(TAG, "refreshSelect: "); 72 | for (PqVideoPlayer vb : 73 | mData) { 74 | vb.setFocus(vb == videoBean); 75 | } 76 | // notifyDataSetChanged(); 77 | } 78 | 79 | @Override 80 | public int getItemCount() { 81 | return mData.size(); 82 | } 83 | 84 | public static class ContainerHoler extends RecyclerView.ViewHolder { 85 | // private PqVideoPlayer pqVideoPlayer; 86 | private ConstraintLayout constraintLayout; 87 | 88 | public ContainerHoler(View itemView) { 89 | super(itemView); 90 | // pqVideoPlayer = itemView.findViewById(R.id.pq_video_player); 91 | constraintLayout = itemView.findViewById(R.id.container); 92 | } 93 | } 94 | 95 | public void setVideoPlayerListener(PqVideoPlayer.VideoPlayerListener videoPlayerListener) { 96 | this.videoPlayerListener = videoPlayerListener; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /app/src/main/java/com/chezi008/videosurveillance/base/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.chezi008.videosurveillance.base; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.app.AppCompatActivity; 6 | 7 | /** 8 | * @author :chezi008 on 2017/12/27 18:05 9 | * @description : 10 | * @email :chezi008@163.com 11 | */ 12 | 13 | public abstract class BaseActivity extends AppCompatActivity implements BaseUi { 14 | @Override 15 | protected void onCreate(@Nullable Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | initBaseView(); 18 | initVariable(); 19 | initView(); 20 | initData(); 21 | } 22 | 23 | protected void initBaseView(){ 24 | setContentView(getLayoutResId()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/chezi008/videosurveillance/base/BaseUi.java: -------------------------------------------------------------------------------- 1 | package com.chezi008.videosurveillance.base; 2 | 3 | import android.support.annotation.LayoutRes; 4 | 5 | /** 6 | * @author :chezi008 on 2017/12/27 18:07 7 | * @description : 8 | * @email :chezi008@163.com 9 | */ 10 | 11 | public interface BaseUi { 12 | @LayoutRes 13 | int getLayoutResId(); 14 | 15 | void initVariable(); 16 | 17 | void initView(); 18 | 19 | void initData(); 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/chezi008/videosurveillance/bean/VideoBean.java: -------------------------------------------------------------------------------- 1 | package com.chezi008.videosurveillance.bean; 2 | 3 | /** 4 | * @author :chezi008 on 2017/12/29 13:34 5 | * @description : 6 | * @email :chezi008@163.com 7 | */ 8 | 9 | public class VideoBean { 10 | private int id; 11 | private boolean isSelect; 12 | 13 | public int getId() { 14 | return id; 15 | } 16 | 17 | public void setId(int id) { 18 | this.id = id; 19 | } 20 | 21 | public boolean isSelect() { 22 | return isSelect; 23 | } 24 | 25 | public void setSelect(boolean select) { 26 | isSelect = select; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/chezi008/videosurveillance/constant/FileConstant.java: -------------------------------------------------------------------------------- 1 | package com.chezi008.videosurveillance.constant; 2 | 3 | import android.os.Environment; 4 | 5 | import java.io.File; 6 | 7 | /** 8 | * @author :chezi008 on 2017/12/27 20:15 9 | * @description : 10 | * @email :chezi008@163.com 11 | */ 12 | 13 | public class FileConstant { 14 | public static final String baseFile = Environment.getExternalStorageDirectory().getPath(); 15 | public static final String h264FileName = "mtv.h264"; 16 | public static final String mp4FileName = "mtv.mp4"; 17 | public static final String aacFileName = "test.aac"; 18 | 19 | public static final String mp4FilePath = baseFile + File.separator + mp4FileName; 20 | public static final String h264FilePath = baseFile + File.separator + h264FileName; 21 | public static final String aacFilePath = baseFile + File.separator + aacFileName; 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/chezi008/videosurveillance/ui/FullScreenActivity.java: -------------------------------------------------------------------------------- 1 | package com.chezi008.videosurveillance.ui; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.graphics.SurfaceTexture; 6 | import android.view.TextureView; 7 | 8 | import com.chezi008.videosurveillance.R; 9 | import com.chezi008.videosurveillance.base.BaseActivity; 10 | 11 | public class FullScreenActivity extends BaseActivity { 12 | private String TAG = getClass().getSimpleName(); 13 | private TextureView tv_texture; 14 | private SurfaceTexture mSurfaceTexture; 15 | @Override 16 | public int getLayoutResId() { 17 | return R.layout.activity_full_screen; 18 | } 19 | 20 | @Override 21 | public void initVariable() { 22 | 23 | } 24 | 25 | @Override 26 | public void initView() { 27 | tv_texture = findViewById(R.id.tv_texture); 28 | tv_texture.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() { 29 | @Override 30 | public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { 31 | 32 | } 33 | 34 | @Override 35 | public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 36 | 37 | } 38 | 39 | @Override 40 | public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 41 | return false; 42 | } 43 | 44 | @Override 45 | public void onSurfaceTextureUpdated(SurfaceTexture surface) { 46 | 47 | } 48 | }); 49 | } 50 | 51 | @Override 52 | public void initData() { 53 | 54 | } 55 | 56 | public static void start(Context context) { 57 | Intent starter = new Intent(context, FullScreenActivity.class); 58 | context.startActivity(starter); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/chezi008/videosurveillance/ui/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.chezi008.videosurveillance.ui; 2 | 3 | import android.Manifest; 4 | import android.os.Bundle; 5 | import android.support.annotation.NonNull; 6 | import android.view.View; 7 | import android.widget.Button; 8 | import android.widget.RelativeLayout; 9 | import android.widget.TextView; 10 | 11 | import com.chezi008.videosurveillance.R; 12 | import com.chezi008.videosurveillance.base.BaseActivity; 13 | import com.chezi008.videosurveillance.utils.PermissionTools; 14 | 15 | /** 16 | * @author :chezi008 on 2017/12/27 17:56 17 | * @description :视频监控测试主界面 18 | * @email :chezi008@qq.com 19 | */ 20 | public class MainActivity extends BaseActivity implements View.OnClickListener { 21 | 22 | private Button btn_video_container,btn_auto_update; 23 | private PermissionTools mPermissionTools; 24 | 25 | private String[] permissions = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, 26 | Manifest.permission.READ_EXTERNAL_STORAGE}; 27 | 28 | @Override 29 | public int getLayoutResId() { 30 | return R.layout.activity_main; 31 | } 32 | 33 | @Override 34 | public void initVariable() { 35 | mPermissionTools = new PermissionTools(this, permissions); 36 | mPermissionTools.requestApplicationPermission(); 37 | } 38 | 39 | @Override 40 | public void initView() { 41 | btn_video_container = findViewById(R.id.btn_video_container); 42 | btn_auto_update = findViewById(R.id.btn_auto_update); 43 | 44 | btn_video_container.setOnClickListener(this); 45 | btn_auto_update.setOnClickListener(this); 46 | } 47 | 48 | @Override 49 | public void initData() { 50 | 51 | } 52 | 53 | @Override 54 | public void onClick(View v) { 55 | switch (v.getId()) { 56 | case R.id.btn_video_container: 57 | PqVideoContainerActivity.start(this); 58 | break; 59 | case R.id.btn_auto_update: 60 | PqVideoContainerActivity.start(this); 61 | break; 62 | } 63 | } 64 | 65 | @Override 66 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 67 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 68 | mPermissionTools.onRequestPermissionsResult(requestCode, permissions, grantResults); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/java/com/chezi008/videosurveillance/ui/PqVideoContainerActivity.java: -------------------------------------------------------------------------------- 1 | package com.chezi008.videosurveillance.ui; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | 6 | import com.chezi008.videosurveillance.R; 7 | import com.chezi008.videosurveillance.base.BaseActivity; 8 | 9 | public class PqVideoContainerActivity extends BaseActivity { 10 | 11 | 12 | @Override 13 | public int getLayoutResId() { 14 | return R.layout.activity_pq_video_container; 15 | } 16 | 17 | @Override 18 | public void initVariable() { 19 | 20 | } 21 | 22 | @Override 23 | public void initView() { 24 | 25 | } 26 | 27 | @Override 28 | public void initData() { 29 | 30 | } 31 | 32 | public static void start(Context context) { 33 | Intent starter = new Intent(context, PqVideoContainerActivity.class); 34 | context.startActivity(starter); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/chezi008/videosurveillance/ui/VideoContainerActivity.java: -------------------------------------------------------------------------------- 1 | package com.chezi008.videosurveillance.ui; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.graphics.SurfaceTexture; 6 | import android.support.v7.widget.GridLayoutManager; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.support.v7.widget.helper.ItemTouchHelper; 9 | import android.util.Log; 10 | import android.view.View; 11 | 12 | import com.chezi008.videosurveillance.VideoGridLayoutManager; 13 | import com.chezi008.videosurveillance.R; 14 | import com.chezi008.videosurveillance.adapter.ContainerAdapter; 15 | import com.chezi008.videosurveillance.base.BaseActivity; 16 | import com.chezi008.videosurveillance.bean.VideoBean; 17 | import com.chezi008.videosurveillance.viedeoview.PqVideoPlayer; 18 | import com.chezi008.videosurveillance.viedeoview.PqVideoPlayerZoomIn; 19 | 20 | import java.util.ArrayList; 21 | import java.util.Collections; 22 | import java.util.List; 23 | 24 | /** 25 | * @author :chezi008 on 2017/12/27 17:59 26 | * @description :视频播放器容器界面 27 | * @email :chezi008@qq.com 28 | */ 29 | public class VideoContainerActivity extends BaseActivity { 30 | private String TAG = getClass().getSimpleName(); 31 | 32 | private RecyclerView rv_list; 33 | private PqVideoPlayerZoomIn pqZoomOutVideoPlayer; 34 | private PqVideoPlayer mCurPqVideoPlayer; 35 | private GridLayoutManager mGridLayoutManager; 36 | 37 | private ContainerAdapter containerAdapter; 38 | private List mData; 39 | 40 | 41 | public static void start(Context context) { 42 | Intent starter = new Intent(context, VideoContainerActivity.class); 43 | context.startActivity(starter); 44 | } 45 | 46 | @Override 47 | public int getLayoutResId() { 48 | return R.layout.activity_video_container; 49 | } 50 | 51 | @Override 52 | public void initVariable() { 53 | mData = new ArrayList<>(); 54 | for (int i = 0; i < 4; i++) { 55 | VideoBean videoBean = new VideoBean(); 56 | mData.add(videoBean); 57 | } 58 | } 59 | 60 | @Override 61 | protected void onResume() { 62 | super.onResume(); 63 | } 64 | 65 | @Override 66 | public void initView() { 67 | rv_list = findViewById(R.id.rv_list); 68 | pqZoomOutVideoPlayer = findViewById(R.id.pq_video); 69 | 70 | // containerAdapter = new ContainerAdapter(mData); 71 | containerAdapter.setVideoPlayerListener(new PqVideoPlayer.VideoPlayerListener() { 72 | @Override 73 | public void onZoomInView(PqVideoPlayer videoPlayer) { 74 | mCurPqVideoPlayer = videoPlayer; 75 | 76 | videoPlayer.setVisibility(View.GONE); 77 | pqZoomOutVideoPlayer.setVisibility(View.VISIBLE); 78 | pqZoomOutVideoPlayer.setPqZoomOutVideoListener(videoPlayer); 79 | 80 | // mGridLayoutManager.setSpanCount(1); 81 | // mGridLayoutManager.requestLayout(); 82 | } 83 | 84 | @Override 85 | public void onChangeSurface(SurfaceTexture surfaceTexture) { 86 | pqZoomOutVideoPlayer.setSurfaceView(surfaceTexture); 87 | } 88 | 89 | }); 90 | mGridLayoutManager = new VideoGridLayoutManager(this, 2); 91 | rv_list.setLayoutManager(mGridLayoutManager); 92 | rv_list.setAdapter(containerAdapter); 93 | 94 | ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new ItemTouchHelper.Callback() { 95 | @Override 96 | public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { 97 | Log.d(TAG, "getMovementFlags: "); 98 | int dragFlags; 99 | int swipeFlags; 100 | if (recyclerView.getLayoutManager() instanceof GridLayoutManager) { 101 | dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | 102 | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT; 103 | swipeFlags = 0; 104 | } else { 105 | dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN; 106 | swipeFlags = 0; 107 | } 108 | return makeMovementFlags(dragFlags, swipeFlags); 109 | } 110 | 111 | @Override 112 | public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { 113 | Log.d(TAG, "onMove: "); 114 | int fromPosition = viewHolder.getAdapterPosition(); 115 | int toPosition = target.getAdapterPosition(); 116 | if (fromPosition < toPosition) { 117 | for (int i = fromPosition; i < toPosition; i++) { 118 | Collections.swap(mData, i, i + 1); 119 | } 120 | } else { 121 | for (int i = fromPosition; i < toPosition; i--) { 122 | Collections.swap(mData, i, i - 1); 123 | } 124 | } 125 | containerAdapter.notifyItemMoved(fromPosition, toPosition); 126 | return true; 127 | } 128 | 129 | @Override 130 | public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { 131 | Log.d(TAG, "onSwiped: "); 132 | } 133 | }); 134 | itemTouchHelper.attachToRecyclerView(rv_list); 135 | } 136 | 137 | @Override 138 | public void initData() { 139 | 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /app/src/main/java/com/chezi008/videosurveillance/utils/DensityUtils.java: -------------------------------------------------------------------------------- 1 | package com.chezi008.videosurveillance.utils; 2 | 3 | 4 | import android.content.Context; 5 | 6 | /** 7 | * 描述: 8 | * 作者:chezi008 on 2017/5/25 11:10 9 | * 邮箱:chezi008@163.com 10 | */ 11 | public class DensityUtils { 12 | /** 13 | * 根据手机的分辨率从 dip 的单位 转成为 px(像素) 14 | */ 15 | public static int dip2px(Context context, float dpValue) { 16 | final float scale = context.getResources().getDisplayMetrics().density; 17 | return (int) (dpValue * scale + 0.5f); 18 | } 19 | 20 | /** 21 | * 根据手机的分辨率从 px(像素) 的单位 转成为 dp 22 | */ 23 | public static int px2dip(Context context, float pxValue) { 24 | final float scale = context.getResources().getDisplayMetrics().density; 25 | return (int) (pxValue / scale + 0.5f); 26 | } 27 | 28 | /** 29 | * 将px值转换为sp值,保证文字大小不变 30 | */ 31 | public static int px2sp(Context context, float pxValue) { 32 | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; 33 | return (int) (pxValue / fontScale + 0.5f); 34 | } 35 | 36 | /** 37 | * 将sp值转换为px值,保证文字大小不变 38 | */ 39 | public static int sp2px(Context context, float spValue) { 40 | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; 41 | return (int) (spValue * fontScale + 0.5f); 42 | } 43 | 44 | public static int getScreenWidth(Context context) { 45 | return context.getResources().getDisplayMetrics().widthPixels; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/chezi008/videosurveillance/utils/PermissionTools.java: -------------------------------------------------------------------------------- 1 | package com.chezi008.videosurveillance.utils; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.DialogInterface; 6 | import android.content.Intent; 7 | import android.content.pm.PackageManager; 8 | import android.net.Uri; 9 | import android.provider.Settings; 10 | import android.support.annotation.NonNull; 11 | import android.support.v4.app.ActivityCompat; 12 | import android.support.v4.content.ContextCompat; 13 | import android.support.v7.app.AlertDialog; 14 | 15 | /** 16 | * 描述: 17 | * 作者:chezi008 on 2017/7/6 9:51 18 | * 邮箱:chezi008@163.com 19 | */ 20 | 21 | public class PermissionTools { 22 | private static final int MY_PERMISSION_REQUEST_CODE = 10000; 23 | private Context mContext; 24 | private String[] requestPermissions; 25 | private RequestPermissionListenner mRequestPermissionListenner; 26 | 27 | public PermissionTools(Context context, String[] permissions) { 28 | this.mContext = context; 29 | this.requestPermissions = permissions; 30 | } 31 | 32 | public void setmRequestPermissionListenner(RequestPermissionListenner mRequestPermissionListenner) { 33 | this.mRequestPermissionListenner = mRequestPermissionListenner; 34 | } 35 | 36 | /** 37 | * 点击按钮,将通讯录备份保存到外部存储器备。 38 | *

39 | * 需要3个权限(都是危险权限): 40 | * 1. 读取通讯录权限; 41 | * 2. 读取外部存储器权限; 42 | * 3. 写入外部存储器权限. 43 | */ 44 | public void requestApplicationPermission() { 45 | /** 46 | * 第 1 步: 检查是否有相应的权限 47 | */ 48 | boolean isAllGranted = checkPermissionAllGranted(requestPermissions); 49 | // 如果这3个权限全都拥有, 则直接执行备份代码 50 | if (isAllGranted) { 51 | if (mRequestPermissionListenner != null) 52 | mRequestPermissionListenner.requestSuccess(); 53 | return; 54 | } 55 | 56 | /** 57 | * 第 2 步: 请求权限 58 | */ 59 | // 一次请求多个权限, 如果其他有权限是已经授予的将会自动忽略掉 60 | ActivityCompat.requestPermissions( 61 | (Activity) mContext, 62 | requestPermissions, 63 | MY_PERMISSION_REQUEST_CODE 64 | ); 65 | } 66 | 67 | /** 68 | * 检查是否拥有指定的所有权限 69 | */ 70 | private boolean checkPermissionAllGranted(String[] permissions) { 71 | for (String permission : permissions) { 72 | if (ContextCompat.checkSelfPermission(mContext, permission) != PackageManager.PERMISSION_GRANTED) { 73 | // 只要有一个权限没有被授予, 则直接返回 false 74 | return false; 75 | } 76 | } 77 | return true; 78 | } 79 | 80 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 81 | 82 | if (requestCode == MY_PERMISSION_REQUEST_CODE) { 83 | boolean isAllGranted = true; 84 | 85 | // 判断是否所有的权限都已经授予了 86 | for (int grant : grantResults) { 87 | if (grant != PackageManager.PERMISSION_GRANTED) { 88 | isAllGranted = false; 89 | break; 90 | } 91 | } 92 | 93 | if (isAllGranted) { 94 | // 如果所有的权限都授予了, 则执行备份代码 95 | if (mRequestPermissionListenner != null){ 96 | mRequestPermissionListenner.requestSuccess(); 97 | } 98 | } else { 99 | // 弹出对话框告诉用户需要权限的原因, 并引导用户去应用权限管理中手动打开权限按钮 100 | openAppDetails(); 101 | } 102 | } 103 | } 104 | 105 | /** 106 | * 打开 APP 的详情设置 107 | */ 108 | private void openAppDetails() { 109 | AlertDialog.Builder builder = new AlertDialog.Builder(mContext); 110 | builder.setMessage("缺少必要权限,请到 “应用信息 -> 权限” 中授予!"); 111 | builder.setPositiveButton("去手动授权", new DialogInterface.OnClickListener() { 112 | @Override 113 | public void onClick(DialogInterface dialog, int which) { 114 | Intent intent = new Intent(); 115 | intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); 116 | intent.addCategory(Intent.CATEGORY_DEFAULT); 117 | intent.setData(Uri.parse("package:" + mContext.getPackageName())); 118 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 119 | intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); 120 | intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 121 | mContext.startActivity(intent); 122 | } 123 | }); 124 | builder.setNegativeButton("取消", null); 125 | builder.show(); 126 | } 127 | 128 | public interface RequestPermissionListenner { 129 | void requestSuccess(); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /app/src/main/java/com/chezi008/videosurveillance/viedeoview/PlayerViewState.java: -------------------------------------------------------------------------------- 1 | package com.chezi008.videosurveillance.viedeoview; 2 | 3 | /** 4 | * Created by Administrator on 2016/10/24. 5 | */ 6 | 7 | public enum PlayerViewState { 8 | Default, 9 | Playying, 10 | Waiting, 11 | Control 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/chezi008/videosurveillance/viedeoview/PqVideoDecoder.java: -------------------------------------------------------------------------------- 1 | package com.chezi008.videosurveillance.viedeoview; 2 | 3 | import android.annotation.TargetApi; 4 | import android.graphics.SurfaceTexture; 5 | import android.media.MediaCodec; 6 | import android.media.MediaFormat; 7 | import android.os.Build; 8 | import android.support.annotation.RequiresApi; 9 | import android.util.Log; 10 | import android.view.Surface; 11 | 12 | import java.nio.ByteBuffer; 13 | import java.util.Arrays; 14 | 15 | /** 16 | * @author :chezi008 on 2017/11/29 12:34 17 | * @description :视频解码器 18 | * @email :chezi008@qq.com 19 | */ 20 | 21 | public class PqVideoDecoder { 22 | 23 | public static final int NALU_TYPE_IDR = 5; 24 | public static final int NALU_TYPE_SPS = 7; 25 | public static final int NALU_TYPE_PPS = 8; 26 | 27 | private String TAG = getClass().getSimpleName(); 28 | private MediaFormat mVideoFormat; 29 | private MediaCodec mVideoCodec; 30 | private Surface mSurface; 31 | private ByteBuffer[] mVideoInputBuffers; 32 | 33 | private long timeUs=10000; 34 | private long presentationTimeUs; 35 | 36 | public static byte[] header_sps; 37 | public static byte[] header_pps; 38 | 39 | /** 40 | * 初始化视频编码器 41 | * 42 | * @param surfaceTexture 43 | */ 44 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 45 | public boolean init(SurfaceTexture surfaceTexture, int width, int height) { 46 | if (mVideoCodec == null) { 47 | try { 48 | mVideoFormat = MediaFormat.createVideoFormat("video/avc", width, height); 49 | // if (true) { 50 | // Log.d(TAG, "init: header_sps"+ Arrays.toString(header_sps)); 51 | // byte[] sps = { 0, 0, 0, 1, 103, 100, 0, 40, -84, 52, -59, 1, -32, 17, 31, 120, 11, 80, 16, 16, 31, 0, 0, 3, 3, -23, 0, 0, -22, 96, -108 }; 52 | // byte[] pps = { 0, 0, 0, 1, 104, -18, 60, -128 }; 53 | // mVideoFormat.setByteBuffer("csd-0", ByteBuffer.wrap(sps)); 54 | // mVideoFormat.setByteBuffer("csd-1", ByteBuffer.wrap(pps)); 55 | // } 56 | // mVideoFormat.setInteger(MediaFormat.KEY_FRAME_RATE,5); 57 | // mVideoFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,1); 58 | String mime = mVideoFormat.getString(MediaFormat.KEY_MIME); 59 | mVideoCodec = MediaCodec.createDecoderByType(mime); 60 | mSurface = new Surface(surfaceTexture); 61 | mVideoCodec.configure(mVideoFormat, mSurface, null, 0); 62 | mVideoCodec.start(); 63 | } catch (Exception ex) { 64 | ex.printStackTrace(); 65 | return false; 66 | } 67 | } 68 | return true; 69 | } 70 | 71 | private boolean isSetSpecificData; 72 | 73 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 74 | public void feedData(byte[] buf, int offset, int length) { 75 | int naluType = 0; 76 | if (length < 4) { 77 | return; 78 | } 79 | //判断帧的类型 80 | // Log.d(TAG, "feedData: type:"+(buf[4] & 0x1f)); 81 | switch (buf[4] & 0x1f) { 82 | case NALU_TYPE_IDR: 83 | naluType = MediaCodec.BUFFER_FLAG_KEY_FRAME; 84 | break; 85 | case NALU_TYPE_SPS: 86 | isSetSpecificData = true; 87 | naluType = MediaCodec.BUFFER_FLAG_CODEC_CONFIG; 88 | header_sps = buf; 89 | break; 90 | case NALU_TYPE_PPS: 91 | naluType = MediaCodec.BUFFER_FLAG_CODEC_CONFIG; 92 | header_pps = buf; 93 | break; 94 | default: 95 | naluType = 0; 96 | break; 97 | } 98 | if (!isSetSpecificData) { 99 | Log.d(TAG, "feedData: return"); 100 | return; 101 | } 102 | try { 103 | mVideoInputBuffers = mVideoCodec.getInputBuffers(); 104 | // 这里解释一下 传0是不等待 传-1是一直等待 但是传-1会在很多机器上挂掉,所以还是用0吧 丢帧总比挂掉强 105 | int inputBufferIndex = mVideoCodec.dequeueInputBuffer(timeUs); 106 | if (inputBufferIndex >= 0) { 107 | // 从输入队列里去空闲buffer 108 | ByteBuffer inputBuffer = mVideoCodec.getInputBuffer(inputBufferIndex); 109 | inputBuffer.clear(); 110 | inputBuffer.put(buf, offset, length); 111 | mVideoCodec.queueInputBuffer(inputBufferIndex, 0, length, presentationTimeUs * 1000000 / 25, naluType); 112 | presentationTimeUs ++; 113 | } 114 | 115 | MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); 116 | //出列输出缓冲区,阻塞最多timeoutUs微妙 117 | int outputBufferIndex = mVideoCodec.dequeueOutputBuffer(bufferInfo, timeUs); 118 | // if (outputBufferIndex >= 0) { 119 | // // 将解码后数据渲染到surface上 120 | // boolean doRender = (bufferInfo.size != 0); 121 | // // doRender为true的时候意味着将数据显示在之前设置好的surfaceview上面 122 | // mVideoCodec.releaseOutputBuffer(outputBufferIndex, doRender); 123 | // } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 124 | // Log.d(TAG, "onDecodeVideoFrame: ----->outputBufferIndex:" + outputBufferIndex); 125 | // } 126 | while (outputBufferIndex>0){ 127 | mVideoCodec.releaseOutputBuffer(outputBufferIndex, true); 128 | outputBufferIndex = mVideoCodec.dequeueOutputBuffer(bufferInfo, 0); 129 | } 130 | // switch (outputBufferIndex) { 131 | // case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED: 132 | // return ; 133 | // case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: 134 | // return ; 135 | // case MediaCodec.INFO_TRY_AGAIN_LATER: 136 | // return ; 137 | // case MediaCodec.BUFFER_FLAG_KEY_FRAME: 138 | // return ; 139 | // default: 140 | // //show image right now 141 | // mVideoCodec.releaseOutputBuffer(outputBufferIndex, true); 142 | // return ; 143 | // } 144 | } catch (Exception ex) { 145 | ex.printStackTrace(); 146 | } 147 | } 148 | 149 | public void stop() { 150 | if (mVideoCodec != null) { 151 | try { 152 | mVideoCodec.stop(); 153 | mVideoCodec.release(); 154 | mVideoCodec = null; 155 | } catch (IllegalStateException e) { 156 | e.printStackTrace(); 157 | mVideoCodec = null; 158 | } 159 | } 160 | 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /app/src/main/java/com/chezi008/videosurveillance/viedeoview/PqVideoPlayer.java: -------------------------------------------------------------------------------- 1 | package com.chezi008.videosurveillance.viedeoview; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Color; 6 | import android.graphics.SurfaceTexture; 7 | import android.os.Handler; 8 | import android.os.Message; 9 | import android.os.SystemClock; 10 | import android.text.TextUtils; 11 | import android.util.AttributeSet; 12 | import android.util.Log; 13 | import android.view.MotionEvent; 14 | import android.view.TextureView; 15 | import android.view.View; 16 | import android.view.ViewGroup; 17 | import android.widget.ImageView; 18 | import android.widget.LinearLayout; 19 | import android.widget.ProgressBar; 20 | import android.widget.RelativeLayout; 21 | import android.widget.TextView; 22 | 23 | import com.chezi008.videosurveillance.H264ReadRunable; 24 | import com.chezi008.videosurveillance.R; 25 | import com.chezi008.videosurveillance.ui.FullScreenActivity; 26 | import com.chezi008.videosurveillance.utils.DensityUtils; 27 | 28 | import java.util.concurrent.ArrayBlockingQueue; 29 | import java.util.concurrent.ScheduledExecutorService; 30 | import java.util.concurrent.ScheduledThreadPoolExecutor; 31 | import java.util.concurrent.ThreadPoolExecutor; 32 | import java.util.concurrent.TimeUnit; 33 | 34 | /** 35 | * 描述:自定义播放器 36 | * 37 | * @author :chezi008 on 2017/11/21 15:41 38 | * 邮箱:chezi008@163.com 39 | */ 40 | 41 | public class PqVideoPlayer extends RelativeLayout implements View.OnClickListener, PqVideoPlayerZoomIn.PqZoomOutVideoListener { 42 | 43 | private static final int CORE_POOL_SIZE = 5; 44 | private static final int MAXIMUM_POOL_SIZE = 10; 45 | private static final int KEEP_ALIVE_TIME = 60000; 46 | 47 | public static final int HANDLER_SET_STATUS_DEAULT = 0; 48 | public static final int HANDLER_SET_STATUS_PLAYING = 1; 49 | public static final int HANDLER_UPDATE_BPS = 2; 50 | public static final int HANDLER_UPDATE_LOSS_FRAME = 3; 51 | 52 | private ArrayBlockingQueue mArrayBlockingQueue; 53 | private String TAG = getClass().getSimpleName(); 54 | //自定义属性 1、选中时候边框大小 2、选中边框颜色 55 | //3、默认背景色 4、选中背景色 56 | // 5、资源添加icon 57 | /** 58 | * 默认边框大小 59 | */ 60 | private final int DEFAULT_BORDER_SIZE = 1; 61 | private final int DEFALUT_BORDER_COLOR = Color.parseColor("#00000000"); 62 | /** 63 | * 默认背景 64 | */ 65 | private final int DEFAULT_BG_COLOR = Color.parseColor("#6b7e9e"); 66 | private final int DEFAULT_BG_SELECT_COLOR = Color.parseColor("#FFFFFF"); 67 | /** 68 | * 默认添加资源图片 69 | */ 70 | private final int DEFAULT_RES_ICON = R.mipmap.vp_ic_res_add; 71 | private final int DEFAULT_FULL_SCREEN_ICON = R.mipmap.vp_ic_full_screen; 72 | private final int DEFAULT_STOP_ICON = R.mipmap.vp_ic_stop_play; 73 | /** 74 | * 选中边框 75 | */ 76 | private int mBorderSize, mBorderColor; 77 | private int mBgColor, mBgSelectColor; 78 | private int mResIcon, mFullIcon, mStopIcon; 79 | 80 | private RelativeLayout mRlBorderBg, mRlVideoBg, mTextureParent, mRlBottomBar; 81 | private TextureView mTextureView; 82 | private SurfaceTexture mSurfaceTexture; 83 | private TextureView.SurfaceTextureListener mSurfaceTextureListener; 84 | 85 | private ProgressBar mProcessBar; 86 | private TextView mTvPath, mTvLoss; 87 | private ImageView ivResAdd; 88 | 89 | /** 90 | * 解码器 91 | */ 92 | private PqVideoDecoder mVideoDecoder; 93 | /** 94 | * 线程池 95 | */ 96 | private ThreadPoolExecutor mExecutorService; 97 | private ScheduledExecutorService mScheduledExecutorService; 98 | private Runnable mScheduleRunable; 99 | private H264ReadRunable mH264ReadRunable; 100 | 101 | private boolean isFullScreen, isFoucsed, isZoomIn; 102 | /** 103 | * 码流统计 104 | */ 105 | private Handler mMainHandler; 106 | 107 | private VideoPlayerListener videoPlayerListener; 108 | private PqVideoOnclickListener mPqVideoOnclickListener; 109 | 110 | private PlayerViewState curState; 111 | 112 | public PqVideoPlayer(Context context) { 113 | this(context, null); 114 | } 115 | 116 | public PqVideoPlayer(Context context, AttributeSet attrs) { 117 | super(context, attrs); 118 | TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PqVideoPlayer); 119 | mBorderSize = ta.getInt(R.styleable.PqVideoPlayer_vp_border_size, DEFAULT_BORDER_SIZE); 120 | mBorderColor = ta.getColor(R.styleable.PqVideoPlayer_vp_border_color, DEFALUT_BORDER_COLOR); 121 | mBgColor = ta.getColor(R.styleable.PqVideoPlayer_vp_bg_default_color, DEFAULT_BG_COLOR); 122 | mBgSelectColor = ta.getColor(R.styleable.PqVideoPlayer_vp_bg_select_color, DEFAULT_BG_SELECT_COLOR); 123 | mResIcon = ta.getResourceId(R.styleable.PqVideoPlayer_vp_res_icon, DEFAULT_RES_ICON); 124 | mFullIcon = ta.getResourceId(R.styleable.PqVideoPlayer_vp_fullscreen_icon, DEFAULT_FULL_SCREEN_ICON); 125 | mStopIcon = ta.getResourceId(R.styleable.PqVideoPlayer_vp_stop_icon, DEFAULT_STOP_ICON); 126 | 127 | initVirable(); 128 | initView(); 129 | } 130 | 131 | 132 | public void setVideoPlayerListener(VideoPlayerListener videoPlayerListener) { 133 | this.videoPlayerListener = videoPlayerListener; 134 | } 135 | 136 | public void setPqVideoOnclickListener(PqVideoOnclickListener mPqVideoOnclickListener) { 137 | this.mPqVideoOnclickListener = mPqVideoOnclickListener; 138 | } 139 | 140 | private void initVirable() { 141 | mArrayBlockingQueue = new ArrayBlockingQueue(MAXIMUM_POOL_SIZE); 142 | mExecutorService = new ThreadPoolExecutor(CORE_POOL_SIZE, 143 | MAXIMUM_POOL_SIZE, 144 | KEEP_ALIVE_TIME, 145 | TimeUnit.MILLISECONDS, mArrayBlockingQueue); 146 | mScheduledExecutorService = new ScheduledThreadPoolExecutor(1); 147 | 148 | if (mMainHandler == null) { 149 | mMainHandler = new Handler() { 150 | @Override 151 | public void handleMessage(Message msg) { 152 | switch (msg.what) { 153 | case HANDLER_SET_STATUS_DEAULT: 154 | setDefaultUI(); 155 | break; 156 | case HANDLER_SET_STATUS_PLAYING: 157 | setPlayerViewState(PlayerViewState.Playying); 158 | break; 159 | case HANDLER_UPDATE_BPS: 160 | // if (videoPlayerFullScreenListener != null) { 161 | // videoPlayerFullScreenListener.onGetBps((String) msg.obj); 162 | // } 163 | break; 164 | //播放器丢包率更新 165 | case HANDLER_UPDATE_LOSS_FRAME: 166 | mTvLoss.setText((String) msg.obj); 167 | // if (videoPlayerFullScreenListener != null) { 168 | // videoPlayerFullScreenListener.onGetLossFrame((String) msg.obj); 169 | // } 170 | break; 171 | default: 172 | break; 173 | } 174 | } 175 | }; 176 | } 177 | } 178 | 179 | /** 180 | * 初始化视图 181 | */ 182 | private void initView() { 183 | //设置屏幕常亮 184 | setKeepScreenOn(true); 185 | //添加选中的边框 186 | LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 187 | mRlBorderBg = new RelativeLayout(getContext()); 188 | mRlBorderBg.setBackgroundColor(mBorderColor); 189 | mRlBorderBg.setPadding(mBorderSize, mBorderSize, mBorderSize, mBorderSize); 190 | addView(mRlBorderBg, layoutParams); 191 | //添加播放器背景 192 | layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 193 | mRlVideoBg = new RelativeLayout(getContext()); 194 | mRlVideoBg.setBackgroundColor(mBgColor); 195 | mRlBorderBg.addView(mRlVideoBg, layoutParams); 196 | 197 | //添加播放器显示界面 198 | layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 199 | mTextureParent = new RelativeLayout(getContext()); 200 | mTextureParent.setBackgroundColor(Color.BLACK); 201 | mTextureParent.setVisibility(GONE); 202 | mRlVideoBg.addView(mTextureParent, layoutParams); 203 | 204 | //添加添加资源图标 205 | layoutParams = new LayoutParams(DensityUtils.sp2px(getContext(), 35), 206 | DensityUtils.sp2px(getContext(), 35)); 207 | layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT); 208 | ivResAdd = new ImageView(getContext()); 209 | ivResAdd.setImageResource(mResIcon); 210 | ivResAdd.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 211 | ivResAdd.setId(R.id.iv_res_add); 212 | ivResAdd.setOnClickListener(this); 213 | mRlVideoBg.addView(ivResAdd, layoutParams); 214 | //添加progres 215 | mProcessBar = new ProgressBar(getContext(), null, android.R.attr.progressBarStyle); 216 | mRlVideoBg.addView(mProcessBar, layoutParams); 217 | mProcessBar.setVisibility(GONE); 218 | 219 | // addTextureView(); 220 | 221 | //添加播放器控制栏 222 | //底部白色背景 223 | LayoutParams layoutParamsInner = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 224 | mRlBottomBar = new RelativeLayout(getContext()); 225 | mRlBottomBar.setBackgroundColor(Color.WHITE); 226 | mRlBottomBar.getBackground().setAlpha(128); 227 | mRlBottomBar.setGravity(CENTER_VERTICAL); 228 | layoutParamsInner.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); 229 | mRlVideoBg.addView(mRlBottomBar, layoutParamsInner); 230 | mRlBottomBar.setVisibility(GONE); 231 | //全屏 232 | int btnWidth = DensityUtils.sp2px(getContext(), 25); 233 | int btnPadding = DensityUtils.sp2px(getContext(), 3); 234 | LayoutParams layoutParamsFullScreen = new LayoutParams(btnWidth, btnWidth); 235 | layoutParamsFullScreen.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); 236 | ImageView ivFull = new ImageView(getContext()); 237 | ivFull.setId(R.id.btnFullScreen); 238 | 239 | ivFull.setPadding(btnPadding, btnPadding, btnPadding, btnPadding); 240 | ivFull.setImageResource(mFullIcon); 241 | ivFull.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 242 | ivFull.setOnClickListener(this); 243 | mRlBottomBar.addView(ivFull, layoutParamsFullScreen); 244 | //停止 245 | LayoutParams layoutParamsStopVideo = new LayoutParams(btnWidth, btnWidth); 246 | layoutParamsStopVideo.addRule(RelativeLayout.ALIGN_PARENT_LEFT); 247 | ImageView ivStop = new ImageView(getContext()); 248 | ivStop.setId(R.id.btnStop); 249 | ivStop.setPadding(btnPadding, btnPadding, btnPadding, btnPadding); 250 | ivStop.setImageResource(mStopIcon); 251 | ivStop.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 252 | ivStop.setOnClickListener(this); 253 | mRlBottomBar.addView(ivStop, layoutParamsStopVideo); 254 | 255 | //设备路径信息 256 | LinearLayout llPath = new LinearLayout(getContext()); 257 | llPath.setId(R.id.tvDeviceInfo); 258 | mTvPath = new TextView(getContext()); 259 | mTvPath.setTextColor(Color.WHITE); 260 | mTvPath.setTextSize(8); 261 | mTvPath.setSingleLine(true); 262 | mTvPath.setText("轮播字"); 263 | mTvPath.setEllipsize(TextUtils.TruncateAt.MARQUEE); 264 | mTvPath.setMarqueeRepeatLimit(-1); 265 | mTvPath.setSelected(true); 266 | llPath.addView(mTvPath); 267 | 268 | LayoutParams layoutParamsDevicePathTextView = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); 269 | layoutParamsDevicePathTextView.setMargins(btnPadding, btnPadding, btnPadding, btnPadding); 270 | layoutParamsDevicePathTextView.addRule(RIGHT_OF, R.id.btnStop); 271 | layoutParamsDevicePathTextView.addRule(RelativeLayout.LEFT_OF, R.id.tvLossFrame); 272 | layoutParamsDevicePathTextView.addRule(CENTER_VERTICAL); 273 | mRlBottomBar.addView(llPath, layoutParamsDevicePathTextView); 274 | 275 | //丢包率 276 | mTvLoss = new TextView(getContext()); 277 | mTvLoss.setId(R.id.tvLossFrame); 278 | mTvLoss.setTextColor(Color.WHITE); 279 | mTvLoss.setTextSize(8); 280 | mTvLoss.setLines(1); 281 | mTvLoss.setText("0.00%"); 282 | LayoutParams rlLoss = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 283 | rlLoss.setMargins(btnPadding, btnPadding, btnPadding, btnPadding); 284 | rlLoss.addRule(RelativeLayout.LEFT_OF, R.id.btnFullScreen); 285 | rlLoss.addRule(CENTER_VERTICAL); 286 | mRlBottomBar.addView(mTvLoss, rlLoss); 287 | 288 | setPlayerViewState(PlayerViewState.Default); 289 | // setPlayerViewState(PlayerViewState.Playying); 290 | } 291 | 292 | private void initTextureView() { 293 | if (mTextureView == null) { 294 | mTextureView = new VideoTextureView(getContext()); 295 | mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() { 296 | @Override 297 | public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { 298 | Log.d(TAG, "onSurfaceTextureAvailable: "); 299 | //检查解码器是否初始化 300 | if (!initDecoder(surface)) { 301 | Log.d(TAG, "onSurfaceTextureAvailable: init decoder failed!"); 302 | return; 303 | } 304 | if (mH264ReadRunable != null) { 305 | Log.d(TAG, "onSurfaceTextureAvailable: 读取视频流"); 306 | mExecutorService.execute(mH264ReadRunable); 307 | } 308 | } 309 | 310 | @Override 311 | public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 312 | 313 | } 314 | 315 | @Override 316 | public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 317 | Log.d(TAG, "onSurfaceTextureDestroyed: "); 318 | // stopDecoder(); 319 | mSurfaceTexture = surface; 320 | if (isZoomIn) { 321 | videoPlayerListener.onChangeSurface(surface); 322 | } 323 | return false; 324 | } 325 | 326 | @Override 327 | public void onSurfaceTextureUpdated(SurfaceTexture surface) { 328 | 329 | } 330 | }); 331 | 332 | } 333 | } 334 | 335 | /** 336 | * 添加显示画面 337 | */ 338 | private void addTextureView() { 339 | Log.d(TAG, "addTextureView: ----------->"); 340 | LayoutParams textureParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 341 | textureParams.addRule(RelativeLayout.CENTER_IN_PARENT); 342 | if (mTextureParent.getChildCount() > 0) { 343 | throw new IllegalStateException("已经添加了显示控件!"); 344 | } 345 | //不能添加空控件 播放器setPlaying的时候会添加 346 | mTextureParent.addView(mTextureView, textureParams); 347 | mTextureParent.setVisibility(VISIBLE); 348 | } 349 | 350 | 351 | /** 352 | * 停止解码器 353 | */ 354 | private void stopDecoder() { 355 | if (mVideoDecoder != null) { 356 | mVideoDecoder.stop(); 357 | } 358 | } 359 | 360 | /** 361 | * 移除显示画面 362 | */ 363 | private void removeTextureView() { 364 | mTextureParent.setVisibility(GONE); 365 | if (mTextureParent.getChildCount() > 0) { 366 | mTextureParent.removeView(mTextureView); 367 | // mTextureView = null; 368 | } 369 | } 370 | 371 | private void resetTextureView() { 372 | mTextureParent.setVisibility(GONE); 373 | if (mTextureParent.getChildCount() > 0) { 374 | mTextureParent.removeView(mTextureView); 375 | mTextureView = null; 376 | } 377 | } 378 | 379 | 380 | @Override 381 | public void onClick(View v) { 382 | if (v.getId() == R.id.iv_res_add) { 383 | //播放 384 | if (mH264ReadRunable == null) { 385 | mH264ReadRunable = new H264ReadRunable(); 386 | mH264ReadRunable.setH264ReadListener(new H264ReadRunable.H264ReadListener() { 387 | @Override 388 | public void onFrameData(byte[] datas) { 389 | mVideoDecoder.feedData(datas, 0, datas.length); 390 | } 391 | 392 | @Override 393 | public void onStopRead() { 394 | stopVideoPlayer(true); 395 | } 396 | }); 397 | } 398 | setPlayerViewState(PlayerViewState.Playying); 399 | } else if (v.getId() == R.id.btnFullScreen) { 400 | //全屏 401 | startFullScreen(); 402 | } else if (v.getId() == R.id.btnStop) { 403 | //停止 404 | Log.d(TAG, "onClick:btnStop "); 405 | stopVideoPlayer(true); 406 | } 407 | setSelectStyle(); 408 | } 409 | 410 | /** 411 | * 开启全屏 412 | */ 413 | public void startFullScreen() { 414 | isFullScreen = true; 415 | FullScreenActivity.start(getContext()); 416 | } 417 | 418 | /** 419 | * 退出全屏模式 420 | */ 421 | public void exitFullScreen() { 422 | isFullScreen = false; 423 | } 424 | 425 | 426 | @Override 427 | public boolean onTouchEvent(MotionEvent event) { 428 | // gestureDetector.onTouchEvent(event); 429 | switch (event.getAction()) { 430 | case MotionEvent.ACTION_DOWN: 431 | doubleClick_2(); 432 | mPqVideoOnclickListener.onClick(); 433 | return true; 434 | case MotionEvent.ACTION_UP: 435 | return true; 436 | } 437 | return super.onTouchEvent(event); 438 | } 439 | 440 | private long[] mHits = new long[2]; 441 | 442 | private void doubleClick_2() { 443 | System.arraycopy(mHits, 1, mHits, 0, mHits.length - 1); 444 | //获取手机开机时间 445 | mHits[mHits.length - 1] = SystemClock.uptimeMillis(); 446 | if (mHits[mHits.length - 1] - mHits[0] < 500) { 447 | /**双击的业务逻辑*/ 448 | Log.d(TAG, "doubleClick_2: 双击"); 449 | onZoomViewExchange(); 450 | } 451 | } 452 | 453 | private void onZoomViewExchange() { 454 | if (mTextureView != null) { 455 | mSurfaceTextureListener = mTextureView.getSurfaceTextureListener(); 456 | } 457 | isZoomIn = true; 458 | removeTextureView(); 459 | videoPlayerListener.onZoomInView(PqVideoPlayer.this); 460 | } 461 | 462 | /** 463 | * 设置选中的状态 464 | */ 465 | private void setSelectStyle() { 466 | isFoucsed = true; 467 | //背景为黑色,且出现选中边框 468 | mRlVideoBg.setBackgroundColor(Color.BLACK); 469 | mRlBorderBg.setBackgroundColor(mBgSelectColor); 470 | } 471 | 472 | private void setDefultStyle() { 473 | isFoucsed = false; 474 | mRlVideoBg.setBackgroundColor(mBgColor); 475 | mRlBorderBg.setBackgroundColor(mBorderColor); 476 | } 477 | 478 | /** 479 | * 设置时候获取焦点 480 | * 481 | * @param focus 482 | */ 483 | public void setFocus(boolean focus) { 484 | if (focus) { 485 | setSelectStyle(); 486 | } else { 487 | setDefultStyle(); 488 | } 489 | } 490 | 491 | /** 492 | * 开始播放视频 493 | */ 494 | public void startVideoPlayer() { 495 | setPlayerViewState(PlayerViewState.Playying); 496 | //设置轮播字幕 497 | // mTvPath.setText(mParams.getVideoName()); 498 | } 499 | 500 | public void stopVideoPlayer(boolean isClear) { 501 | //停止视频解码器 502 | stopDecoder(); 503 | //移除显示画面 504 | mMainHandler.sendEmptyMessage(HANDLER_SET_STATUS_DEAULT); 505 | // setPlayerViewState(PlayerViewState.Default); 506 | if (isClear) { 507 | // mParams.getSessionParams().requestId = null; 508 | } 509 | } 510 | 511 | /** 512 | * 显示加载动画 513 | */ 514 | public void showProgressbar() { 515 | setPlayerViewState(PlayerViewState.Waiting); 516 | //定时器 15s还没来自动关闭 517 | if (mScheduleRunable == null) { 518 | mScheduleRunable = new Runnable() { 519 | @Override 520 | public void run() { 521 | if (mProcessBar.getVisibility() == VISIBLE) { 522 | mMainHandler.sendEmptyMessage(HANDLER_SET_STATUS_DEAULT); 523 | } 524 | } 525 | }; 526 | } 527 | mScheduledExecutorService.schedule(mScheduleRunable, 15000, TimeUnit.MILLISECONDS); 528 | } 529 | 530 | 531 | private void setPlayerViewState(PlayerViewState state) { 532 | curState = state; 533 | Log.d(TAG, "setPlayerViewState: " + curState); 534 | switch (state) { 535 | case Default: 536 | //默认背景 537 | mRlVideoBg.setBackgroundColor(mBgColor); 538 | ivResAdd.setVisibility(VISIBLE); 539 | //隐藏textureView 540 | resetTextureView(); 541 | //隐藏dialog 542 | mProcessBar.setVisibility(GONE); 543 | //隐藏底部控制栏 544 | mRlBottomBar.setVisibility(GONE); 545 | break; 546 | case Waiting: 547 | //背景设为黑色 548 | mRlVideoBg.setBackgroundColor(Color.BLACK); 549 | //隐藏资源图标 550 | ivResAdd.setVisibility(GONE); 551 | //显示dialog 552 | mProcessBar.setVisibility(VISIBLE); 553 | break; 554 | case Playying: 555 | //隐藏dialog 556 | mProcessBar.setVisibility(GONE); 557 | //隐藏资源 558 | ivResAdd.setVisibility(GONE); 559 | initTextureView(); 560 | addTextureView(); 561 | //显示底部控制栏 562 | mRlBottomBar.setVisibility(VISIBLE); 563 | break; 564 | case Control: 565 | break; 566 | } 567 | } 568 | 569 | 570 | private boolean initDecoder(SurfaceTexture surface) { 571 | Log.d(TAG, "initDecoder: "); 572 | if (mVideoDecoder == null) { 573 | mVideoDecoder = new PqVideoDecoder(); 574 | } 575 | boolean initSuccess = mVideoDecoder.init(surface, 576 | 1280, 577 | 720); 578 | 579 | return initSuccess; 580 | } 581 | 582 | 583 | private void setDefaultUI() { 584 | //判断当前是否是播放状态 585 | if (mRlBottomBar.getVisibility() == VISIBLE || mProcessBar.getVisibility() == VISIBLE) { 586 | //停止视频解码器 587 | stopDecoder(); 588 | //移除显示画面 589 | setPlayerViewState(PlayerViewState.Default); 590 | setSelectStyle(); 591 | } 592 | } 593 | 594 | @Override 595 | public void onZoomIn() { 596 | onResume(); 597 | setVisibility(VISIBLE); 598 | isZoomIn = false; 599 | } 600 | 601 | 602 | public interface VideoPlayerListener { 603 | /** 604 | * 双击放大 605 | * 606 | * @param videoPlayer 607 | */ 608 | void onZoomInView(PqVideoPlayer videoPlayer); 609 | 610 | /** 611 | * 渲染画面被交换 612 | * 613 | * @param surfaceTexture 614 | */ 615 | void onChangeSurface(SurfaceTexture surfaceTexture); 616 | 617 | } 618 | 619 | public void onResume() { 620 | if (curState == PlayerViewState.Playying) { 621 | setSurfaceTexture(); 622 | } 623 | } 624 | 625 | private void setSurfaceTexture() { 626 | if (null == mTextureView.getSurfaceTexture()) { 627 | Log.d(TAG, "setSurfaceTexture: "); 628 | mTextureView.setSurfaceTextureListener(mSurfaceTextureListener); 629 | mTextureView.setSurfaceTexture(mSurfaceTexture); 630 | } 631 | addTextureView(); 632 | } 633 | 634 | public SurfaceTexture getSurfaceTexture() { 635 | return mTextureView.getSurfaceTexture(); 636 | } 637 | 638 | @Override 639 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 640 | setMeasuredDimension(getDefaultSize(0, widthMeasureSpec), 641 | getDefaultSize(0, heightMeasureSpec)); 642 | 643 | int childWidthSize = getMeasuredWidth(); 644 | // 高度和宽度一样 645 | heightMeasureSpec = widthMeasureSpec = MeasureSpec.makeMeasureSpec( 646 | childWidthSize, MeasureSpec.EXACTLY); 647 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 648 | } 649 | 650 | public interface PqVideoOnclickListener { 651 | void onClick(); 652 | } 653 | } 654 | -------------------------------------------------------------------------------- /app/src/main/java/com/chezi008/videosurveillance/viedeoview/PqVideoPlayerZoomIn.java: -------------------------------------------------------------------------------- 1 | package com.chezi008.videosurveillance.viedeoview; 2 | 3 | import android.content.Context; 4 | import android.graphics.Color; 5 | import android.graphics.SurfaceTexture; 6 | import android.os.SystemClock; 7 | import android.support.annotation.NonNull; 8 | import android.support.annotation.Nullable; 9 | import android.text.TextUtils; 10 | import android.util.AttributeSet; 11 | import android.util.Log; 12 | import android.view.Gravity; 13 | import android.view.MotionEvent; 14 | import android.view.TextureView; 15 | import android.view.View; 16 | import android.view.ViewGroup; 17 | import android.widget.FrameLayout; 18 | import android.widget.ImageView; 19 | import android.widget.LinearLayout; 20 | import android.widget.RelativeLayout; 21 | import android.widget.TextView; 22 | 23 | import com.chezi008.videosurveillance.R; 24 | import com.chezi008.videosurveillance.utils.DensityUtils; 25 | 26 | /** 27 | * @author :chezi008 on 2017/12/28 16:30 28 | * @description : 29 | * @email :chezi008@163.com 30 | */ 31 | 32 | public class PqVideoPlayerZoomIn extends FrameLayout implements View.OnClickListener { 33 | private String TAG = getClass().getSimpleName(); 34 | 35 | private RelativeLayout mTextureParent, mRlBottomBar; 36 | private TextureView mTextureView; 37 | 38 | private ImageView ivResAdd; 39 | 40 | private TextView mTvPath, mTvLoss; 41 | 42 | private PqZoomOutVideoListener mPqZoomOutVideoListener; 43 | private SurfaceTexture mSurfaceTexture; 44 | 45 | public PqVideoPlayerZoomIn(@NonNull Context context) { 46 | this(context, null); 47 | } 48 | 49 | 50 | public PqVideoPlayerZoomIn(@NonNull Context context, @Nullable AttributeSet attrs) { 51 | this(context, attrs, 0); 52 | } 53 | 54 | public PqVideoPlayerZoomIn(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 55 | super(context, attrs, defStyleAttr); 56 | initView(); 57 | } 58 | 59 | private void initView() { 60 | //设置屏幕常亮 61 | setKeepScreenOn(true); 62 | setBackgroundColor(getResources().getColor(R.color.colorPrimary)); 63 | addTextureParent(); 64 | // addTextureView(); 65 | addResIconView(); 66 | addBottomView(); 67 | } 68 | 69 | private void addBottomView() { 70 | //底部白色背景 71 | LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 72 | params.gravity = Gravity.BOTTOM; 73 | mRlBottomBar = new RelativeLayout(getContext()); 74 | mRlBottomBar.setBackgroundColor(Color.WHITE); 75 | mRlBottomBar.getBackground().setAlpha(128); 76 | mRlBottomBar.setGravity(RelativeLayout.CENTER_VERTICAL); 77 | addView(mRlBottomBar, params); 78 | 79 | mRlBottomBar.setVisibility(VISIBLE); 80 | //全屏 81 | int btnWidth = DensityUtils.sp2px(getContext(), 25); 82 | int btnPadding = DensityUtils.sp2px(getContext(), 3); 83 | RelativeLayout.LayoutParams layoutParamsFullScreen = new RelativeLayout.LayoutParams(btnWidth, btnWidth); 84 | layoutParamsFullScreen.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); 85 | ImageView ivFull = new ImageView(getContext()); 86 | ivFull.setId(R.id.btnFullScreen); 87 | 88 | ivFull.setPadding(btnPadding, btnPadding, btnPadding, btnPadding); 89 | ivFull.setImageResource(R.mipmap.vp_ic_full_screen); 90 | ivFull.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 91 | ivFull.setOnClickListener(this); 92 | mRlBottomBar.addView(ivFull, layoutParamsFullScreen); 93 | //停止 94 | RelativeLayout.LayoutParams layoutParamsStopVideo = new RelativeLayout.LayoutParams(btnWidth, btnWidth); 95 | layoutParamsStopVideo.addRule(RelativeLayout.ALIGN_PARENT_LEFT); 96 | ImageView ivStop = new ImageView(getContext()); 97 | ivStop.setId(R.id.btnStop); 98 | ivStop.setPadding(btnPadding, btnPadding, btnPadding, btnPadding); 99 | ivStop.setImageResource(R.mipmap.vp_ic_stop_play); 100 | ivStop.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 101 | ivStop.setOnClickListener(this); 102 | mRlBottomBar.addView(ivStop, layoutParamsStopVideo); 103 | 104 | //设备路径信息 105 | LinearLayout llPath = new LinearLayout(getContext()); 106 | llPath.setId(R.id.tvDeviceInfo); 107 | mTvPath = new TextView(getContext()); 108 | mTvPath.setTextColor(Color.WHITE); 109 | mTvPath.setTextSize(8); 110 | mTvPath.setSingleLine(true); 111 | mTvPath.setText("轮播字"); 112 | mTvPath.setEllipsize(TextUtils.TruncateAt.MARQUEE); 113 | mTvPath.setMarqueeRepeatLimit(-1); 114 | mTvPath.setSelected(true); 115 | llPath.addView(mTvPath); 116 | 117 | RelativeLayout.LayoutParams layoutParamsDevicePathTextView = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); 118 | layoutParamsDevicePathTextView.setMargins(btnPadding, btnPadding, btnPadding, btnPadding); 119 | layoutParamsDevicePathTextView.addRule(RelativeLayout.RIGHT_OF, R.id.btnStop); 120 | layoutParamsDevicePathTextView.addRule(RelativeLayout.LEFT_OF, R.id.tvLossFrame); 121 | layoutParamsDevicePathTextView.addRule(RelativeLayout.CENTER_VERTICAL); 122 | mRlBottomBar.addView(llPath, layoutParamsDevicePathTextView); 123 | 124 | //丢包率 125 | mTvLoss = new TextView(getContext()); 126 | mTvLoss.setId(R.id.tvLossFrame); 127 | mTvLoss.setTextColor(Color.WHITE); 128 | mTvLoss.setTextSize(8); 129 | mTvLoss.setLines(1); 130 | mTvLoss.setText("0.00%"); 131 | RelativeLayout.LayoutParams rlLoss = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 132 | rlLoss.setMargins(btnPadding, btnPadding, btnPadding, btnPadding); 133 | rlLoss.addRule(RelativeLayout.LEFT_OF, R.id.btnFullScreen); 134 | rlLoss.addRule(RelativeLayout.CENTER_VERTICAL); 135 | mRlBottomBar.addView(mTvLoss, rlLoss); 136 | 137 | // mRlBorderBg.addView(mRlVideoBg, layoutParams); 138 | } 139 | 140 | private void addTextureParent() { 141 | //先添加其父容器 142 | LayoutParams parentParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); 143 | parentParams.gravity = Gravity.CENTER; 144 | 145 | mTextureParent = new RelativeLayout(getContext()); 146 | mTextureParent.setBackgroundColor(Color.BLACK); 147 | mTextureParent.setVisibility(VISIBLE); 148 | addView(mTextureParent, parentParams); 149 | } 150 | 151 | private void addResIconView() { 152 | //添加添加资源图标 153 | LayoutParams layoutParams = new LayoutParams(DensityUtils.sp2px(getContext(), 35), 154 | DensityUtils.sp2px(getContext(), 35)); 155 | layoutParams.gravity = Gravity.CENTER; 156 | ivResAdd = new ImageView(getContext()); 157 | ivResAdd.setImageResource(R.mipmap.vp_ic_res_add); 158 | ivResAdd.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 159 | ivResAdd.setId(R.id.iv_res_add); 160 | ivResAdd.setOnClickListener(this); 161 | addView(ivResAdd, layoutParams); 162 | } 163 | 164 | private void addTextureView() { 165 | //添加渲染视图 166 | if (mTextureView == null) { 167 | mTextureView = new VideoTextureView(getContext()); 168 | mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() { 169 | @Override 170 | public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { 171 | Log.d(TAG, "onSurfaceTextureAvailable: "); 172 | if(mSurfaceTexture!=null){ 173 | mTextureView.setSurfaceTexture(mSurfaceTexture); 174 | } 175 | } 176 | 177 | @Override 178 | public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 179 | 180 | } 181 | 182 | @Override 183 | public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 184 | Log.d(TAG, "onSurfaceTextureDestroyed: "); 185 | mSurfaceTexture = surface; 186 | return false; 187 | } 188 | 189 | @Override 190 | public void onSurfaceTextureUpdated(SurfaceTexture surface) { 191 | 192 | } 193 | }); 194 | } 195 | 196 | if (mTextureParent.getChildCount() > 0) { 197 | throw new IllegalStateException("已经添加了一个子控件了!"); 198 | } 199 | RelativeLayout.LayoutParams LinlayoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, 200 | RelativeLayout.LayoutParams.MATCH_PARENT); 201 | LinlayoutParams.addRule(RelativeLayout.CENTER_IN_PARENT); 202 | mTextureParent.addView(mTextureView, LinlayoutParams); 203 | } 204 | 205 | public void setSurfaceView(SurfaceTexture surfaceView) { 206 | Log.d(TAG, "setSurfaceView: "); 207 | addTextureView(); 208 | mTextureView.setSurfaceTexture(surfaceView); 209 | } 210 | 211 | public void setPqZoomOutVideoListener(PqZoomOutVideoListener mPqZoomOutVideoListener) { 212 | this.mPqZoomOutVideoListener = mPqZoomOutVideoListener; 213 | } 214 | 215 | @Override 216 | public boolean onTouchEvent(MotionEvent event) { 217 | switch (event.getAction()) { 218 | case MotionEvent.ACTION_DOWN: 219 | doubleClick_2(); 220 | return true; 221 | case MotionEvent.ACTION_UP: 222 | return true; 223 | } 224 | return super.onTouchEvent(event); 225 | } 226 | 227 | private long[] mHits = new long[2]; 228 | 229 | private void doubleClick_2() { 230 | System.arraycopy(mHits, 1, mHits, 0, mHits.length - 1); 231 | //获取手机开机时间 232 | mHits[mHits.length - 1] = SystemClock.uptimeMillis(); 233 | if (mHits[mHits.length - 1] - mHits[0] < 500) { 234 | /**双击的业务逻辑*/ 235 | Log.d(TAG, "doubleClick_2: 双击"); 236 | onZoomViewExchange(); 237 | } 238 | } 239 | 240 | private void onZoomViewExchange() { 241 | mTextureParent.removeView(mTextureView); 242 | mPqZoomOutVideoListener.onZoomIn(); 243 | setVisibility(GONE); 244 | } 245 | 246 | @Override 247 | public void onClick(View v) { 248 | switch (v.getId()) { 249 | case R.id.iv_res_add: 250 | break; 251 | } 252 | } 253 | 254 | public interface PqZoomOutVideoListener { 255 | void onZoomIn(); 256 | } 257 | 258 | } 259 | -------------------------------------------------------------------------------- /app/src/main/java/com/chezi008/videosurveillance/viedeoview/VideoContainer.java: -------------------------------------------------------------------------------- 1 | package com.chezi008.videosurveillance.viedeoview; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.SurfaceTexture; 6 | import android.support.annotation.NonNull; 7 | import android.support.annotation.Nullable; 8 | import android.support.v7.widget.GridLayoutManager; 9 | import android.support.v7.widget.RecyclerView; 10 | import android.support.v7.widget.helper.ItemTouchHelper; 11 | import android.util.AttributeSet; 12 | import android.util.Log; 13 | import android.view.Gravity; 14 | import android.view.View; 15 | import android.view.ViewGroup; 16 | import android.widget.FrameLayout; 17 | 18 | import com.chezi008.videosurveillance.R; 19 | import com.chezi008.videosurveillance.VideoGridLayoutManager; 20 | import com.chezi008.videosurveillance.adapter.ContainerAdapter; 21 | 22 | import java.util.ArrayList; 23 | import java.util.Collections; 24 | import java.util.List; 25 | 26 | /** 27 | * @author :chezi008 on 2017/12/29 10:35 28 | * @description : 29 | * @email :chezi008@163.com 30 | */ 31 | 32 | public class VideoContainer extends FrameLayout { 33 | 34 | private String TAG = getClass().getSimpleName(); 35 | private static final int DEFALT_COLUMNS_NUM = 2; 36 | 37 | private VideoRecycler mVideoRecycler; 38 | private ItemTouchHelper mItemTouchHelper; 39 | private VideoGridLayoutManager mGridLayoutManager; 40 | private ContainerAdapter mAdapter; 41 | /** 42 | * 放大后的播放器 43 | */ 44 | private PqVideoPlayerZoomIn mPqVideoPlayerZoomIn; 45 | 46 | private List videoIdList; 47 | 48 | /** 49 | * 控件属性 50 | */ 51 | private int mColumns; 52 | 53 | public VideoContainer(@NonNull Context context) { 54 | this(context, null); 55 | } 56 | 57 | public VideoContainer(@NonNull Context context, @Nullable AttributeSet attrs) { 58 | this(context, attrs, 0); 59 | 60 | } 61 | 62 | public VideoContainer(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 63 | super(context, attrs, defStyleAttr); 64 | TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.VideoRecycler); 65 | mColumns = ta.getInt(R.styleable.VideoRecycler_vc_columns_num, DEFALT_COLUMNS_NUM); 66 | initVariable(); 67 | initView(); 68 | } 69 | 70 | private void initVariable() { 71 | videoIdList = new ArrayList<>(); 72 | 73 | for (int i = 0; i < mColumns * mColumns; i++) { 74 | PqVideoPlayer pqVideoPlayer = new PqVideoPlayer(getContext()); 75 | pqVideoPlayer.setId(1000 + i); 76 | videoIdList.add(pqVideoPlayer); 77 | } 78 | 79 | mPqVideoPlayerZoomIn = new PqVideoPlayerZoomIn(getContext()); 80 | mGridLayoutManager = new VideoGridLayoutManager(getContext(), mColumns); 81 | mAdapter = new ContainerAdapter(videoIdList); 82 | mVideoRecycler = new VideoRecycler(getContext()); 83 | 84 | mAdapter.setVideoPlayerListener(new PqVideoPlayer.VideoPlayerListener() { 85 | @Override 86 | public void onZoomInView(PqVideoPlayer videoPlayer) { 87 | videoPlayer.setVisibility(GONE); 88 | mPqVideoPlayerZoomIn.setVisibility(View.VISIBLE); 89 | mPqVideoPlayerZoomIn.setPqZoomOutVideoListener(videoPlayer); 90 | } 91 | 92 | @Override 93 | public void onChangeSurface(SurfaceTexture surfaceTexture) { 94 | mPqVideoPlayerZoomIn.setSurfaceView(surfaceTexture); 95 | } 96 | }); 97 | 98 | mItemTouchHelper = new ItemTouchHelper(new ItemTouchHelper.Callback() { 99 | @Override 100 | public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { 101 | Log.d(TAG, "getMovementFlags: "); 102 | int dragFlags; 103 | int swipeFlags; 104 | if (recyclerView.getLayoutManager() instanceof GridLayoutManager) { 105 | dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | 106 | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT; 107 | swipeFlags = 0; 108 | } else { 109 | dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN; 110 | swipeFlags = 0; 111 | } 112 | return makeMovementFlags(dragFlags, swipeFlags); 113 | } 114 | 115 | @Override 116 | public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { 117 | Log.d(TAG, "onMove: "); 118 | int fromPosition = viewHolder.getAdapterPosition(); 119 | int toPosition = target.getAdapterPosition(); 120 | if (fromPosition < toPosition) { 121 | for (int i = fromPosition; i < toPosition; i++) { 122 | Collections.swap(videoIdList, i, i + 1); 123 | } 124 | } else { 125 | for (int i = fromPosition; i < toPosition; i--) { 126 | Collections.swap(videoIdList, i, i - 1); 127 | } 128 | } 129 | mAdapter.notifyItemMoved(fromPosition, toPosition); 130 | return true; 131 | } 132 | 133 | @Override 134 | public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { 135 | Log.d(TAG, "onSwiped: "); 136 | } 137 | }); 138 | } 139 | 140 | private void initView() { 141 | 142 | mVideoRecycler.setLayoutManager(mGridLayoutManager); 143 | mVideoRecycler.setAdapter(mAdapter); 144 | //容器可拖拽 145 | mItemTouchHelper.attachToRecyclerView(mVideoRecycler); 146 | 147 | //添加放大的播放器 148 | FrameLayout.LayoutParams zoomInParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT 149 | , ViewGroup.LayoutParams.MATCH_PARENT); 150 | zoomInParams.gravity = Gravity.CENTER; 151 | 152 | //添加视频列表 153 | addView(mVideoRecycler, zoomInParams); 154 | //添加放大的播放器 155 | mPqVideoPlayerZoomIn.setVisibility(GONE); 156 | addView(mPqVideoPlayerZoomIn, zoomInParams); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /app/src/main/java/com/chezi008/videosurveillance/viedeoview/VideoRecycler.java: -------------------------------------------------------------------------------- 1 | package com.chezi008.videosurveillance.viedeoview; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.util.AttributeSet; 7 | 8 | import com.chezi008.videosurveillance.R; 9 | 10 | /** 11 | * @author :chezi008 on 2017/12/29 10:37 12 | * @description : 13 | * @email :chezi008@163.com 14 | */ 15 | 16 | public class VideoRecycler extends RecyclerView { 17 | 18 | public VideoRecycler(Context context) { 19 | this(context, null); 20 | } 21 | 22 | public VideoRecycler(Context context, @Nullable AttributeSet attrs) { 23 | this(context, attrs, 0); 24 | } 25 | 26 | public VideoRecycler(Context context, @Nullable AttributeSet attrs, int defStyle) { 27 | super(context, attrs, defStyle); 28 | initView(); 29 | } 30 | 31 | private void initView() { 32 | setBackgroundColor(getResources().getColor(R.color.colorAccent)); 33 | } 34 | 35 | @Override 36 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 37 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 38 | int widthMode = MeasureSpec.getMode(widthMeasureSpec); 39 | int heightMode = MeasureSpec.getMode(heightMeasureSpec); 40 | int widthSize = MeasureSpec.getSize(widthMeasureSpec); 41 | int heightSize = MeasureSpec.getSize(heightMeasureSpec); 42 | int width = 0; 43 | int height = 0; 44 | 45 | width = widthSize; 46 | switch (heightMode) { 47 | case MeasureSpec.EXACTLY: 48 | height = heightSize; 49 | break; 50 | default: 51 | height = widthSize; 52 | break; 53 | } 54 | if (height > heightSize) { 55 | height = heightSize; 56 | } 57 | setMeasuredDimension(width, height); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/com/chezi008/videosurveillance/viedeoview/VideoTextureView.java: -------------------------------------------------------------------------------- 1 | package com.chezi008.videosurveillance.viedeoview; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.TextureView; 6 | 7 | /** 8 | * 描述: 9 | * 作者:chezi008 on 2017/6/1 16:11 10 | * 邮箱:chezi008@163.com 11 | */ 12 | 13 | public class VideoTextureView extends TextureView { 14 | public String TAG = getClass().getSimpleName(); 15 | 16 | public VideoTextureView(Context context) { 17 | this(context, null); 18 | } 19 | 20 | public VideoTextureView(Context context, AttributeSet attrs) { 21 | super(context, attrs); 22 | } 23 | 24 | @Override 25 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 26 | int widthMode = MeasureSpec.getMode(widthMeasureSpec); 27 | int heightMode = MeasureSpec.getMode(heightMeasureSpec); 28 | int widthSize = MeasureSpec.getSize(widthMeasureSpec); 29 | int heightSize = MeasureSpec.getSize(heightMeasureSpec); 30 | 31 | int width = 0; 32 | int height = 0; 33 | // Log.d(TAG, "onMeasure: widthMode-->" + widthMode + "heightMode-->" + heightMode); 34 | // if (widthMode == MeasureSpec.EXACTLY) { 35 | // // Parent has told us how big to be. So be it. 36 | // width = widthSize; 37 | // } else if (widthMode == MeasureSpec.AT_MOST) { 38 | // width = 200; 39 | // } else { 40 | // width = widthSize; 41 | // Log.d(TAG, "onMeasure: width else"); 42 | // } 43 | width = widthSize; 44 | height = width * 3 / 4; 45 | if (height > heightSize) { 46 | height = heightSize; 47 | } 48 | 49 | // if (heightMode == MeasureSpec.EXACTLY) { 50 | // // Parent has told us how big to be. So be it. 51 | // height = heightSize; 52 | // } else if (widthMode == MeasureSpec.AT_MOST) { 53 | // height = width * 3 / 4; 54 | // } else { 55 | // height = heightSize; 56 | // } 57 | // Log.d(TAG, "onMeasure: width-->" + width + "height-->" + height); 58 | setMeasuredDimension(width, height); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_full_screen.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 |