一、测试
实现:
二、项目:
三、准备工作
1.添加权限,网络和读写
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
2.添加依赖包:
implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.50'
implementation 'com.github.dueeeke.dkplayer:dkplayer-java:2.5.5'
implementation 'com.github.dueeeke.dkplayer:dkplayer-armv7a:2.5.5'
implementation 'com.github.dueeeke.dkplayer:dkplayer-ui:2.5.5'
implementation 'me.jessyan:autosize:1.1.2'
3.在app的build.gradle的defaultConfig里面添加,和视频播放器有关
ndk {
// 设置支持的SO库架构
abiFilters "armeabi-v7a"
}
4.在AndroidManifest.xml中的application里面添加:
<!-- autoSize配置-->
<meta-data
android:name="design_width_in_dp"
android:value="360"/>
<meta-data
android:name="design_height_in_dp"
android:value="640"/>
5.保存视频到手机的路经封装:
(1).ExternalStorage.java
public class ExternalStorage {
/**
* 外部存储根目录
*/
private String sdkStorageRoot = null;
private static ExternalStorage instance;
private static final String TAG = "ExternalStorage";
private boolean hasPermission = true; // 是否拥有存储卡权限
private Context context;
private ExternalStorage() {
}
synchronized public static ExternalStorage getInstance() {
if (instance == null) {
instance = new ExternalStorage();
}
return instance;
}
public void init(Context context, String sdkStorageRoot) {
this.context = context;
// 判断权限
hasPermission = checkPermission(context);
if (!TextUtils.isEmpty(sdkStorageRoot)) {
File dir = new File(sdkStorageRoot);
if (!dir.exists()) {
dir.mkdirs();
}
if (dir.exists() && !dir.isFile()) {
this.sdkStorageRoot = sdkStorageRoot;
if (!sdkStorageRoot.endsWith("/")) {
this.sdkStorageRoot = sdkStorageRoot + "/";
}
}
}
if (TextUtils.isEmpty(this.sdkStorageRoot)) {
loadStorageState(context);
}
createSubFolders();
}
private void loadStorageState(Context context) {
String externalPath = Environment.getExternalStorageDirectory().getPath();
this.sdkStorageRoot = externalPath + "/" + context.getPackageName() + "/";
}
private void createSubFolders() {
boolean result = true;
File root = new File(sdkStorageRoot);
if (root.exists() && !root.isDirectory()) {
root.delete();
}
for (StorageType storageType : StorageType.values()) {
result &= makeDirectory(sdkStorageRoot + storageType.getStoragePath());
}
if (result) {
createNoMediaFile(sdkStorageRoot);
}
}
/**
* 创建目录
*
* @param path
* @return
*/
private boolean makeDirectory(String path) {
File file = new File(path);
boolean exist = file.exists();
if (!exist) {
exist = file.mkdirs();
}
return exist;
}
protected static String NO_MEDIA_FILE_NAME = ".nomedia";
private void createNoMediaFile(String path) {
File noMediaFile = new File(path + "/" + NO_MEDIA_FILE_NAME);
try {
if (!noMediaFile.exists()) {
noMediaFile.createNewFile();
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 文件全名转绝对路径(写)
*
* @param fileName 文件全名(文件名.扩展名)
* @return 返回绝对路径信息
*/
public String getWritePath(String fileName, StorageType fileType) {
return pathForName(fileName, fileType, false, false);
}
private String pathForName(String fileName, StorageType type, boolean dir,
boolean check) {
String directory = getDirectoryByDirType(type);
StringBuilder path = new StringBuilder(directory);
if (!dir) {
path.append(fileName);
}
String pathString = path.toString();
File file = new File(pathString);
if (check) {
if (file.exists()) {
if ((dir && file.isDirectory())
|| (!dir && !file.isDirectory())) {
return pathString;
}
}
return "";
} else {
return pathString;
}
}
/**
* 返回指定类型的文件夹路径
*
* @param fileType
* @return
*/
public String getDirectoryByDirType(StorageType fileType) {
return sdkStorageRoot + fileType.getStoragePath();
}
/**
* 根据输入的文件名和类型,找到该文件的全路径。
*
* @param fileName
* @param fileType
* @return 如果存在该文件,返回路径,否则返回空
*/
public String getReadPath(String fileName, StorageType fileType) {
if (TextUtils.isEmpty(fileName)) {
return "";
}
return pathForName(fileName, fileType, false, true);
}
public boolean isSdkStorageReady() {
String externalRoot = Environment.getExternalStorageDirectory().getAbsolutePath();
if (this.sdkStorageRoot.startsWith(externalRoot)) {
return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
} else {
return true;
}
}
/**
* 获取外置存储卡剩余空间
*
* @return
*/
public long getAvailableExternalSize() {
return getResidualSpace(sdkStorageRoot);
}
/**
* 获取目录剩余空间
*
* @param directoryPath
* @return
*/
private long getResidualSpace(String directoryPath) {
try {
StatFs sf = new StatFs(directoryPath);
long blockSize = sf.getBlockSize();
long availCount = sf.getAvailableBlocks();
long availCountByte = availCount * blockSize;
return availCountByte;
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
/**
* SD卡存储权限检查
*/
private boolean checkPermission(Context context) {
if (context == null) {
Log.e(TAG, "checkMPermission context null");
return false;
}
// 写权限有了默认就赋予了读权限
PackageManager pm = context.getPackageManager();
if (PackageManager.PERMISSION_GRANTED !=
pm.checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, context.getApplicationInfo().packageName)) {
Log.e(TAG, "without permission to access storage");
return false;
}
return true;
}
/**
* 有效性检查
*/
public boolean checkStorageValid() {
if (hasPermission) {
return true; // M以下版本&授权过的M版本不需要检查
}
hasPermission = checkPermission(context); // 检查是否已经获取权限了
if (hasPermission) {
Log.i(TAG, "get permission to access storage");
// 已经重新获得权限,那么重新检查一遍初始化过程
createSubFolders();
}
return hasPermission;
}
}
(2).StorageType.java
public enum StorageType {
TYPE_LOG(DirectoryName.LOG_DIRECTORY_NAME),
TYPE_TEMP(DirectoryName.TEMP_DIRECTORY_NAME),
TYPE_FILE(DirectoryName.FILE_DIRECTORY_NAME),
TYPE_AUDIO(DirectoryName.AUDIO_DIRECTORY_NAME),
TYPE_IMAGE(DirectoryName.IMAGE_DIRECTORY_NAME),
TYPE_VIDEO(DirectoryName.VIDEO_DIRECTORY_NAME),
TYPE_THUMB_IMAGE(DirectoryName.THUMB_DIRECTORY_NAME),
TYPE_THUMB_VIDEO(DirectoryName.THUMB_DIRECTORY_NAME),
;
private DirectoryName storageDirectoryName;
private long storageMinSize;
public String getStoragePath() {
return storageDirectoryName.getPath();
}
public long getStorageMinSize() {
return storageMinSize;
}
StorageType(DirectoryName dirName) {
this(dirName, StorageUtil.THRESHOLD_MIN_SPCAE);
}
StorageType(DirectoryName dirName, long storageMinSize) {
this.storageDirectoryName = dirName;
this.storageMinSize = storageMinSize;
}
enum DirectoryName {
AUDIO_DIRECTORY_NAME("audio/"),
DATA_DIRECTORY_NAME("data/"),
FILE_DIRECTORY_NAME("file/"),
LOG_DIRECTORY_NAME("log/"),
TEMP_DIRECTORY_NAME("temp/"),
IMAGE_DIRECTORY_NAME("image/"),
THUMB_DIRECTORY_NAME("thumb/"),
VIDEO_DIRECTORY_NAME("video/"),
;
private String path;
public String getPath() {
return path;
}
DirectoryName(String path) {
this.path = path;
}
}
}
(3).StorageUtil.java
public class StorageUtil {
public final static long K = 1024;
public final static long M = 1024 * 1024;
// 外置存储卡默认预警临界值
private static final long THRESHOLD_WARNING_SPACE = 100 * M;
// 保存文件时所需的最小空间的默认值
static final long THRESHOLD_MIN_SPCAE = 20 * M;
public static void init(Context context, String rootPath) {
ExternalStorage.getInstance().init(context, rootPath);
}
/**
* 获取文件保存路径,没有toast提示
*
* @param fileName
* @param fileType
* @return 可用的保存路径或者null
*/
public static String getWritePath(String fileName, StorageType fileType) {
return getWritePath(null, fileName, fileType, false);
}
/**
* 获取文件保存路径
*
* @param fileName 文件全名
* @param tip 空间不足时是否给出默认的toast提示
* @return 可用的保存路径或者null
*/
private static String getWritePath(Context context, String fileName, StorageType fileType, boolean tip) {
String path = ExternalStorage.getInstance().getWritePath(fileName, fileType);
if (TextUtils.isEmpty(path)) {
return null;
}
File dir = new File(path).getParentFile();
if (dir != null && !dir.exists()) {
dir.mkdirs();
}
return path;
}
/**
* 判断能否使用外置存储
*/
public static boolean isExternalStorageExist() {
return ExternalStorage.getInstance().isSdkStorageReady();
}
/**
* 判断外部存储是否存在,以及是否有足够空间保存指定类型的文件
*
* @param context
* @param fileType
* @param tip 是否需要toast提示
* @return false: 无存储卡或无空间可写, true: 表示ok
*/
public static boolean hasEnoughSpaceForWrite(Context context, StorageType fileType, boolean tip) {
if (!ExternalStorage.getInstance().isSdkStorageReady()) {
return false;
}
long residual = ExternalStorage.getInstance().getAvailableExternalSize();
if (residual < fileType.getStorageMinSize()) {
return false;
} else if (residual < THRESHOLD_WARNING_SPACE) {
}
return true;
}
/**
* 根据输入的文件名和类型,找到该文件的全路径。
*
* @param fileName
* @param fileType
* @return 如果存在该文件,返回路径,否则返回空
*/
public static String getReadPath(String fileName, StorageType fileType) {
return ExternalStorage.getInstance().getReadPath(fileName, fileType);
}
/**
* 获取文件保存路径,空间不足时有toast提示
*
* @param context
* @param fileName
* @param fileType
* @return 可用的保存路径或者null
*/
public static String getWritePath(Context context, String fileName, StorageType fileType) {
return getWritePath(context, fileName, fileType, true);
}
public static String getDirectoryByDirType(StorageType fileType) {
return ExternalStorage.getInstance().getDirectoryByDirType(fileType);
}
public static String getSystemImagePath() {
// String filePath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "eWorld";
// return filePath + "/image/";
if (Build.VERSION.SDK_INT > 7) {
String picturePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath();
return picturePath + "/eWorld/";
} else {
String picturePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath();
return picturePath + "/eWorld/";
}
}
public static String getSaveVideoPath() {
// String filePath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "eWorld";
String filePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath() + File.separator + "eWorld";
return filePath + "/video/";
}
public static boolean isInvalidVideoFile(String filePath) {
return filePath.toLowerCase().endsWith(".3gp")
|| filePath.toLowerCase().endsWith(".mp4");
}
public static void checkValid() {
ExternalStorage.getInstance().checkStorageValid();
}
}
6.视频播放器PlayVideoActivity.java
public class PlayVideoActivity extends AppCompatActivity {
private String url;
private IjkVideoView videoView;
private Button button;
private static String dstPath;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_play_video);
initData();
initViews();
initPlayer();
startPlay();
}
private void initViews() {
//播放view
videoView = findViewById(R.id.video_view);
button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final AlertDialog.Builder alert = new AlertDialog.Builder(PlayVideoActivity.this);
alert.setTitle("保存视频");
alert.setMessage("你确定要保存视频吗?");
alert.setNegativeButton("取消", null);
alert.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
saveVideo();
dialog.dismiss();
}
});
alert.show();
}
});
}
private void initData() {
if (getIntent() != null && getIntent().getExtras() != null) {
//视频url
url = getIntent().getExtras().getString("url");
}
}
/**
* 初始化Player
*/
private void initPlayer() {
com.dueeeke.videocontroller.StandardVideoController controller = new StandardVideoController(this);
IjkPlayer ijkPlayer = new IjkPlayer(this) {
@Override
public void setOptions() {
super.setOptions();
mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "dns_cache_clear", 1);
}
};
videoView.setPlayerConfig(new PlayerConfig.Builder().setCustomMediaPlayer(ijkPlayer).build());
videoView.setVideoController(controller);
}
/**
* 开始播放
*/
private void startPlay() {
if (url != null) {
videoView.setUrl(url);
videoView.start();
videoView.isFullScreen();
}
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
//横竖屏切换
refresh();
}
/**
* 横竖屏切换刷新
*/
private void refresh() {
final boolean isBaseOnWidth = (getResources().getDisplayMetrics().widthPixels <= getResources().getDisplayMetrics().heightPixels);
final Window window = getWindow();
if (window != null) {
window.getDecorView().post(new Runnable() {
@Override
public void run() {
window.getDecorView().setVisibility(View.GONE);
AutoSizeCompat.autoConvertDensity(PlayVideoActivity.this.getResources(), 420, isBaseOnWidth);
window.getDecorView().setVisibility(View.VISIBLE);
}
});
}
}
@Override
public void onResume() {
super.onResume();
if (videoView != null) {
videoView.resume();
}
}
@Override
public void onPause() {
super.onPause();
if (videoView != null) {
videoView.pause();
}
}
@Override
public void onBackPressed() {
if (!videoView.onBackPressed()) {
super.onBackPressed();
}
super.onBackPressed();
}
@Override
protected void onDestroy() {
super.onDestroy();
//释放播放器
if (videoView != null) {
videoView.release();
videoView = null;
}
}
private void saveVideo() {
String picPath = StorageUtil.getSaveVideoPath();
StorageUtil.init(PlayVideoActivity.this, picPath);
dstPath = picPath + (System.currentTimeMillis() / 1000) + ".mp4";
loadNewVideoProgress();
}
/**
* 保存视频,需要子线程
*/
private void loadNewVideoProgress() {
final ProgressDialog pd; //进度条对话框
pd = new ProgressDialog(this);
pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
pd.setMessage("正在下载更新");
pd.show();
//启动子线程下载任务
new Thread() {
@Override
public void run() {
try {
getFileFromServer(url, pd);
sleep(3000);
pd.dismiss(); //结束掉进度条对话框
} catch (Exception e) {
//保存视频失败
Toast.makeText(getApplicationContext(), "保存视频失败", Toast.LENGTH_LONG).show();
e.printStackTrace();
}
}
}.start();
}
/**
* 从服务器获取apk文件的代码
* 传入网址uri,进度条对象即可获得一个File文件
* (要在子线程中执行哦)
*/
public static File getFileFromServer(String uri, ProgressDialog pd) throws Exception {
//如果相等的话表示当前的sdcard挂载在手机上并且是可用的
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
URL url = new URL(uri);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5000);
//获取到文件的大小
pd.setMax(conn.getContentLength());
InputStream is = conn.getInputStream();
long time = System.currentTimeMillis();//当前时间的毫秒数
// File file = new File(Environment.getExternalStorageDirectory(), time+".mp4");
File file = new File(dstPath);
FileOutputStream fos = new FileOutputStream(file);
BufferedInputStream bis = new BufferedInputStream(is);
byte[] buffer = new byte[1024];
int len;
int total = 0;
while ((len = bis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
total += len;
//获取当前下载量
pd.setProgress(total);
}
fos.close();
bis.close();
is.close();
return file;
} else {
return null;
}
}
}
三、使用工作
主活动MainActivity.java
public class MainActivity extends AppCompatActivity {
private String url = "https://vd4.bdstatic.com/mda-jbppbefbbztvws50/sc/mda-jbppbefbbztvws50.mp4";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public void button(View view) {
Intent intent = new Intent(this, PlayVideoActivity.class);
intent.putExtra("url", Objects.requireNonNull(url));
startActivity(intent);
}
}