├── .gitignore ├── .idea ├── .gitignore ├── compiler.xml ├── encodings.xml ├── gradle.xml ├── jarRepositories.xml ├── misc.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── libs │ ├── ijkplayer-java-release.aar │ └── libvlc-all-4.0.0-eap8.aar ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── company │ │ └── ijkpalyer │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── company │ │ │ └── ijkpalyer │ │ │ ├── ActivityVLC4.java │ │ │ ├── MainActivity.java │ │ │ ├── dk │ │ │ ├── IjkPlayer.java │ │ │ ├── IjkPlayerFactory.java │ │ │ └── RawDataSourceProvider.java │ │ │ ├── ijk │ │ │ ├── IjkPlayerActivity.java │ │ │ └── MyIjkPlayerActivity.java │ │ │ └── utils │ │ │ ├── FileUtil.java │ │ │ └── LogUtils.java │ ├── jniLibs │ │ ├── arm64-v8a │ │ │ ├── libijkffmpeg.so │ │ │ ├── libijkplayer.so │ │ │ └── libijksdl.so │ │ ├── armeabi-v7a │ │ │ ├── libijkffmpeg.so │ │ │ ├── libijkplayer.so │ │ │ └── libijksdl.so │ │ ├── armeabi │ │ │ ├── libijkffmpeg.so │ │ │ ├── libijkplayer.so │ │ │ └── libijksdl.so │ │ ├── x86 │ │ │ ├── libijkffmpeg.so │ │ │ ├── libijkplayer.so │ │ │ └── libijksdl.so │ │ └── x86_64 │ │ │ ├── libijkffmpeg.so │ │ │ ├── libijkplayer.so │ │ │ └── libijksdl.so │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_ijk.xml │ │ ├── activity_main.xml │ │ ├── activity_my_ijk_player.xml │ │ └── activity_vlc4.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 │ │ └── vlc_logo.jpg │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values-night │ │ └── themes.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ └── java │ └── com │ └── company │ └── ijkpalyer │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 22 | 23 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | 34 | 35 | 39 | 40 | 44 | 45 | 49 | 50 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 移步:VLC录像截图Demo地址:[VlcRecordPlayer](https://github.com/ILoveLin/VlcRecordPlayer) 2 | ## 移步:VLC录像截图Demo地址:[VlcRecordPlayer](https://github.com/ILoveLin/VlcRecordPlayer) 3 | 4 | #### 此Demo(不维护了): 支持录像,截图功能(点播不支持录像,只支持直播噢!),支持RTSP,RTMP,HTTP等等在线直播(支持所有CPU架构),您可以做二次开发添加其他功能!! 5 | #### 此Demo(不维护了): 支持录像,截图功能(点播不支持录像,只支持直播噢!),支持RTSP,RTMP,HTTP等等在线直播(支持所有CPU架构),您可以做二次开发添加其他功能!! 6 | #### 此Demo(不维护了): 支持录像,截图功能(点播不支持录像,只支持直播噢!),支持RTSP,RTMP,HTTP等等在线直播(支持所有CPU架构),您可以做二次开发添加其他功能!! 7 | * 8 | #### 此Demo(不维护了): 因为此项目只支持直播的时候,才能录制视频~~。我现在使用VLC,作为播放器来实现,录像,截图功能(点播,直播都支持!) 请移步:VLC录像截图Demo地址:[VlcRecordPlayer](https://github.com/ILoveLin/VlcRecordPlayer) 9 | #### 此Demo(不维护了): 因为此项目只支持直播的时候,才能录制视频。我现在使用VLC,作为播放器来实现,录像,截图功能(点播,直播都支持!) 请移步:VLC录像截图Demo地址:[VlcRecordPlayer](https://github.com/ILoveLin/VlcRecordPlayer) 10 | #### 此Demo(不维护了): 因为此项目只支持直播的时候,才能录制视频。我现在使用VLC,作为播放器来实现,录像,截图功能(点播,直播都支持!) 请移步:VLC录像截图Demo地址:[VlcRecordPlayer](https://github.com/ILoveLin/VlcRecordPlayer) 11 | * 12 | #### 如果帮助的到了您,请您不要吝啬你的Star,谢谢您的点赞(Star),3Q 13 | #### 如果帮助的到了您,请您不要吝啬你的Star,谢谢您的点赞(Star),3Q 14 | #### 如果帮助的到了您,请您不要吝啬你的Star,谢谢您的点赞(Star),3Q 15 | 16 | #### VLC录像截图Demo地址:[VlcRecordPlayer](https://github.com/ILoveLin/VlcRecordPlayer) 17 | * 18 | * 19 | #### 百度ijkplayer录像 截图功能有很多教程,但是很多都是会,崩溃或者闪退,等等问题!!!!所以在此我觉得把这个Demo开源出来,希望能帮助到需要的人(不维护了)! 20 | #### VLC录像截图Demo地址:[VlcRecordPlayer](https://github.com/ILoveLin/VlcRecordPlayer) 21 | 22 | 23 | ## 此项目已经停止更新,目前使用VLC做二次开发(点播,直播,都能实现截图,录像功能,健壮性更强)VLC录像截图Demo地址:[VlcRecordPlayer](https://github.com/ILoveLin/VlcRecordPlayer) 24 | ## 此项目已经停止更新,目前使用VLC做二次开发(点播,直播,都能实现截图,录像功能,健壮性更强)VLC录像截图Demo地址:[VlcRecordPlayer](https://github.com/ILoveLin/VlcRecordPlayer) 25 | ## 此项目已经停止更新,目前使用VLC做二次开发(点播,直播,都能实现截图,录像功能,健壮性更强)VLC录像截图Demo地址:[VlcRecordPlayer](https://github.com/ILoveLin/VlcRecordPlayer) 26 | 27 | #### 项目使用 28 | * 备注:您不需要编译任何东西,只需要下载Demo,拷贝main目录下的 jniLibs包下的所有so依赖库 和app/libs目录下 ijkplayer-java-release.aar (这个是ijk工程里面的java代码)到你自己的项目里面. 29 | * 30 | * 第一步:(只支持直播噢!) 31 | * 只需要下载Demo,拷贝main目录下的 jniLibs包下的所有so依赖库 和app目录下 ijkplayer-java-release.aar (这个是ijk工程里面的java代码) 32 | * 您在app的build.gradle中android节点里面找到-->defaultConfig节点--->然后添加 33 | * ndk {abiFilters 'arm64-v8a','armeabi' ,'armeabi-v7a', 'x86', 'x86_64'} // 设置支持的SO库架构 34 | * 然后在 dependencies中添加 implementation files('libs/ijkplayer-java-release.aar') //第一步拷贝的aar的依赖 35 | * 36 | * 第二步:(记得动态获取权限在调用以下API,不然会失败哦) 37 | * 项目中如何使用?(只支持直播噢!) 38 | * 39 | * 直播中:开始录像: mPlayer.startRecord(mRecordPath); //mRecordPath 是录像存入的文件路径 40 | * 直播中:结束录像: mPlayer.stopRecord(); 41 | * 直播中:截图: Bitmap srcBitmap = Bitmap.createBitmap(1920,1080, Bitmap.Config.ARGB_8888); mPlayer.getCurrentFrame(srcBitmap); //PS:异步的哦,记得开线程!!!! 42 | * 43 | 44 | * 重要的事情说三遍!!! 45 | * What ?还是不懂?请您下载Demo,查看MainActivity里面的简单代码,我相信您一看就会! 46 | * What ?还是不懂?请您下载Demo,查看MainActivity里面的简单代码,我相信您一看就会! 47 | * What ?还是不懂?请您下载Demo,查看MainActivity里面的简单代码,我相信您一看就会! 48 | * 49 | #### 简单说明 50 | * 网上有很多修改底层C的代码,实现录像和截图,但是好多都会程序奔溃和闪退,这个Demo做了优化 51 | 52 | * 修改底层C代码增加了三个native的API: 53 | * 我们使用的其实都是底层调用这三个方法: 54 | * public native int startRecord(String var1); //开始录像 55 | * public native int stopRecord(); //结束录像 56 | * public native boolean getCurrentFrame(Bitmap var1); //截图 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | *.iml 3 | .gradle 4 | /local.properties 5 | /.idea/caches 6 | /.idea/libraries 7 | /.idea/modules.xml 8 | /.idea/workspace.xml 9 | /.idea/navEditor.xml 10 | /.idea/assetWizardSettings.xml 11 | .DS_Store 12 | /build 13 | /captures 14 | .externalNativeBuild 15 | .cxx 16 | /gradle/ 17 | /.idea/ 18 | /gradle.properties 19 | *.apk 20 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | } 4 | 5 | android { 6 | compileSdkVersion 30 7 | buildToolsVersion "30.0.2" 8 | 9 | defaultConfig { 10 | applicationId "com.company.myapplication" 11 | minSdkVersion 21 12 | targetSdkVersion 30 13 | versionCode 1 14 | versionName "1.0" 15 | ndk { 16 | // 设置支持的SO库架构 17 | // abiFilters 'armeabi-v7a' 18 | abiFilters 'arm64-v8a','armeabi' ,'armeabi-v7a', 'x86', 'x86_64' 19 | } 20 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 21 | } 22 | 23 | buildTypes { 24 | release { 25 | minifyEnabled false 26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 27 | } 28 | } 29 | compileOptions.encoding = "UTF-8" 30 | 31 | compileOptions { 32 | sourceCompatibility JavaVersion.VERSION_1_8 33 | targetCompatibility JavaVersion.VERSION_1_8 34 | } 35 | 36 | // sourceSets.main { 37 | // jniLibs.srcDirs = ['libs'] 38 | // } 39 | } 40 | 41 | dependencies { 42 | 43 | implementation files('libs/ijkplayer-java-release.aar') 44 | implementation files('libs/libvlc-all-4.0.0-eap8.aar') 45 | implementation 'com.hjq:xxpermissions:9.6' 46 | // # 必选,内部默认使用系统mediaplayer进行解码 47 | // implementation 'tv.danmaku.ijk.media:ijkplayer-java:0.8.1' 48 | // implementation 'tv.danmaku.ijk.media:ijkplayer-armv7a:0.8.1' 49 | // implementation 'tv.danmaku.ijk.media:ijkplayer-exo:0.8.1' 50 | 51 | 52 | //// # required, enough for most devices. 53 | // implementation 'tv.danmaku.ijk.media:ijkplayer-java:0.8.8' 54 | // implementation 'tv.danmaku.ijk.media:ijkplayer-armv7a:0.8.8' 55 | // 56 | //// # Other ABIs: optional 57 | // implementation 'tv.danmaku.ijk.media:ijkplayer-armv5:0.8.8' 58 | // implementation 'tv.danmaku.ijk.media:ijkplayer-arm64:0.8.8' 59 | // implementation 'tv.danmaku.ijk.media:ijkplayer-x86:0.8.8' 60 | // implementation 'tv.danmaku.ijk.media:ijkplayer-x86_64:0.8.8' 61 | // 62 | //// # ExoPlayer as IMediaPlayer: optional, experimental 63 | // implementation 'tv.danmaku.ijk.media:ijkplayer-exo:0.8.8' 64 | 65 | 66 | implementation 'androidx.appcompat:appcompat:1.2.0' 67 | implementation 'com.google.android.material:material:1.2.1' 68 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4' 69 | testImplementation 'junit:junit:4.+' 70 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 71 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 72 | implementation 'xyz.doikki.android.dkplayer:dkplayer-java:3.3.7' 73 | 74 | } -------------------------------------------------------------------------------- /app/libs/ijkplayer-java-release.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ILoveLin/IjkRecordPlayer/ea8f5c029e8b1fe15c85bdfe964b453e625564cb/app/libs/ijkplayer-java-release.aar -------------------------------------------------------------------------------- /app/libs/libvlc-all-4.0.0-eap8.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ILoveLin/IjkRecordPlayer/ea8f5c029e8b1fe15c85bdfe964b453e625564cb/app/libs/libvlc-all-4.0.0-eap8.aar -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/company/ijkpalyer/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.company.ijkpalyer; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | assertEquals("com.company.myapplication", appContext.getPackageName()); 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/company/ijkpalyer/ActivityVLC4.java: -------------------------------------------------------------------------------- 1 | package com.company.ijkpalyer; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.graphics.SurfaceTexture; 6 | import android.net.Uri; 7 | import android.os.Bundle; 8 | import android.os.Environment; 9 | import android.util.Log; 10 | import android.view.Surface; 11 | import android.view.SurfaceView; 12 | import android.view.TextureView; 13 | import android.view.View; 14 | import android.widget.Button; 15 | import android.widget.Toast; 16 | 17 | import androidx.appcompat.app.AppCompatActivity; 18 | 19 | import com.hjq.permissions.OnPermissionCallback; 20 | import com.hjq.permissions.Permission; 21 | import com.hjq.permissions.XXPermissions; 22 | 23 | import org.videolan.libvlc.LibVLC; 24 | import org.videolan.libvlc.Media; 25 | import org.videolan.libvlc.MediaPlayer; 26 | import org.videolan.libvlc.interfaces.IVLCVout; 27 | 28 | import java.io.File; 29 | import java.text.SimpleDateFormat; 30 | import java.util.ArrayList; 31 | import java.util.Date; 32 | import java.util.List; 33 | import java.util.Locale; 34 | 35 | //import org.videolan.libvlc.media.MediaPlayer; 36 | 37 | /** 38 | * vlc 播放器 4.0.0 版本还是测试版本 39 | * 播放视频会卡顿 仅供参考 40 | */ 41 | public class ActivityVLC4 extends AppCompatActivity implements View.OnClickListener { 42 | private static final String TAG = "MainActivity"; 43 | //苹果公司点播的的流地址 44 | // public static final String path = "http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear2/prog_index.m3u8"; 45 | //我自己公司rtsp的流地址 46 | // public static String path = "http://192.168.67.210:3333/api/stream/audio?session=123456"; 47 | public static String path = "http://219.151.31.38/liveplay-kk.rtxapp.com/live/program/live/hnwshd/4000000/mnf.m3u8"; 48 | 49 | // public static final String path = "rtsp://root:root@192.168.66.31:7788/session0.mpg"; 50 | private SurfaceView texture_video; 51 | private LibVLC mLibVLC = null; 52 | private MediaPlayer mMediaPlayer = null; 53 | private TextureView textureView; 54 | private Surface mSurface; 55 | private SurfaceView surfaceView; 56 | private Button btn_end_record; 57 | private Button btn_frame; 58 | private Button btn_start_record; 59 | private String mRecordPath; 60 | private Button start_live; 61 | 62 | 63 | @Override 64 | protected void onCreate(Bundle savedInstanceState) { 65 | super.onCreate(savedInstanceState); 66 | setContentView(R.layout.activity_vlc4); 67 | // textureView = findViewById(R.id.texture_video); 68 | textureView = findViewById(R.id.texture_view); 69 | textureView.setSurfaceTextureListener(listener); 70 | 71 | btn_start_record = findViewById(R.id.btn_start_record); 72 | btn_end_record = findViewById(R.id.btn_end_record); 73 | start_live = findViewById(R.id.start_live); 74 | btn_frame = findViewById(R.id.btn_frame); 75 | start_live.setOnClickListener(this); 76 | btn_start_record.setOnClickListener(this); 77 | btn_end_record.setOnClickListener(this); 78 | btn_frame.setOnClickListener(this); 79 | requestPermission(); 80 | 81 | } 82 | 83 | @Override 84 | public void onClick(View v) { 85 | switch (v.getId()) { 86 | case R.id.start_live: //开始 87 | mMediaPlayer.play(); 88 | Toast.makeText(this, "开始-播放", Toast.LENGTH_SHORT).show(); 89 | break; 90 | case R.id.btn_start_record: //开始 91 | ///storage/emulated/0/MyMovies/2023-03-10 14:42:50.mp4 92 | ///storage/emulated/0/MyMovies/2023-03-10 14:49:33.mp4 93 | Toast.makeText(this, "开始录像", Toast.LENGTH_SHORT).show(); 94 | File file = new File(Environment.getExternalStorageDirectory() + "/MyMovies"); 95 | // /storage/emulated/0/RecordVideos 96 | Log.e(TAG, "===file.exists()===" + file.exists()); 97 | Log.e(TAG, "===file.mkdirs()===" + file.mkdirs()); 98 | Log.e(TAG, "===file.mkdirs()=file==" + file.getAbsolutePath()); 99 | if (!file.exists()) { 100 | file.mkdirs(); 101 | Log.e(TAG, "===000===" + file.exists()); 102 | } 103 | String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(new Date(System.currentTimeMillis())); 104 | String s = file.getAbsolutePath() + "/" + date + ".mp4"; 105 | Log.e(TAG, "===s===" + s); 106 | 107 | mRecordPath = s; 108 | //没有找到结束 的方法,所以不知道录像是否成功 109 | mMediaPlayer.record(mRecordPath); 110 | break; 111 | case R.id.btn_end_record: //结束 112 | 113 | scanFile(this, mRecordPath); 114 | Toast.makeText(this, "结束录像", Toast.LENGTH_SHORT).show(); 115 | 116 | break; 117 | case R.id.btn_frame: //截图 118 | 119 | Toast.makeText(this, "截图", Toast.LENGTH_SHORT).show(); 120 | 121 | 122 | break; 123 | 124 | 125 | } 126 | } 127 | 128 | //刷新相册,出现刚刚录像视频 129 | public static void scanFile(Context context, String filePath) { 130 | Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); 131 | intent.setData(Uri.fromFile(new File(filePath))); 132 | context.sendBroadcast(intent); 133 | } 134 | 135 | @Override 136 | protected void onDestroy() { 137 | super.onDestroy(); 138 | mMediaPlayer.release(); 139 | mLibVLC.release(); 140 | } 141 | 142 | @Override 143 | protected void onStart() { 144 | super.onStart(); 145 | 146 | 147 | } 148 | 149 | @Override 150 | protected void onStop() { 151 | super.onStop(); 152 | 153 | mMediaPlayer.stop(); 154 | mMediaPlayer.detachViews(); 155 | } 156 | 157 | 158 | private TextureView.SurfaceTextureListener listener = new TextureView.SurfaceTextureListener() { 159 | @Override 160 | public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { 161 | createPlayer(); 162 | // mSurface = new Surface(surface); 163 | // mMediaPlayer.setSurface(mSurface); 164 | } 165 | 166 | @Override 167 | public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 168 | 169 | } 170 | 171 | @Override 172 | public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 173 | textureView.setSurfaceTextureListener(null); 174 | textureView = null; 175 | mSurface = null; 176 | return true; 177 | } 178 | 179 | @Override 180 | public void onSurfaceTextureUpdated(SurfaceTexture surface) { 181 | 182 | } 183 | }; 184 | 185 | private void createPlayer() { 186 | 187 | final ArrayList args = new ArrayList<>();//VLC参数 188 | 189 | args.add("--rtsp-tcp");//强制rtsp-tcp,加快加载视频速度 190 | 191 | args.add("--aout=opensles"); 192 | 193 | args.add("--audio-time-stretch"); 194 | 195 | 196 | //args.add("--sub-source=marq{marquee=\"%Y-%m-%d,%H:%M:%S\",position=10,color=0xFF0000,size=40}");//这行是可以再vlc窗口右下角添加当前时间的 197 | 198 | args.add("-vvv"); 199 | 200 | mLibVLC = new LibVLC(this, args); 201 | mMediaPlayer = new MediaPlayer(mLibVLC); 202 | // Rect surfaceFrame = textureView.getHolder().getSurfaceFrame(); 203 | 204 | //设置vlc视频铺满布局 205 | // 206 | //mMediaPlayer.getVLCVout().setWindowSize(layout_video.getWidth(), layout_video.getHeight());//宽,高 播放窗口的大小 207 | // 208 | //mMediaPlayer.setAspectRatio(layout_video.getWidth()+":"+layout_video.getHeight());//宽,高 画面大小 209 | 210 | mMediaPlayer.setScale(0);//这行必须加,为了让视图填满布局 211 | 212 | //添加视图 213 | 214 | IVLCVout vout = mMediaPlayer.getVLCVout(); 215 | 216 | vout.setVideoView(textureView); 217 | 218 | vout.attachViews(); 219 | 220 | Uri uri = Uri.parse(path);//rtsp流地址或其他流地址//"https://media.w3.org/2010/05/sintel/trailer.mp4" 221 | 222 | final Media media = new Media(mLibVLC, uri); 223 | 224 | int cache = 1000; 225 | 226 | media.addOption(":network-caching=" + cache); 227 | 228 | media.addOption(":file-caching=" + cache); 229 | 230 | media.addOption(":live-cacheing=" + cache); 231 | 232 | media.addOption(":sout-mux-caching=" + cache); 233 | 234 | media.addOption(":codec=mediacodec,iomx,all"); 235 | 236 | mMediaPlayer.setMedia(media);// 237 | 238 | media.setHWDecoderEnabled(false, false);//设置后才可以录制和截屏,这行必须放在mMediaPlayer.setMedia(media)后面,因为setMedia会设置setHWDecoderEnabled为true 239 | 240 | mMediaPlayer.setEventListener(new MediaPlayer.EventListener() { 241 | @Override 242 | public void onEvent(MediaPlayer.Event event) { 243 | if (event.type == MediaPlayer.Event.Playing) { 244 | Log.w("main", "正在播放"); 245 | } else { 246 | 247 | } 248 | 249 | } 250 | }); 251 | } 252 | 253 | private void requestPermission() { 254 | 255 | XXPermissions.with(this) 256 | // 不适配 Android 11 可以这样写 257 | //.permission(Permission.Group.STORAGE) 258 | // 适配 Android 11 需要这样写,这里无需再写 Permission.Group.STORAGE 259 | .permission(Permission.MANAGE_EXTERNAL_STORAGE) 260 | .request(new OnPermissionCallback() { 261 | 262 | @Override 263 | public void onGranted(List permissions, boolean all) { 264 | if (all) { 265 | Toast.makeText(ActivityVLC4.this, "获取存储权限成功", Toast.LENGTH_SHORT).show(); 266 | } 267 | } 268 | 269 | @Override 270 | public void onDenied(List permissions, boolean never) { 271 | if (never) { 272 | Toast.makeText(ActivityVLC4.this, "被永久拒绝授权,请手动授予存储权限", Toast.LENGTH_SHORT).show(); 273 | 274 | // 如果是被永久拒绝就跳转到应用权限系统设置页面 275 | XXPermissions.startPermissionActivity(ActivityVLC4.this, permissions); 276 | } else { 277 | Toast.makeText(ActivityVLC4.this, "获取存储权限失败", Toast.LENGTH_SHORT).show(); 278 | } 279 | } 280 | }); 281 | 282 | 283 | // XXPermissions.with(MainActivity.this) 284 | // // 不适配 Android 11 可以这样写 285 | // //.permission(Permission.Group.STORAGE) 286 | // // 适配 Android 11 需要这样写,这里无需再写 Permission.Group.STORAGE 287 | //// .permission(Permission.MANAGE_EXTERNAL_STORAGE) 288 | // .permission(Permission.WRITE_EXTERNAL_STORAGE) 289 | // .permission(Permission.READ_EXTERNAL_STORAGE) 290 | // .request(new OnPermission() { 291 | // 292 | // @Override 293 | // public void hasPermission(List granted, boolean all) { 294 | // if (all) { 295 | //// showToast("获取存储权限成功"); 296 | // } 297 | // } 298 | // 299 | // @Override 300 | // public void noPermission(List denied, boolean never) { 301 | // if (never) { 302 | //// showToast("被永久拒绝授权,请手动授予存储权限"); 303 | // // 如果是被永久拒绝就跳转到应用权限系统设置页面 304 | // XXPermissions.startPermissionActivity(MainActivity.this, denied); 305 | // } else { 306 | //// showToast("获取存储权限失败"); 307 | // } 308 | // } 309 | // }); 310 | 311 | } 312 | // //截图 313 | // public Bitmap capture() { 314 | // Bitmap srcBitmap = Bitmap.createBitmap(1920, 315 | // 1080, Bitmap.Config.ARGB_8888); 316 | // mPlayer.getCurrentFrame(srcBitmap); 317 | // return srcBitmap; 318 | // } 319 | } 320 | -------------------------------------------------------------------------------- /app/src/main/java/com/company/ijkpalyer/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.company.ijkpalyer; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | 7 | import androidx.appcompat.app.AppCompatActivity; 8 | 9 | import com.company.ijkpalyer.ijk.IjkPlayerActivity; 10 | import com.company.ijkpalyer.ijk.MyIjkPlayerActivity; 11 | 12 | /** 13 | * 原版ijk,aar的录像,没有集成DK播放器 14 | */ 15 | public class MainActivity extends AppCompatActivity { 16 | private static final String TAG = "MainActivity"; 17 | //苹果公司点播的的流地址 18 | // public static final String path = "http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear2/prog_index.m3u8"; 19 | //我自己公司rtsp的流地址 20 | // public static final String path = "rtsp://root:root@192.168.66.31:7788/session0.mpg"; 21 | // public static String path = "http://219.151.31.38/liveplay-kk.rtxapp.com/live/program/live/hnwshd/4000000/mnf.m3u8"; 22 | // public static String path = "http://192.168.67.210:3333/api/stream/video?session=123456"; 23 | public static String path = "http://220.161.87.62:8800/hls/0/index.m3u8"; 24 | 25 | 26 | @Override 27 | protected void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | setContentView(R.layout.activity_main); 30 | //跳转DKPlayer播放器--切换成自己自定义可以录像的ijk内核 31 | findViewById(R.id.go_record_ijkdkplaeyr).setOnClickListener(new View.OnClickListener() { 32 | @Override 33 | public void onClick(View v) { 34 | Intent intent = new Intent(MainActivity.this, IjkPlayerActivity.class); 35 | startActivity(intent); 36 | } 37 | }); 38 | //自己写的ijk测试播放器 39 | findViewById(R.id.go_my_ijkdkplaeyr).setOnClickListener(new View.OnClickListener() { 40 | @Override 41 | public void onClick(View v) { 42 | Intent intent = new Intent(MainActivity.this, MyIjkPlayerActivity.class); 43 | startActivity(intent); 44 | } 45 | }); 46 | 47 | 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/company/ijkpalyer/dk/IjkPlayer.java: -------------------------------------------------------------------------------- 1 | package com.company.ijkpalyer.dk; 2 | 3 | import android.content.ContentResolver; 4 | import android.content.Context; 5 | import android.content.res.AssetFileDescriptor; 6 | import android.net.Uri; 7 | import android.os.Bundle; 8 | import android.text.TextUtils; 9 | import android.util.Log; 10 | import android.view.Surface; 11 | import android.view.SurfaceHolder; 12 | 13 | import java.util.Map; 14 | 15 | import tv.danmaku.ijk.media.player.IMediaPlayer; 16 | import tv.danmaku.ijk.media.player.IjkMediaPlayer; 17 | import tv.danmaku.ijk.media.player.misc.ITrackInfo; 18 | import tv.danmaku.ijk.media.player.misc.IjkTrackInfo; 19 | import xyz.doikki.videoplayer.player.AbstractPlayer; 20 | import xyz.doikki.videoplayer.player.VideoViewManager; 21 | 22 | public class IjkPlayer extends AbstractPlayer implements IMediaPlayer.OnErrorListener, 23 | IMediaPlayer.OnCompletionListener, IMediaPlayer.OnInfoListener, 24 | IMediaPlayer.OnBufferingUpdateListener, IMediaPlayer.OnPreparedListener, 25 | IMediaPlayer.OnVideoSizeChangedListener, IjkMediaPlayer.OnNativeInvokeListener { 26 | 27 | public IjkMediaPlayer mMediaPlayer; 28 | private int mBufferedPercent; 29 | private final Context mAppContext; 30 | 31 | public IjkPlayer(Context context) { 32 | mAppContext = context; 33 | } 34 | //IjkMediaPlayer 35 | public IjkMediaPlayer getIjkMediaPlayer(){ 36 | 37 | Log.e("TAG", "===测试=222==" + mMediaPlayer); 38 | 39 | return mMediaPlayer; 40 | } 41 | 42 | @Override 43 | public void initPlayer() { 44 | mMediaPlayer = new IjkMediaPlayer(); 45 | //native日志 46 | IjkMediaPlayer.native_setLogLevel(VideoViewManager.getConfig().mIsEnableLog ? IjkMediaPlayer.IJK_LOG_INFO : IjkMediaPlayer.IJK_LOG_SILENT); 47 | setOptions(); 48 | mMediaPlayer.setOnErrorListener(this); 49 | mMediaPlayer.setOnCompletionListener(this); 50 | mMediaPlayer.setOnInfoListener(this); 51 | mMediaPlayer.setOnBufferingUpdateListener(this); 52 | mMediaPlayer.setOnPreparedListener(this); 53 | mMediaPlayer.setOnVideoSizeChangedListener(this); 54 | mMediaPlayer.setOnNativeInvokeListener(this); 55 | Log.e("TAG", "===测试=111==" + mMediaPlayer); 56 | 57 | } 58 | 59 | @Override 60 | public void setOptions() { 61 | } 62 | 63 | @Override 64 | public void setDataSource(String path, Map headers) { 65 | try { 66 | Uri uri = Uri.parse(path); 67 | if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(uri.getScheme())) { 68 | RawDataSourceProvider rawDataSourceProvider = RawDataSourceProvider.create(mAppContext, uri); 69 | mMediaPlayer.setDataSource(rawDataSourceProvider); 70 | } else { 71 | //处理UA问题 72 | if (headers != null) { 73 | String userAgent = headers.get("User-Agent"); 74 | if (!TextUtils.isEmpty(userAgent)) { 75 | mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "user_agent", userAgent); 76 | // 移除header中的User-Agent,防止重复 77 | headers.remove("User-Agent"); 78 | } 79 | } 80 | mMediaPlayer.setDataSource(mAppContext, uri, headers); 81 | } 82 | } catch (Exception e) { 83 | mPlayerEventListener.onError(); 84 | } 85 | } 86 | 87 | @Override 88 | public void setDataSource(AssetFileDescriptor fd) { 89 | try { 90 | mMediaPlayer.setDataSource(new RawDataSourceProvider(fd)); 91 | } catch (Exception e) { 92 | mPlayerEventListener.onError(); 93 | } 94 | } 95 | 96 | @Override 97 | public void pause() { 98 | try { 99 | mMediaPlayer.pause(); 100 | } catch (IllegalStateException e) { 101 | mPlayerEventListener.onError(); 102 | } 103 | } 104 | 105 | @Override 106 | public void start() { 107 | try { 108 | mMediaPlayer.start(); 109 | } catch (IllegalStateException e) { 110 | mPlayerEventListener.onError(); 111 | } 112 | } 113 | 114 | @Override 115 | public void stop() { 116 | try { 117 | mMediaPlayer.stop(); 118 | } catch (IllegalStateException e) { 119 | mPlayerEventListener.onError(); 120 | } 121 | } 122 | 123 | @Override 124 | public void prepareAsync() { 125 | try { 126 | mMediaPlayer.prepareAsync(); 127 | } catch (IllegalStateException e) { 128 | mPlayerEventListener.onError(); 129 | } 130 | } 131 | 132 | @Override 133 | public void reset() { 134 | mMediaPlayer.reset(); 135 | mMediaPlayer.setOnVideoSizeChangedListener(this); 136 | setOptions(); 137 | } 138 | 139 | @Override 140 | public boolean isPlaying() { 141 | return mMediaPlayer.isPlaying(); 142 | } 143 | 144 | @Override 145 | public void seekTo(long time) { 146 | try { 147 | mMediaPlayer.seekTo((int) time); 148 | } catch (IllegalStateException e) { 149 | mPlayerEventListener.onError(); 150 | } 151 | } 152 | 153 | @Override 154 | public void release() { 155 | mMediaPlayer.setOnErrorListener(null); 156 | mMediaPlayer.setOnCompletionListener(null); 157 | mMediaPlayer.setOnInfoListener(null); 158 | mMediaPlayer.setOnBufferingUpdateListener(null); 159 | mMediaPlayer.setOnPreparedListener(null); 160 | mMediaPlayer.setOnVideoSizeChangedListener(null); 161 | new Thread() { 162 | @Override 163 | public void run() { 164 | try { 165 | mMediaPlayer.release(); 166 | } catch (Exception e) { 167 | e.printStackTrace(); 168 | } 169 | } 170 | }.start(); 171 | } 172 | 173 | @Override 174 | public long getCurrentPosition() { 175 | return mMediaPlayer.getCurrentPosition(); 176 | } 177 | 178 | @Override 179 | public long getDuration() { 180 | return mMediaPlayer.getDuration(); 181 | } 182 | 183 | @Override 184 | public int getBufferedPercentage() { 185 | return mBufferedPercent; 186 | } 187 | 188 | @Override 189 | public void setSurface(Surface surface) { 190 | mMediaPlayer.setSurface(surface); 191 | } 192 | 193 | @Override 194 | public void setDisplay(SurfaceHolder holder) { 195 | mMediaPlayer.setDisplay(holder); 196 | } 197 | 198 | @Override 199 | public void setVolume(float v1, float v2) { 200 | mMediaPlayer.setVolume(v1, v2); 201 | } 202 | 203 | @Override 204 | public void setLooping(boolean isLooping) { 205 | mMediaPlayer.setLooping(isLooping); 206 | } 207 | 208 | @Override 209 | public void setSpeed(float speed) { 210 | mMediaPlayer.setSpeed(speed); 211 | } 212 | 213 | @Override 214 | public float getSpeed() { 215 | return mMediaPlayer.getSpeed(0); 216 | } 217 | 218 | @Override 219 | public long getTcpSpeed() { 220 | return mMediaPlayer.getTcpSpeed(); 221 | } 222 | 223 | @Override 224 | public boolean onError(IMediaPlayer mp, int what, int extra) { 225 | mPlayerEventListener.onError(); 226 | return true; 227 | } 228 | 229 | @Override 230 | public void onCompletion(IMediaPlayer mp) { 231 | mPlayerEventListener.onCompletion(); 232 | } 233 | 234 | @Override 235 | public boolean onInfo(IMediaPlayer mp, int what, int extra) { 236 | mPlayerEventListener.onInfo(what, extra); 237 | return true; 238 | } 239 | 240 | @Override 241 | public void onBufferingUpdate(IMediaPlayer mp, int percent) { 242 | mBufferedPercent = percent; 243 | } 244 | 245 | @Override 246 | public void onPrepared(IMediaPlayer mp) { 247 | mPlayerEventListener.onPrepared(); 248 | // 修复播放纯音频时状态出错问题 249 | if (!isVideo()) { 250 | mPlayerEventListener.onInfo(AbstractPlayer.MEDIA_INFO_RENDERING_START, 0); 251 | } 252 | } 253 | 254 | private boolean isVideo() { 255 | IjkTrackInfo[] trackInfo = mMediaPlayer.getTrackInfo(); 256 | if (trackInfo == null) return false; 257 | for (IjkTrackInfo info : trackInfo) { 258 | if (info.getTrackType() == ITrackInfo.MEDIA_TRACK_TYPE_VIDEO) { 259 | return true; 260 | } 261 | } 262 | return false; 263 | } 264 | 265 | @Override 266 | public void onVideoSizeChanged(IMediaPlayer mp, int width, int height, int sar_num, int sar_den) { 267 | int videoWidth = mp.getVideoWidth(); 268 | int videoHeight = mp.getVideoHeight(); 269 | if (videoWidth != 0 && videoHeight != 0) { 270 | mPlayerEventListener.onVideoSizeChanged(videoWidth, videoHeight); 271 | } 272 | } 273 | 274 | @Override 275 | public boolean onNativeInvoke(int what, Bundle args) { 276 | return true; 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /app/src/main/java/com/company/ijkpalyer/dk/IjkPlayerFactory.java: -------------------------------------------------------------------------------- 1 | package com.company.ijkpalyer.dk; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | 6 | import tv.danmaku.ijk.media.player.IjkMediaPlayer; 7 | import xyz.doikki.videoplayer.player.PlayerFactory; 8 | 9 | public class IjkPlayerFactory extends PlayerFactory { 10 | 11 | private IjkMediaPlayer ijkMediaPlayer; 12 | public static IjkPlayer ijkPlayer; 13 | 14 | public static IjkPlayerFactory create() { 15 | return new IjkPlayerFactory(); 16 | } 17 | 18 | @Override 19 | public IjkPlayer createPlayer(Context context) { 20 | ijkPlayer = new IjkPlayer(context); 21 | ijkMediaPlayer = ijkPlayer.getIjkMediaPlayer(); 22 | Log.e("TAG", "===测试=333==" + ijkMediaPlayer); 23 | 24 | return ijkPlayer; 25 | } 26 | 27 | public IjkMediaPlayer getIjkMediaPlayer() { 28 | Log.e("TAG", "===测试=444==" + ijkMediaPlayer); 29 | 30 | return ijkMediaPlayer; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/company/ijkpalyer/dk/RawDataSourceProvider.java: -------------------------------------------------------------------------------- 1 | package com.company.ijkpalyer.dk; 2 | 3 | import android.content.Context; 4 | import android.content.res.AssetFileDescriptor; 5 | import android.net.Uri; 6 | 7 | import java.io.ByteArrayOutputStream; 8 | import java.io.FileNotFoundException; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | 12 | import tv.danmaku.ijk.media.player.misc.IMediaDataSource; 13 | 14 | public class RawDataSourceProvider implements IMediaDataSource { 15 | private AssetFileDescriptor mDescriptor; 16 | 17 | private byte[] mMediaBytes; 18 | 19 | public RawDataSourceProvider(AssetFileDescriptor descriptor) { 20 | this.mDescriptor = descriptor; 21 | } 22 | 23 | @Override 24 | public int readAt(long position, byte[] buffer, int offset, int size) { 25 | if (position + 1 >= mMediaBytes.length) { 26 | return -1; 27 | } 28 | 29 | int length; 30 | if (position + size < mMediaBytes.length) { 31 | length = size; 32 | } else { 33 | length = (int) (mMediaBytes.length - position); 34 | if (length > buffer.length) 35 | length = buffer.length; 36 | 37 | length--; 38 | } 39 | System.arraycopy(mMediaBytes, (int) position, buffer, offset, length); 40 | 41 | return length; 42 | } 43 | 44 | @Override 45 | public long getSize() throws IOException { 46 | long length = mDescriptor.getLength(); 47 | if (mMediaBytes == null) { 48 | InputStream inputStream = mDescriptor.createInputStream(); 49 | mMediaBytes = readBytes(inputStream); 50 | } 51 | 52 | 53 | return length; 54 | } 55 | 56 | @Override 57 | public void close() throws IOException { 58 | if (mDescriptor != null) 59 | mDescriptor.close(); 60 | 61 | mDescriptor = null; 62 | mMediaBytes = null; 63 | } 64 | 65 | private byte[] readBytes(InputStream inputStream) throws IOException { 66 | ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream(); 67 | 68 | int bufferSize = 1024; 69 | byte[] buffer = new byte[bufferSize]; 70 | 71 | int len; 72 | while ((len = inputStream.read(buffer)) != -1) { 73 | byteBuffer.write(buffer, 0, len); 74 | } 75 | 76 | return byteBuffer.toByteArray(); 77 | } 78 | 79 | public static RawDataSourceProvider create(Context context, Uri uri) { 80 | try { 81 | AssetFileDescriptor fileDescriptor = context.getContentResolver().openAssetFileDescriptor(uri, "r"); 82 | return new RawDataSourceProvider(fileDescriptor); 83 | 84 | } catch (FileNotFoundException e) { 85 | e.printStackTrace(); 86 | } 87 | return null; 88 | } 89 | } -------------------------------------------------------------------------------- /app/src/main/java/com/company/ijkpalyer/ijk/IjkPlayerActivity.java: -------------------------------------------------------------------------------- 1 | package com.company.ijkpalyer.ijk; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.graphics.Bitmap; 6 | import android.net.Uri; 7 | import android.os.Bundle; 8 | import android.os.Environment; 9 | import android.util.Log; 10 | import android.view.Surface; 11 | import android.view.View; 12 | import android.widget.Button; 13 | import android.widget.EditText; 14 | import android.widget.ImageView; 15 | import android.widget.Toast; 16 | 17 | import androidx.appcompat.app.AppCompatActivity; 18 | 19 | import com.company.ijkpalyer.R; 20 | import com.company.ijkpalyer.dk.IjkPlayerFactory; 21 | import com.hjq.permissions.OnPermissionCallback; 22 | import com.hjq.permissions.Permission; 23 | import com.hjq.permissions.XXPermissions; 24 | 25 | import java.io.File; 26 | import java.text.SimpleDateFormat; 27 | import java.util.Date; 28 | import java.util.List; 29 | import java.util.Locale; 30 | 31 | import tv.danmaku.ijk.media.player.IjkMediaPlayer; 32 | import xyz.doikki.videoplayer.player.BaseVideoView; 33 | import xyz.doikki.videoplayer.player.VideoView; 34 | 35 | /** 36 | * DK播放器,切换成自己的ijk可以录像的播放器 arr内核+jniLibs.so 文件 37 | */ 38 | public class IjkPlayerActivity extends AppCompatActivity implements View.OnClickListener { 39 | private static final String TAG = "IjkPlayerActivity"; 40 | //苹果公司点播的的流地址 41 | public static final String path = "http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear2/prog_index.m3u8"; 42 | //我自己公司rtsp的流地址 43 | // public static final String path = "rtsp://root:root@192.168.66.31:7788/session0.mpg"; 44 | // public static String path = "http://219.151.31.38/liveplay-kk.rtxapp.com/live/program/live/hnwshd/4000000/mnf.m3u8"; 45 | // public static String path = "http://220.161.87.62:8800/hls/0/index.m3u8"; 46 | // public static String path = "http://192.168.67.210:3333/api/stream/video?session=123456"; 47 | 48 | private Surface mSurface; 49 | private IjkMediaPlayer mPlayer; 50 | private Button btn_end_record; 51 | private Button btn_frame; 52 | private Button btn_start_record; 53 | private String mRecordPath; 54 | private Button start_live; 55 | private VideoView mVideoView; 56 | private IjkPlayerFactory ijkPlayerFactory; 57 | private EditText et_get_path; 58 | private String currentPath; 59 | private ImageView imageview; 60 | private Button btn_16_9; 61 | private Button btn_default; 62 | private Button btn_yuanshi; 63 | private Button btn_no_voice; 64 | 65 | @Override 66 | protected void onCreate(Bundle savedInstanceState) { 67 | super.onCreate(savedInstanceState); 68 | setContentView(R.layout.activity_ijk); 69 | btn_start_record = findViewById(R.id.btn_start_record); 70 | btn_end_record = findViewById(R.id.btn_end_record); 71 | btn_frame = findViewById(R.id.btn_frame); 72 | start_live = findViewById(R.id.start_live); 73 | et_get_path = findViewById(R.id.et_get_path); 74 | imageview = findViewById(R.id.imageview); 75 | mVideoView = findViewById(R.id.player); 76 | btn_16_9 = findViewById(R.id.btn_16_9); 77 | btn_default = findViewById(R.id.btn_default); 78 | btn_yuanshi = findViewById(R.id.btn_yuanshi); 79 | btn_no_voice = findViewById(R.id.btn_no_voice); 80 | btn_16_9.setOnClickListener(this); 81 | btn_default.setOnClickListener(this); 82 | btn_yuanshi.setOnClickListener(this); 83 | btn_no_voice.setOnClickListener(this); 84 | btn_start_record.setOnClickListener(this); 85 | btn_end_record.setOnClickListener(this); 86 | btn_frame.setOnClickListener(this); 87 | start_live.setOnClickListener(this); 88 | //使用IjkPlayer解码 89 | ijkPlayerFactory = IjkPlayerFactory.create(); 90 | mVideoView.setPlayerFactory(ijkPlayerFactory); 91 | requestPermission(); 92 | mVideoView.setUrl(path); //设置视频地址 93 | mVideoView.start(); //开始播放,不调用则不自动播放 94 | responseListener(); 95 | 96 | } 97 | 98 | private void responseListener() { 99 | mVideoView.addOnStateChangeListener(new BaseVideoView.OnStateChangeListener() { 100 | @Override 101 | public void onPlayerStateChanged(int playerState) { 102 | } 103 | 104 | @Override 105 | public void onPlayStateChanged(int playState) { 106 | switch (playState) { 107 | case VideoView.STATE_BUFFERING://缓存中 108 | Log.e(TAG, "==DKPlayer===缓存中"); 109 | break; 110 | case VideoView.STATE_BUFFERED://缓存完毕 111 | Log.e(TAG, "==DKPlayer===缓存完毕"); 112 | break; 113 | case VideoView.STATE_PLAYING://播放 114 | Log.e(TAG, "==DKPlayer===播放"); 115 | break; 116 | case VideoView.STATE_PAUSED://播放暂停 117 | Log.e(TAG, "==DKPlayer===播放暂停"); 118 | break; 119 | case VideoView.STATE_ERROR://播放失败 120 | Log.e(TAG, "==DKPlayer===播放失败"); 121 | 122 | break; 123 | } 124 | } 125 | }); 126 | 127 | } 128 | 129 | //刷新相册,出现刚刚录像视频 130 | public static void scanFile(Context context, String filePath) { 131 | Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); 132 | intent.setData(Uri.fromFile(new File(filePath))); 133 | context.sendBroadcast(intent); 134 | } 135 | 136 | 137 | @Override 138 | public void onClick(View v) { 139 | switch (v.getId()) { 140 | case R.id.btn_default: //默认 141 | mVideoView.setScreenScaleType(VideoView.SCREEN_SCALE_DEFAULT); 142 | break; 143 | case R.id.btn_yuanshi: //原始 144 | mVideoView.setScreenScaleType(VideoView.SCREEN_SCALE_ORIGINAL); 145 | break; 146 | case R.id.btn_16_9: //16:9 147 | mVideoView.setScreenScaleType(VideoView.SCREEN_SCALE_16_9); 148 | break; 149 | case R.id.btn_no_voice: //静音 150 | if (mVideoView.isMute()) { 151 | mVideoView.setMute(false); 152 | Toast.makeText(IjkPlayerActivity.this, "开启-声音", Toast.LENGTH_SHORT).show(); 153 | } else { 154 | mVideoView.setMute(true); 155 | Toast.makeText(IjkPlayerActivity.this, "开启-静音", Toast.LENGTH_SHORT).show(); 156 | } 157 | break; 158 | case R.id.start_live: //开始播放 159 | mVideoView.release(); 160 | currentPath = et_get_path.getText().toString().trim(); 161 | mVideoView.setUrl(currentPath); //设置视频地址 162 | mVideoView.start(); //开始播放,不调用则不自动播放 163 | /** 164 | * 必须播放之后 才能拿到IjkMediaPlayer==mPlayer; 165 | */ 166 | break; 167 | case R.id.btn_start_record: //开始录像 168 | //获取可以录像的IjkMediaPlayer 169 | mPlayer = IjkPlayerFactory.ijkPlayer.getIjkMediaPlayer(); 170 | Toast.makeText(this, "开始录像", Toast.LENGTH_SHORT).show(); 171 | record(); 172 | break; 173 | case R.id.btn_end_record: //结束录像 174 | //获取可以录像的IjkMediaPlayer 175 | mPlayer = IjkPlayerFactory.ijkPlayer.getIjkMediaPlayer(); 176 | mPlayer.stopRecord(); 177 | scanFile(this, mRecordPath); 178 | Toast.makeText(this, "结束录像", Toast.LENGTH_SHORT).show(); 179 | 180 | break; 181 | case R.id.btn_frame: //截图 182 | //1,DK播放器自带截图功能 183 | Bitmap bitmap = mVideoView.doScreenShot(); 184 | imageview.setImageBitmap(bitmap); 185 | Toast.makeText(this, "截图", Toast.LENGTH_SHORT).show(); 186 | break; 187 | 188 | 189 | } 190 | } 191 | 192 | private void record() { 193 | File file = new File(Environment.getExternalStorageDirectory() + "/MyMovies"); 194 | // /storage/emulated/0/RecordVideos 195 | Log.e(TAG, "===file.exists()===" + file.exists()); 196 | Log.e(TAG, "===file.mkdirs()===" + file.mkdirs()); 197 | Log.e(TAG, "===file.mkdirs()=file==" + file.getAbsolutePath()); 198 | if (!file.exists()) { 199 | file.mkdirs(); 200 | Log.e(TAG, "===000===" + file.exists()); 201 | } 202 | String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(new Date(System.currentTimeMillis())); 203 | String s = file.getAbsolutePath() + "/" + date + ".mp4"; 204 | Log.e(TAG, "===s===" + s); 205 | String replace = s.replace("-", "_"); 206 | Log.e(TAG, "===s==replace=" + replace); 207 | String replace1 = replace.replace(" ", "_"); 208 | Log.e(TAG, "===s==replace1=" + replace1); 209 | String replace2 = replace1.replace(":", "_"); 210 | Log.e(TAG, "===s==replace2=" + replace2); 211 | mRecordPath = replace2; 212 | Log.e(TAG, "===mRecordPath===" + mRecordPath); 213 | 214 | mPlayer.startRecord(mRecordPath); 215 | } 216 | 217 | 218 | @Override 219 | protected void onPause() { 220 | super.onPause(); 221 | mVideoView.pause(); 222 | } 223 | 224 | @Override 225 | protected void onDestroy() { 226 | super.onDestroy(); 227 | mVideoView.release(); 228 | } 229 | 230 | @Override 231 | protected void onResume() { 232 | super.onResume(); 233 | mVideoView.resume(); 234 | } 235 | 236 | 237 | @Override 238 | public void onBackPressed() { 239 | if (!mVideoView.onBackPressed()) { 240 | super.onBackPressed(); 241 | } 242 | } 243 | 244 | private void requestPermission() { 245 | 246 | XXPermissions.with(this) 247 | // 不适配 Android 11 可以这样写 248 | //.permission(Permission.Group.STORAGE) 249 | // 适配 Android 11 需要这样写,这里无需再写 Permission.Group.STORAGE 250 | .permission(Permission.MANAGE_EXTERNAL_STORAGE) 251 | .request(new OnPermissionCallback() { 252 | 253 | @Override 254 | public void onGranted(List permissions, boolean all) { 255 | if (all) { 256 | Toast.makeText(IjkPlayerActivity.this, "获取存储权限成功", Toast.LENGTH_SHORT).show(); 257 | } 258 | } 259 | 260 | @Override 261 | public void onDenied(List permissions, boolean never) { 262 | if (never) { 263 | Toast.makeText(IjkPlayerActivity.this, "被永久拒绝授权,请手动授予存储权限", Toast.LENGTH_SHORT).show(); 264 | 265 | // 如果是被永久拒绝就跳转到应用权限系统设置页面 266 | XXPermissions.startPermissionActivity(IjkPlayerActivity.this, permissions); 267 | } else { 268 | Toast.makeText(IjkPlayerActivity.this, "获取存储权限失败", Toast.LENGTH_SHORT).show(); 269 | } 270 | } 271 | }); 272 | 273 | 274 | // XXPermissions.with(MainActivity.this) 275 | // // 不适配 Android 11 可以这样写 276 | // //.permission(Permission.Group.STORAGE) 277 | // // 适配 Android 11 需要这样写,这里无需再写 Permission.Group.STORAGE 278 | //// .permission(Permission.MANAGE_EXTERNAL_STORAGE) 279 | // .permission(Permission.WRITE_EXTERNAL_STORAGE) 280 | // .permission(Permission.READ_EXTERNAL_STORAGE) 281 | // .request(new OnPermission() { 282 | // 283 | // @Override 284 | // public void hasPermission(List granted, boolean all) { 285 | // if (all) { 286 | //// showToast("获取存储权限成功"); 287 | // } 288 | // } 289 | // 290 | // @Override 291 | // public void noPermission(List denied, boolean never) { 292 | // if (never) { 293 | //// showToast("被永久拒绝授权,请手动授予存储权限"); 294 | // // 如果是被永久拒绝就跳转到应用权限系统设置页面 295 | // XXPermissions.startPermissionActivity(MainActivity.this, denied); 296 | // } else { 297 | //// showToast("获取存储权限失败"); 298 | // } 299 | // } 300 | // }); 301 | 302 | } 303 | // //截图 304 | // public Bitmap capture() { 305 | // Bitmap srcBitmap = Bitmap.createBitmap(1920, 306 | // 1080, Bitmap.Config.ARGB_8888); 307 | // mPlayer.getCurrentFrame(srcBitmap); 308 | // return srcBitmap; 309 | // } 310 | } 311 | -------------------------------------------------------------------------------- /app/src/main/java/com/company/ijkpalyer/ijk/MyIjkPlayerActivity.java: -------------------------------------------------------------------------------- 1 | package com.company.ijkpalyer.ijk; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.graphics.Bitmap; 6 | import android.graphics.SurfaceTexture; 7 | import android.media.AudioManager; 8 | import android.net.Uri; 9 | import android.os.Bundle; 10 | import android.os.Environment; 11 | import android.provider.MediaStore; 12 | import android.util.Log; 13 | import android.view.Surface; 14 | import android.view.SurfaceView; 15 | import android.view.TextureView; 16 | import android.view.View; 17 | import android.widget.Button; 18 | import android.widget.Toast; 19 | 20 | import androidx.appcompat.app.AppCompatActivity; 21 | 22 | import com.company.ijkpalyer.R; 23 | import com.hjq.permissions.OnPermissionCallback; 24 | import com.hjq.permissions.Permission; 25 | import com.hjq.permissions.XXPermissions; 26 | 27 | import java.io.File; 28 | import java.io.IOException; 29 | import java.text.SimpleDateFormat; 30 | import java.util.Calendar; 31 | import java.util.Date; 32 | import java.util.List; 33 | import java.util.Locale; 34 | 35 | import tv.danmaku.ijk.media.player.IjkMediaPlayer; 36 | 37 | /** 38 | * company:江西神州医疗设备有限公司 39 | * author: LoveLin 40 | * time:2023/7/6 10:43 41 | * desc:自己写的ijk测试播放器 42 | */ 43 | public class MyIjkPlayerActivity extends AppCompatActivity implements View.OnClickListener { 44 | private static final String TAG = "MainActivity"; 45 | //苹果公司点播的的流地址 46 | // public static final String path = "http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear2/prog_index.m3u8"; 47 | //我自己公司rtsp的流地址 48 | // public static final String path = "rtsp://root:root@192.168.66.31:7788/session0.mpg"; 49 | // public static String path = "http://219.151.31.38/liveplay-kk.rtxapp.com/live/program/live/hnwshd/4000000/mnf.m3u8"; 50 | // public static String path = "http://192.168.67.210:3333/api/stream/video?session=123456"; 51 | public static String path = "http://220.161.87.62:8800/hls/0/index.m3u8"; 52 | 53 | private SurfaceView surfaceView; 54 | private TextureView textureView; 55 | private Surface mSurface; 56 | private IjkMediaPlayer mPlayer; 57 | private Button btn_end_record; 58 | private Button btn_frame; 59 | private Button btn_start_record; 60 | private String mRecordPath; 61 | 62 | @Override 63 | protected void onCreate(Bundle savedInstanceState) { 64 | super.onCreate(savedInstanceState); 65 | setContentView(R.layout.activity_my_ijk_player); 66 | textureView = findViewById(R.id.texture_view); 67 | btn_start_record = findViewById(R.id.btn_start_record); 68 | btn_end_record = findViewById(R.id.btn_end_record); 69 | btn_frame = findViewById(R.id.btn_frame); 70 | textureView.setSurfaceTextureListener(listener); 71 | btn_start_record.setOnClickListener(this); 72 | btn_end_record.setOnClickListener(this); 73 | btn_frame.setOnClickListener(this); 74 | requestPermission(); 75 | 76 | } 77 | 78 | //刷新相册,出现刚刚录像视频 79 | public static void scanFile(Context context, String filePath) { 80 | Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); 81 | intent.setData(Uri.fromFile(new File(filePath))); 82 | context.sendBroadcast(intent); 83 | } 84 | 85 | private void record() { 86 | File file = new File(Environment.getExternalStorageDirectory() + "/MyMovies"); 87 | // /storage/emulated/0/RecordVideos 88 | Log.e(TAG, "===file.exists()===" + file.exists()); 89 | Log.e(TAG, "===file.mkdirs()===" + file.mkdirs()); 90 | Log.e(TAG, "===file.mkdirs()=file==" + file.getAbsolutePath()); 91 | if (!file.exists()) { 92 | file.mkdirs(); 93 | Log.e(TAG, "===000===" + file.exists()); 94 | } 95 | String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(new Date(System.currentTimeMillis())); 96 | String s = file.getAbsolutePath() + "/" + date + ".mp4"; 97 | Log.e(TAG, "===s===" + s); 98 | String replace = s.replace("-", "_"); 99 | Log.e(TAG, "===s==replace=" + replace); 100 | String replace1 = replace.replace(" ", "_"); 101 | Log.e(TAG, "===s==replace1=" + replace1); 102 | String replace2 = replace1.replace(":", "_"); 103 | Log.e(TAG, "===s==replace2=" + replace2); 104 | mRecordPath = replace2; 105 | Log.e(TAG, "===mRecordPath===" + mRecordPath); 106 | 107 | mPlayer.startRecord(mRecordPath); 108 | } 109 | 110 | @Override 111 | public void onClick(View v) { 112 | switch (v.getId()) { 113 | case R.id.btn_start_record: //开始 114 | Toast.makeText(this, "开始录像", Toast.LENGTH_SHORT).show(); 115 | record(); 116 | break; 117 | case R.id.btn_end_record: //结束 118 | mPlayer.stopRecord(); 119 | // FileUtil.RefreshAlbum(mRecordPath, true, this); 120 | // 121 | scanFile(this, mRecordPath); 122 | Toast.makeText(this, "结束录像", Toast.LENGTH_SHORT).show(); 123 | 124 | break; 125 | case R.id.btn_frame: //截图 126 | new Thread() { 127 | @Override 128 | public void run() { 129 | super.run(); 130 | Bitmap bitmap = textureView.getBitmap(); 131 | //插入相册 ,显示刚刚的截图 132 | MediaStore.Images.Media.insertImage(getContentResolver(), bitmap, "IMG" + Calendar.getInstance().getTime(), null); 133 | 134 | } 135 | }.start(); 136 | 137 | Toast.makeText(this, "截图", Toast.LENGTH_SHORT).show(); 138 | break; 139 | 140 | 141 | } 142 | } 143 | 144 | private void createPlayer() { 145 | if (mPlayer == null) { 146 | mPlayer = new IjkMediaPlayer(); 147 | // 设置倍速,应该是0-2的float类型,可以测试一下 148 | mPlayer.setSpeed(1.0f); 149 | // 设置调用prepareAsync不自动播放,即调用start才开始播放 150 | mPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", 0); 151 | mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); 152 | /** 153 | * rtsp直播配置如下参数不然 录像会出问题 154 | */ 155 | if (path.startsWith("rtsp")) { 156 | mPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "fast", 1); 157 | //播放前的探测Size,默认是1M, 改小一点会出画面更快 158 | // ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "probesize", 200); 159 | //每处理一个packet之后刷新io上下文 160 | // ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "flush_packets", 1); 161 | //是否开启预缓冲,一般直播项目会开启,达到秒开的效果,不过带来了播放丢帧卡顿的体验 162 | // ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", 0); 163 | // ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", 48); // 164 | // 设置是否开启环路过滤: 0开启,画面质量高,解码开销大,48关闭,画面质量差点,解码开销小 165 | // ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "skip_loop_filter", 48); 166 | mPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 0); 167 | mPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-handle-resolution-change", 0); 168 | mPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max-buffer-size", 0); 169 | mPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "min-frames", 2); 170 | // ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "fflags", "nobuffer"); 171 | mPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "rtsp_transport", "tcp"); 172 | mPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "analyzedmaxduration", 100); 173 | mPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "http-detect-range-support", 0); 174 | mPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "skip_loop_filter", 48); 175 | mPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "skip_loop_filter", 8); 176 | mPlayer.setOption(1, "analyzemaxduration", 100L); 177 | mPlayer.setOption(1, "probesize", 10240L); //下探视频时间 178 | mPlayer.setOption(1, "flush_packets", 1L); 179 | mPlayer.setOption(4, "framedrop", 1L); 180 | mPlayer.setOption(4, "max_cached_duration", 600); //直播 181 | mPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "infbuf", 1);//直播 182 | mPlayer.setOption(4, "packet-buffering", 0L); 183 | } else { 184 | /** 185 | * rtmp http 配置如下参数即可 186 | */ 187 | mPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", 1); 188 | mPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", 0); 189 | mPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "http-detect-range-support", 0); 190 | mPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "skip_loop_filter", 48); 191 | // 192 | } 193 | 194 | 195 | try { 196 | // mPlayer.setDataSource("http://9890.vod.myqcloud.com/9890_4e292f9a3dd011e6b4078980237cc3d3.f20.mp4"); 197 | mPlayer.setDataSource(path); 198 | mPlayer.start(); 199 | } catch (IOException e) { 200 | e.printStackTrace(); 201 | } 202 | mPlayer.prepareAsync(); 203 | } 204 | } 205 | 206 | private void release() { 207 | if (mPlayer != null) { 208 | mPlayer.stop(); 209 | mPlayer.release(); 210 | mPlayer = null; 211 | } 212 | IjkMediaPlayer.native_profileEnd(); 213 | } 214 | 215 | // 216 | private TextureView.SurfaceTextureListener listener = new TextureView.SurfaceTextureListener() { 217 | @Override 218 | public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { 219 | createPlayer(); 220 | mSurface = new Surface(surface); 221 | mPlayer.setSurface(mSurface); 222 | } 223 | 224 | @Override 225 | public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 226 | 227 | } 228 | 229 | @Override 230 | public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 231 | textureView.setSurfaceTextureListener(null); 232 | textureView = null; 233 | mSurface = null; 234 | return true; 235 | } 236 | 237 | @Override 238 | public void onSurfaceTextureUpdated(SurfaceTexture surface) { 239 | 240 | } 241 | }; 242 | 243 | @Override 244 | protected void onDestroy() { 245 | super.onDestroy(); 246 | release(); 247 | } 248 | 249 | 250 | public void handleTextureView(View view) { 251 | mPlayer.setSurface(mSurface); 252 | if (!mPlayer.isPlaying()) 253 | mPlayer.start(); 254 | } 255 | 256 | private void requestPermission() { 257 | 258 | XXPermissions.with(this) 259 | // 不适配 Android 11 可以这样写 260 | //.permission(Permission.Group.STORAGE) 261 | // 适配 Android 11 需要这样写,这里无需再写 Permission.Group.STORAGE 262 | .permission(Permission.MANAGE_EXTERNAL_STORAGE) 263 | .request(new OnPermissionCallback() { 264 | 265 | @Override 266 | public void onGranted(List permissions, boolean all) { 267 | if (all) { 268 | Toast.makeText(MyIjkPlayerActivity.this, "获取存储权限成功", Toast.LENGTH_SHORT).show(); 269 | } 270 | } 271 | 272 | @Override 273 | public void onDenied(List permissions, boolean never) { 274 | if (never) { 275 | Toast.makeText(MyIjkPlayerActivity.this, "被永久拒绝授权,请手动授予存储权限", Toast.LENGTH_SHORT).show(); 276 | 277 | // 如果是被永久拒绝就跳转到应用权限系统设置页面 278 | XXPermissions.startPermissionActivity(MyIjkPlayerActivity.this, permissions); 279 | } else { 280 | Toast.makeText(MyIjkPlayerActivity.this, "获取存储权限失败", Toast.LENGTH_SHORT).show(); 281 | } 282 | } 283 | }); 284 | 285 | 286 | // XXPermissions.with(MainActivity.this) 287 | // // 不适配 Android 11 可以这样写 288 | // //.permission(Permission.Group.STORAGE) 289 | // // 适配 Android 11 需要这样写,这里无需再写 Permission.Group.STORAGE 290 | //// .permission(Permission.MANAGE_EXTERNAL_STORAGE) 291 | // .permission(Permission.WRITE_EXTERNAL_STORAGE) 292 | // .permission(Permission.READ_EXTERNAL_STORAGE) 293 | // .request(new OnPermission() { 294 | // 295 | // @Override 296 | // public void hasPermission(List granted, boolean all) { 297 | // if (all) { 298 | //// showToast("获取存储权限成功"); 299 | // } 300 | // } 301 | // 302 | // @Override 303 | // public void noPermission(List denied, boolean never) { 304 | // if (never) { 305 | //// showToast("被永久拒绝授权,请手动授予存储权限"); 306 | // // 如果是被永久拒绝就跳转到应用权限系统设置页面 307 | // XXPermissions.startPermissionActivity(MainActivity.this, denied); 308 | // } else { 309 | //// showToast("获取存储权限失败"); 310 | // } 311 | // } 312 | // }); 313 | 314 | } 315 | // //截图 316 | // public Bitmap capture() { 317 | // Bitmap srcBitmap = Bitmap.createBitmap(1920, 318 | // 1080, Bitmap.Config.ARGB_8888); 319 | // mPlayer.getCurrentFrame(srcBitmap); 320 | // return srcBitmap; 321 | // } 322 | } 323 | -------------------------------------------------------------------------------- /app/src/main/java/com/company/ijkpalyer/utils/FileUtil.java: -------------------------------------------------------------------------------- 1 | package com.company.ijkpalyer.utils; 2 | 3 | import android.content.ContentResolver; 4 | import android.content.ContentValues; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.media.MediaScannerConnection; 8 | import android.net.Uri; 9 | import android.os.Environment; 10 | import android.os.StatFs; 11 | import android.provider.MediaStore; 12 | import android.text.TextUtils; 13 | import android.text.format.Formatter; 14 | 15 | import java.io.File; 16 | 17 | public class FileUtil { 18 | 19 | private static MediaScannerConnection mMediaScanner; 20 | 21 | public static String getROMTotalSize(final Context context) { 22 | File path = Environment.getExternalStorageDirectory(); 23 | StatFs stat = new StatFs(path.getPath()); 24 | long blockSize = stat.getBlockSize(); 25 | long totalBlocks = stat.getBlockCount(); 26 | String availMemStr = formateFileSize(context, blockSize * totalBlocks); 27 | return availMemStr; 28 | 29 | } 30 | 31 | //调用系统函数,字符串转换 long -String KB/MB 32 | public static String formateFileSize(Context context, long size) { 33 | return Formatter.formatFileSize(context, size); 34 | } 35 | 36 | public static String getROMAvailableSize(final Context context) { 37 | File path = Environment.getExternalStorageDirectory(); 38 | StatFs stat = new StatFs(path.getPath()); 39 | long blockSize = stat.getBlockSize(); 40 | long availableBlocks = stat.getAvailableBlocks(); 41 | String availMemStr = formateFileSize(context, blockSize * availableBlocks); 42 | return availMemStr; 43 | 44 | } 45 | 46 | static final int ERROR = -1; 47 | 48 | /** 49 | * 外部存储是否可用 50 | * 51 | * @return 52 | */ 53 | static public boolean externalMemoryAvailable() { 54 | return Environment.getExternalStorageState().equals( 55 | Environment.MEDIA_MOUNTED); 56 | } 57 | 58 | /** 59 | * 获取手机内部可用空间大小 60 | * 61 | * @return 62 | */ 63 | static public String getAvailableInternalMemorySize(final Context context) { 64 | File path = Environment.getDataDirectory(); 65 | StatFs stat = new StatFs(path.getPath()); 66 | long blockSize = stat.getBlockSize(); 67 | long availableBlocks = stat.getAvailableBlocks(); 68 | return formateFileSize(context, availableBlocks * blockSize); 69 | } 70 | 71 | /** 72 | * 获取手机内部空间大小 73 | * 74 | * @return 75 | */ 76 | static public String getTotalInternalMemorySize(final Context context) { 77 | File path = Environment.getDataDirectory(); 78 | StatFs stat = new StatFs(path.getPath()); 79 | long blockSize = stat.getBlockSize(); 80 | long totalBlocks = stat.getBlockCount(); 81 | return formateFileSize(context, totalBlocks * blockSize); 82 | } 83 | 84 | /** 85 | * 获取手机外部可用空间大小 86 | * 87 | * @return 88 | */ 89 | static public String getAvailableExternalMemorySize(final Context context) { 90 | if (externalMemoryAvailable()) { 91 | File path = Environment.getExternalStorageDirectory(); 92 | StatFs stat = new StatFs(path.getPath()); 93 | long blockSize = stat.getBlockSize(); 94 | long availableBlocks = stat.getAvailableBlocks(); 95 | return formateFileSize(context, availableBlocks * blockSize); 96 | } else { 97 | return "ERROR"; 98 | } 99 | } 100 | 101 | /** 102 | * 获取手机外部空间大小 103 | * 104 | * @return 105 | */ 106 | static public String getTotalExternalMemorySize(final Context context) { 107 | if (externalMemoryAvailable()) { 108 | File path = Environment.getExternalStorageDirectory(); 109 | StatFs stat = new StatFs(path.getPath()); 110 | long blockSize = stat.getBlockSize(); 111 | long totalBlocks = stat.getBlockCount(); 112 | return formateFileSize(context, totalBlocks * blockSize); 113 | } else { 114 | return "ERROR"; 115 | } 116 | } 117 | 118 | static public String formatSize(long size) { 119 | String suffix = null; 120 | 121 | if (size >= 1024) { 122 | suffix = "KiB"; 123 | size /= 1024; 124 | if (size >= 1024) { 125 | suffix = "MiB"; 126 | size /= 1024; 127 | } 128 | } 129 | 130 | StringBuilder resultBuffer = new StringBuilder(Long.toString(size)); 131 | int commaOffset = resultBuffer.length() - 3; 132 | while (commaOffset > 0) { 133 | resultBuffer.insert(commaOffset, ','); 134 | commaOffset -= 3; 135 | } 136 | 137 | if (suffix != null) 138 | resultBuffer.append(suffix); 139 | return resultBuffer.toString(); 140 | } 141 | 142 | //针对非系统影音资源文件夹 143 | public static void insertIntoMediaStore(Context context, boolean isVideo, File saveFile, long createTime) { 144 | ContentResolver mContentResolver = context.getContentResolver(); 145 | if (createTime == 0) 146 | createTime = System.currentTimeMillis(); 147 | ContentValues values = new ContentValues(); 148 | values.put(MediaStore.MediaColumns.TITLE, saveFile.getName()); 149 | values.put(MediaStore.MediaColumns.DISPLAY_NAME, saveFile.getName()); 150 | //值一样,但是还是用常量区分对待 151 | values.put(isVideo ? MediaStore.Video.VideoColumns.DATE_TAKEN 152 | : MediaStore.Images.ImageColumns.DATE_TAKEN, createTime); 153 | values.put(MediaStore.MediaColumns.DATE_MODIFIED, System.currentTimeMillis()); 154 | values.put(MediaStore.MediaColumns.DATE_ADDED, System.currentTimeMillis()); 155 | if (!isVideo) 156 | values.put(MediaStore.Images.ImageColumns.ORIENTATION, 0); 157 | values.put(MediaStore.MediaColumns.DATA, saveFile.getAbsolutePath()); 158 | values.put(MediaStore.MediaColumns.SIZE, saveFile.length()); 159 | values.put(MediaStore.MediaColumns.MIME_TYPE, isVideo); 160 | //插入 161 | mContentResolver.insert(isVideo 162 | ? MediaStore.Video.Media.EXTERNAL_CONTENT_URI 163 | : MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); 164 | } 165 | 166 | // 获取video的mine_type,暂时只支持mp4,3gp 167 | private static String getVideoMimeType(String path) { 168 | String lowerPath = path.toLowerCase(); 169 | if (lowerPath.endsWith("mp4") || lowerPath.endsWith("mpeg4")) { 170 | return "video/mp4"; 171 | } else if (lowerPath.endsWith("3gp")) { 172 | return "video/3gp"; 173 | } 174 | return "video/mp4"; 175 | } 176 | 177 | public static void RefreshAlbum(String fileAbsolutePath, boolean isVideo, Context mContext) { 178 | mMediaScanner = new MediaScannerConnection(mContext, new MediaScannerConnection.MediaScannerConnectionClient() { 179 | @Override 180 | public void onMediaScannerConnected() { 181 | if (mMediaScanner.isConnected()) { 182 | LogUtils.e("相册更新问题:连接成功 "); 183 | LogUtils.e("相册更新问题:连接成功 isVideo:"+isVideo); 184 | 185 | if (isVideo) { 186 | mMediaScanner.scanFile(fileAbsolutePath, "video/mp4"); 187 | } else { 188 | mMediaScanner.scanFile(fileAbsolutePath, "image/png"); 189 | // mMediaScanner.scanFile(fileAbsolutePath, "image/jpeg"); 190 | } 191 | } else { 192 | LogUtils.e("相册更新问题:无法更新图库,未连接,广播通知更新图库,异常情况下 "); 193 | } 194 | } 195 | 196 | @Override 197 | 198 | public void onScanCompleted(String path, Uri uri) { 199 | LogUtils.e("相册更新问题:扫描完成 path: " + path + "uri:" + uri); 200 | } 201 | 202 | }); 203 | 204 | mMediaScanner.connect(); 205 | 206 | } 207 | 208 | public static void scanFile(Context context, String filePath) { 209 | Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); 210 | intent.setData(Uri.fromFile(new File(filePath))); 211 | context.sendBroadcast(intent); 212 | } 213 | 214 | /** 215 | * 保存视频 216 | * 217 | * @param context 218 | * @param file 219 | */ 220 | public static void saveVideo(Context context, File file) { 221 | //是否添加到相册 222 | ContentResolver localContentResolver = context.getContentResolver(); 223 | ContentValues localContentValues = getVideoContentValues(context, file, System.currentTimeMillis()); 224 | Uri localUri = localContentResolver.insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, localContentValues); 225 | context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, localUri)); 226 | } 227 | 228 | public static ContentValues getVideoContentValues(Context paramContext, File paramFile, long paramLong) { 229 | ContentValues localContentValues = new ContentValues(); 230 | localContentValues.put("title", paramFile.getName()); 231 | localContentValues.put("_display_name", paramFile.getName()); 232 | localContentValues.put("mime_type", "video/mp4"); 233 | localContentValues.put("datetaken", Long.valueOf(paramLong)); 234 | localContentValues.put("date_modified", Long.valueOf(paramLong)); 235 | localContentValues.put("date_added", Long.valueOf(paramLong)); 236 | localContentValues.put("_data", paramFile.getAbsolutePath()); 237 | localContentValues.put("_size", Long.valueOf(paramFile.length())); 238 | return localContentValues; 239 | } 240 | 241 | 242 | /** 243 | * 删除SD卡中的文件或目录 244 | * 245 | * @param path 246 | * @return 247 | */ 248 | public static boolean deleteSDFile(String path) { 249 | return deleteSDFile(path, false); 250 | } 251 | 252 | /** 253 | * 删除SD卡中的文件或目录 254 | * 255 | * @param path 256 | * @param deleteParent true为删除父目录 257 | * @return 258 | */ 259 | public static boolean deleteSDFile(String path, boolean deleteParent) { 260 | if (TextUtils.isEmpty(path)) { 261 | return false; 262 | } 263 | 264 | File file = new File(path); 265 | if (!file.exists()) { 266 | //不存在 267 | return true; 268 | } 269 | return deleteFile(file, deleteParent); 270 | } 271 | 272 | /** 273 | * @param file 274 | * @param deleteParent true为删除父目录 275 | * @return 276 | */ 277 | public static boolean deleteFile(File file, boolean deleteParent) { 278 | boolean flag = false; 279 | if (file == null) { 280 | return flag; 281 | } 282 | if (file.isDirectory()) { 283 | //是文件夹 284 | File[] files = file.listFiles(); 285 | if (files.length > 0) { 286 | for (int i = 0; i < files.length; i++) { 287 | flag = deleteFile(files[i], true); 288 | if (!flag) { 289 | return flag; 290 | } 291 | } 292 | } 293 | if (deleteParent) { 294 | flag = file.delete(); 295 | } 296 | } else { 297 | flag = file.delete(); 298 | } 299 | file = null; 300 | return flag; 301 | } 302 | 303 | /** 304 | * 添加到媒体数据库 305 | * 306 | * @param context 上下文 307 | */ 308 | public static Uri fileScanVideo(Context context, String videoPath, int videoWidth, int videoHeight, 309 | int videoTime) { 310 | 311 | File file = new File(videoPath); 312 | if (file.exists()) { 313 | 314 | Uri uri = null; 315 | 316 | long size = file.length(); 317 | String fileName = file.getName(); 318 | long dateTaken = System.currentTimeMillis(); 319 | 320 | ContentValues values = new ContentValues(11); 321 | values.put(MediaStore.Video.Media.DATA, videoPath); // 路径; 322 | values.put(MediaStore.Video.Media.TITLE, fileName); // 标题; 323 | values.put(MediaStore.Video.Media.DURATION, videoTime * 1000); // 时长 324 | values.put(MediaStore.Video.Media.WIDTH, videoWidth); // 视频宽 325 | values.put(MediaStore.Video.Media.HEIGHT, videoHeight); // 视频高 326 | values.put(MediaStore.Video.Media.SIZE, size); // 视频大小; 327 | values.put(MediaStore.Video.Media.DATE_TAKEN, dateTaken); // 插入时间; 328 | values.put(MediaStore.Video.Media.DISPLAY_NAME, fileName);// 文件名; 329 | values.put(MediaStore.Video.Media.DATE_MODIFIED, dateTaken / 1000);// 修改时间; 330 | values.put(MediaStore.Video.Media.DATE_ADDED, dateTaken / 1000); // 添加时间; 331 | values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4"); 332 | 333 | ContentResolver resolver = context.getContentResolver(); 334 | 335 | if (resolver != null) { 336 | try { 337 | uri = resolver.insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values); 338 | } catch (Exception e) { 339 | e.printStackTrace(); 340 | uri = null; 341 | } 342 | } 343 | 344 | if (uri == null) { 345 | MediaScannerConnection.scanFile(context, new String[]{videoPath}, new String[]{"video/*"}, new MediaScannerConnection.OnScanCompletedListener() { 346 | @Override 347 | public void onScanCompleted(String path, Uri uri) { 348 | 349 | } 350 | }); 351 | } 352 | 353 | return uri; 354 | } 355 | 356 | return null; 357 | } 358 | 359 | /** 360 | * SD卡存在并可以使用 361 | */ 362 | public static boolean isSDExists() { 363 | return Environment.getExternalStorageState().equals( 364 | Environment.MEDIA_MOUNTED); 365 | } 366 | 367 | /** 368 | * 获取SD卡的剩余容量,单位是Byte 369 | * 370 | * @return 371 | */ 372 | public static long getSDFreeMemory() { 373 | try { 374 | if (isSDExists()) { 375 | File pathFile = Environment.getExternalStorageDirectory(); 376 | // Retrieve overall information about the space on a filesystem. 377 | // This is a Wrapper for Unix statfs(). 378 | StatFs statfs = new StatFs(pathFile.getPath()); 379 | // 获取SDCard上每一个block的SIZE 380 | long nBlockSize = statfs.getBlockSize(); 381 | // 获取可供程序使用的Block的数量 382 | // long nAvailBlock = statfs.getAvailableBlocksLong(); 383 | long nAvailBlock = statfs.getAvailableBlocks(); 384 | // 计算SDCard剩余大小Byte 385 | long nSDFreeSize = nAvailBlock * nBlockSize; 386 | return nSDFreeSize; 387 | } 388 | } catch (Exception ex) { 389 | ex.printStackTrace(); 390 | } 391 | return 0; 392 | } 393 | } 394 | -------------------------------------------------------------------------------- /app/src/main/java/com/company/ijkpalyer/utils/LogUtils.java: -------------------------------------------------------------------------------- 1 | package com.company.ijkpalyer.utils; 2 | 3 | import android.util.Log; 4 | 5 | public class LogUtils { 6 | 7 | private static final String TAG = "ijkPlayer"; 8 | 9 | public static final boolean DEBUG = true; 10 | 11 | public static void i(String msg){ 12 | if(!DEBUG){ 13 | return; 14 | } 15 | String componName = Thread.currentThread().getStackTrace()[3].getClassName(); 16 | String className = componName.substring(componName.lastIndexOf(".")+1, componName.length()); 17 | String methodName = Thread.currentThread().getStackTrace()[3].getMethodName(); 18 | Log.i(TAG, TAG+" class:"+className+" called:"+methodName+" "+msg); 19 | } 20 | 21 | public static void e(String msg){ 22 | if(!DEBUG){ 23 | return; 24 | } 25 | String componName = Thread.currentThread().getStackTrace()[3].getClassName(); 26 | String className = componName.substring(componName.lastIndexOf(".")+1, componName.length()); 27 | String methodName = Thread.currentThread().getStackTrace()[3].getMethodName(); 28 | // Log.e(TAG, TAG + " class:" + className + " " + msg); 29 | Log.e(TAG, msg); 30 | 31 | // Log.e(TAG, TAG+" class:"+className+" called:"+methodName+" "+msg); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/jniLibs/arm64-v8a/libijkffmpeg.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ILoveLin/IjkRecordPlayer/ea8f5c029e8b1fe15c85bdfe964b453e625564cb/app/src/main/jniLibs/arm64-v8a/libijkffmpeg.so -------------------------------------------------------------------------------- /app/src/main/jniLibs/arm64-v8a/libijkplayer.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ILoveLin/IjkRecordPlayer/ea8f5c029e8b1fe15c85bdfe964b453e625564cb/app/src/main/jniLibs/arm64-v8a/libijkplayer.so -------------------------------------------------------------------------------- /app/src/main/jniLibs/arm64-v8a/libijksdl.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ILoveLin/IjkRecordPlayer/ea8f5c029e8b1fe15c85bdfe964b453e625564cb/app/src/main/jniLibs/arm64-v8a/libijksdl.so -------------------------------------------------------------------------------- /app/src/main/jniLibs/armeabi-v7a/libijkffmpeg.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ILoveLin/IjkRecordPlayer/ea8f5c029e8b1fe15c85bdfe964b453e625564cb/app/src/main/jniLibs/armeabi-v7a/libijkffmpeg.so -------------------------------------------------------------------------------- /app/src/main/jniLibs/armeabi-v7a/libijkplayer.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ILoveLin/IjkRecordPlayer/ea8f5c029e8b1fe15c85bdfe964b453e625564cb/app/src/main/jniLibs/armeabi-v7a/libijkplayer.so -------------------------------------------------------------------------------- /app/src/main/jniLibs/armeabi-v7a/libijksdl.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ILoveLin/IjkRecordPlayer/ea8f5c029e8b1fe15c85bdfe964b453e625564cb/app/src/main/jniLibs/armeabi-v7a/libijksdl.so -------------------------------------------------------------------------------- /app/src/main/jniLibs/armeabi/libijkffmpeg.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ILoveLin/IjkRecordPlayer/ea8f5c029e8b1fe15c85bdfe964b453e625564cb/app/src/main/jniLibs/armeabi/libijkffmpeg.so -------------------------------------------------------------------------------- /app/src/main/jniLibs/armeabi/libijkplayer.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ILoveLin/IjkRecordPlayer/ea8f5c029e8b1fe15c85bdfe964b453e625564cb/app/src/main/jniLibs/armeabi/libijkplayer.so -------------------------------------------------------------------------------- /app/src/main/jniLibs/armeabi/libijksdl.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ILoveLin/IjkRecordPlayer/ea8f5c029e8b1fe15c85bdfe964b453e625564cb/app/src/main/jniLibs/armeabi/libijksdl.so -------------------------------------------------------------------------------- /app/src/main/jniLibs/x86/libijkffmpeg.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ILoveLin/IjkRecordPlayer/ea8f5c029e8b1fe15c85bdfe964b453e625564cb/app/src/main/jniLibs/x86/libijkffmpeg.so -------------------------------------------------------------------------------- /app/src/main/jniLibs/x86/libijkplayer.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ILoveLin/IjkRecordPlayer/ea8f5c029e8b1fe15c85bdfe964b453e625564cb/app/src/main/jniLibs/x86/libijkplayer.so -------------------------------------------------------------------------------- /app/src/main/jniLibs/x86/libijksdl.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ILoveLin/IjkRecordPlayer/ea8f5c029e8b1fe15c85bdfe964b453e625564cb/app/src/main/jniLibs/x86/libijksdl.so -------------------------------------------------------------------------------- /app/src/main/jniLibs/x86_64/libijkffmpeg.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ILoveLin/IjkRecordPlayer/ea8f5c029e8b1fe15c85bdfe964b453e625564cb/app/src/main/jniLibs/x86_64/libijkffmpeg.so -------------------------------------------------------------------------------- /app/src/main/jniLibs/x86_64/libijkplayer.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ILoveLin/IjkRecordPlayer/ea8f5c029e8b1fe15c85bdfe964b453e625564cb/app/src/main/jniLibs/x86_64/libijkplayer.so -------------------------------------------------------------------------------- /app/src/main/jniLibs/x86_64/libijksdl.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ILoveLin/IjkRecordPlayer/ea8f5c029e8b1fe15c85bdfe964b453e625564cb/app/src/main/jniLibs/x86_64/libijksdl.so -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /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_ijk.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 16 | 17 | 23 | 24 | 32 | 33 |