1.1.9 显示本地视频封面,增加本地视频删除

This commit is contained in:
2026-03-27 09:35:56 +08:00
parent 6f0f7c4c09
commit 73892596b1
6 changed files with 326 additions and 120 deletions

View File

@@ -18,8 +18,8 @@ android {
//There are no CERT files because If the mini sdk version is 23+, the AGP will ignore the V1 scheme signature. //There are no CERT files because If the mini sdk version is 23+, the AGP will ignore the V1 scheme signature.
minSdkVersion 23 minSdkVersion 23
targetSdkVersion 29 targetSdkVersion 29
versionCode 19 versionCode 20
versionName "1.1.8" versionName "1.1.9"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View File

@@ -1,6 +1,7 @@
package com.hainaos.vc.activity.category.online; package com.hainaos.vc.activity.category.online;
import android.content.Intent; import android.content.Intent;
import android.util.Log;
import android.view.View; import android.view.View;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@@ -29,7 +30,12 @@ import com.hjq.toast.Toaster;
import com.tencent.mmkv.MMKV; import com.tencent.mmkv.MMKV;
import java.io.File; import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class CategoryVideoActivity extends BaseMvvmActivity<CategoryVideoViewModel, ActivityCategoryVideoBinding> { public class CategoryVideoActivity extends BaseMvvmActivity<CategoryVideoViewModel, ActivityCategoryVideoBinding> {
private static final String TAG = "CategoryActivity"; private static final String TAG = "CategoryActivity";
@@ -118,6 +124,34 @@ public class CategoryVideoActivity extends BaseMvvmActivity<CategoryVideoViewMod
if (baseResponse.code == 200) { if (baseResponse.code == 200) {
VideoListData videoListData = baseResponse.data; VideoListData videoListData = baseResponse.data;
List<CategoryVideoInfo> categoryVideoInfos = videoListData.getData(); List<CategoryVideoInfo> categoryVideoInfos = videoListData.getData();
File file = new File(FileUtils.getHainaVideoPath(CategoryVideoActivity.this) + mCategoryInfo.getFolder());
if (file.exists()) {
String[] strings = file.list();
Log.e(TAG, "getViedoList: " + Arrays.toString(strings));
if (strings != null) {
List<String> paths = new ArrayList<>(Arrays.asList(strings));
ArrayList<CategoryVideoInfo> localVideoInfos = paths.stream()
.filter(new Predicate<String>() {
@Override
public boolean test(String s) {
return FileUtils.isVideoFile(s);
}
})
.map(new Function<String, CategoryVideoInfo>() {
@Override
public CategoryVideoInfo apply(String s) {
CategoryVideoInfo categoryVideoInfo = new CategoryVideoInfo();
categoryVideoInfo.setName(FileUtils.getFileNameWithoutExtension(s));
categoryVideoInfo.setFile_url(new File(file.getAbsolutePath() + File.separator + s).getAbsolutePath());
return categoryVideoInfo;
}
}).collect(Collectors.toCollection(ArrayList::new));
categoryVideoInfos.addAll(localVideoInfos);
}
}
mCategoryVideoAdapter.setData(categoryVideoInfos); mCategoryVideoAdapter.setData(categoryVideoInfos);

View File

@@ -6,7 +6,7 @@ import androidx.lifecycle.MutableLiveData;
import com.hainaos.vc.base.mvvm.BaseViewModel; import com.hainaos.vc.base.mvvm.BaseViewModel;
import com.hainaos.vc.bean.BaseResponse; import com.hainaos.vc.bean.BaseResponse;
import com.hainaos.vc.bean.LocalVideoInfo; import com.hainaos.vc.bean.CategoryVideoInfo;
import com.hainaos.vc.bean.VideoListData; import com.hainaos.vc.bean.VideoListData;
import com.hainaos.vc.bean.VideoUpdate; import com.hainaos.vc.bean.VideoUpdate;
import com.hainaos.vc.config.CommonConfig; import com.hainaos.vc.config.CommonConfig;
@@ -46,7 +46,7 @@ public class CategoryVideoViewModel extends BaseViewModel<ActivityCategoryVideoB
} }
@Deprecated @Deprecated
public MutableLiveData<ArrayList<LocalVideoInfo>> mLocalVideoInfosData = new MutableLiveData<>(); public MutableLiveData<ArrayList<CategoryVideoInfo>> mLocalVideoInfosData = new MutableLiveData<>();
@Deprecated @Deprecated
public void getViedoList(String dir) { public void getViedoList(String dir) {
@@ -56,12 +56,12 @@ public class CategoryVideoViewModel extends BaseViewModel<ActivityCategoryVideoB
Log.e(TAG, "getViedoList: " + Arrays.toString(strings)); Log.e(TAG, "getViedoList: " + Arrays.toString(strings));
if (strings != null) { if (strings != null) {
List<String> paths = new ArrayList<>(Arrays.asList(strings)); List<String> paths = new ArrayList<>(Arrays.asList(strings));
ArrayList<LocalVideoInfo> localVideoInfos = paths.stream().map(new Function<String, LocalVideoInfo>() { ArrayList<CategoryVideoInfo> localVideoInfos = paths.stream().map(new Function<String, CategoryVideoInfo>() {
@Override @Override
public LocalVideoInfo apply(String s) { public CategoryVideoInfo apply(String s) {
LocalVideoInfo localVideoInfo = new LocalVideoInfo(); CategoryVideoInfo localVideoInfo = new CategoryVideoInfo();
localVideoInfo.setFile_name(FileUtils.getFileNameWithoutExtension(s)); localVideoInfo.setName(FileUtils.getFileNameWithoutExtension(s));
localVideoInfo.setLocalPath(new File(file.getAbsolutePath() + File.separator + s).getAbsolutePath()); localVideoInfo.setFile_url(new File(file.getAbsolutePath() + File.separator + s).getAbsolutePath());
return localVideoInfo; return localVideoInfo;
} }
}).collect(Collectors.toCollection(ArrayList::new)); }).collect(Collectors.toCollection(ArrayList::new));

View File

@@ -24,12 +24,14 @@ import com.arialyy.annotations.Download;
import com.arialyy.aria.core.Aria; import com.arialyy.aria.core.Aria;
import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.task.DownloadTask; import com.arialyy.aria.core.task.DownloadTask;
import com.bumptech.glide.Glide;
import com.hainaos.vc.R; import com.hainaos.vc.R;
import com.hainaos.vc.activity.player.DecryptionPlayerActivity; import com.hainaos.vc.activity.player.DecryptionPlayerActivity;
import com.hainaos.vc.activity.preview.VideoPreviewActivity; import com.hainaos.vc.activity.preview.VideoPreviewActivity;
import com.hainaos.vc.bean.CategoryVideoInfo; import com.hainaos.vc.bean.CategoryVideoInfo;
import com.hainaos.vc.config.Permissions; import com.hainaos.vc.config.Permissions;
import com.hainaos.vc.dialog.PermissionsDialog; import com.hainaos.vc.dialog.PermissionsDialog;
import com.hainaos.vc.utils.FFmpegUtils;
import com.hainaos.vc.utils.FileUtils; import com.hainaos.vc.utils.FileUtils;
import com.hainaos.vc.utils.GlideLoadUtils; import com.hainaos.vc.utils.GlideLoadUtils;
import com.hainaos.vc.utils.TimeUtils; import com.hainaos.vc.utils.TimeUtils;
@@ -43,6 +45,8 @@ import com.shehuan.niv.NiceImageView;
import java.io.File; import java.io.File;
import java.util.List; import java.util.List;
import io.reactivex.rxjava3.core.Observer;
import io.reactivex.rxjava3.disposables.Disposable;
import me.jessyan.autosize.AutoSizeCompat; import me.jessyan.autosize.AutoSizeCompat;
public class CategoryVideoAdapter extends RecyclerView.Adapter<CategoryVideoAdapter.VideoHolder> { public class CategoryVideoAdapter extends RecyclerView.Adapter<CategoryVideoAdapter.VideoHolder> {
@@ -82,131 +86,187 @@ public class CategoryVideoAdapter extends RecyclerView.Adapter<CategoryVideoAdap
if (!TextUtils.isEmpty(name)) { if (!TextUtils.isEmpty(name)) {
holder.tv_title.setText(name); holder.tv_title.setText(name);
} }
String url = categoryVideoInfo.getFile_url(); String url = categoryVideoInfo.getFile_url();
String cover = categoryVideoInfo.getCover(); if (FileUtils.isLocalFileUriType(url)) {
String md5 = categoryVideoInfo.getMd5(); File file = new File(url);
long sizeBytes = categoryVideoInfo.getFile_size();
String fileSize = Formatter.formatFileSize(mContext, sizeBytes);
holder.tv_size.setText("视频大小: " + fileSize);
String fileName = FileUtils.getFileNamefromURL(url);
File file = new File(FileUtils.getHainaVideoPath(mContext) + mDirName + File.separator + fileName);
DownloadEntity entity = Aria.download(mContext).getFirstDownloadEntity(url);
if (entity == null) {
if (file.exists()) { if (file.exists()) {
String dirPaht = file.getParent();
String videoFileNameWithoutEx = FileUtils.getFileNameWithoutExtension(url);
File localCoverFile = new File(dirPaht + File.separator + videoFileNameWithoutEx + ".png");
if (localCoverFile.exists()) {
GlideLoadUtils.getInstance().glideLoad(mContext, localCoverFile, holder.video_image, R.drawable.picture_split);
} else {
Glide.with(mContext).load(localCoverFile).centerCrop().error(R.drawable.picture_split).into(holder.video_image);
}
String fileSize = Formatter.formatFileSize(mContext, file.length());
if (url.endsWith(".hnv")) {
holder.tv_duration.setText("视频时长: 00:00");
} else {
FFmpegUtils.getDurationInMilliseconds(url, new Observer<Integer>() {
@Override
public void onSubscribe(@io.reactivex.rxjava3.annotations.NonNull Disposable d) {
}
@Override
public void onNext(@io.reactivex.rxjava3.annotations.NonNull Integer integer) {
holder.tv_duration.setText("视频时长: " + TimeUtils.TimeFormat(integer * 1000));
}
@Override
public void onError(@io.reactivex.rxjava3.annotations.NonNull Throwable e) {
}
@Override
public void onComplete() {
}
});
}
holder.tv_size.setText("视频大小: " + fileSize);
holder.tv_download.setText("删除"); holder.tv_download.setText("删除");
holder.tv_download.setBackground(mContext.getDrawable(R.drawable.bg_delete_button)); holder.tv_download.setBackground(mContext.getDrawable(R.drawable.bg_delete_button));
} else {
holder.tv_download.setText("下载");
holder.tv_download.setBackground(mContext.getDrawable(R.drawable.bg_download_button));
}
} else { holder.tv_download.setOnClickListener(new View.OnClickListener() {
int state = entity.getState(); @Override
switch (state) { public void onClick(View v) {
case 1: // if (file.exists()) {
if (file.exists()) { showDeleteLocalDialog(file.getAbsolutePath(), position);
holder.tv_download.setText("删除"); // }
holder.tv_download.setBackground(mContext.getDrawable(R.drawable.bg_delete_button));
} else {
holder.tv_download.setText("下载");
holder.tv_download.setBackground(mContext.getDrawable(R.drawable.bg_download_button));
} }
break; });
case 2: }
holder.tv_download.setText("停止"); } else {
holder.tv_download.setBackground(mContext.getDrawable(R.drawable.bg_download_button)); String cover = categoryVideoInfo.getCover();
break; String md5 = categoryVideoInfo.getMd5();
case 3: long sizeBytes = categoryVideoInfo.getFile_size();
holder.tv_download.setText("等待"); String fileSize = Formatter.formatFileSize(mContext, sizeBytes);
holder.tv_download.setBackground(mContext.getDrawable(R.drawable.bg_download_button)); holder.tv_size.setText("视频大小: " + fileSize);
break;
case -1: String fileName = FileUtils.getFileNamefromURL(url);
case 0:
case 5: File file = new File(FileUtils.getHainaVideoPath(mContext) + mDirName + File.separator + fileName);
case 6:
case 7: DownloadEntity entity = Aria.download(mContext).getFirstDownloadEntity(url);
if (entity == null) {
if (file.exists()) {
holder.tv_download.setText("删除");
holder.tv_download.setBackground(mContext.getDrawable(R.drawable.bg_delete_button));
} else {
holder.tv_download.setText("下载"); holder.tv_download.setText("下载");
holder.tv_download.setBackground(mContext.getDrawable(R.drawable.bg_download_button)); holder.tv_download.setBackground(mContext.getDrawable(R.drawable.bg_download_button));
break; }
case 4:
int percent = entity.getPercent();
holder.tv_download.setText(percent + "%");
holder.tv_download.setBackground(mContext.getDrawable(R.drawable.bg_download_button));
break;
}
}
} else {
GlideLoadUtils.getInstance().glideLoad(mContext, cover, holder.video_image); int state = entity.getState();
switch (state) {
holder.tv_duration.setText("视频时长: " + TimeUtils.TimeFormat(categoryVideoInfo.getDuration() * 1000)); case 1:
if (file.exists()) {
holder.tv_download.setOnClickListener(new View.OnClickListener() { holder.tv_download.setText("删除");
@Override holder.tv_download.setBackground(mContext.getDrawable(R.drawable.bg_delete_button));
public void onClick(View v) {
if (entity == null) {
if (file.exists()) {
showDialog(file.getAbsolutePath(), position);
} else {
if (XXPermissions.isGranted(mContext, Permissions.STORAGE_PERMISSIONS)) {
FileUtils.ariaDownload(mContext, mDirName, url, md5);
FileUtils.ariaDownload(mContext, mDirName, cover);
// FileUtils.ariaDownloadCover(mContext, mDirName, cover, md5);
} else { } else {
showPermissionsDialog(mContext); holder.tv_download.setText("下载");
holder.tv_download.setBackground(mContext.getDrawable(R.drawable.bg_download_button));
}
break;
case 2:
holder.tv_download.setText("停止");
holder.tv_download.setBackground(mContext.getDrawable(R.drawable.bg_download_button));
break;
case 3:
holder.tv_download.setText("等待");
holder.tv_download.setBackground(mContext.getDrawable(R.drawable.bg_download_button));
break;
case -1:
case 0:
case 5:
case 6:
case 7:
holder.tv_download.setText("下载");
holder.tv_download.setBackground(mContext.getDrawable(R.drawable.bg_download_button));
break;
case 4:
int percent = entity.getPercent();
holder.tv_download.setText(percent + "%");
holder.tv_download.setBackground(mContext.getDrawable(R.drawable.bg_download_button));
break;
}
}
GlideLoadUtils.getInstance().glideLoad(mContext, cover, holder.video_image);
holder.tv_duration.setText("视频时长: " + TimeUtils.TimeFormat(categoryVideoInfo.getDuration() * 1000));
holder.tv_download.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (entity == null) {
if (file.exists()) {
showDialog(file.getAbsolutePath(), position);
} else {
if (XXPermissions.isGranted(mContext, Permissions.STORAGE_PERMISSIONS)) {
FileUtils.ariaDownload(mContext, mDirName, url, md5);
FileUtils.ariaDownload(mContext, mDirName, cover);
// FileUtils.ariaDownloadCover(mContext, mDirName, cover, md5);
} else {
showPermissionsDialog(mContext);
}
}
} else {
int state = entity.getState();
switch (state) {
case 1:
if (file.exists()) {
showDialog(file.getAbsolutePath(), position);
} else {
if (XXPermissions.isGranted(mContext, Permissions.STORAGE_PERMISSIONS)) {
FileUtils.ariaDownload(mContext, mDirName, url, md5);
FileUtils.ariaDownload(mContext, mDirName, cover);
// FileUtils.ariaDownloadCover(mContext, mDirName, cover, md5);
} else {
showPermissionsDialog(mContext);
}
}
break;
case -1:
case 0:
case 2:
case 3:
case 5:
case 6:
case 7:
case 4:
} }
} }
} else { }
int state = entity.getState(); });
switch (state) {
case 1:
if (file.exists()) {
showDialog(file.getAbsolutePath(), position);
} else {
if (XXPermissions.isGranted(mContext, Permissions.STORAGE_PERMISSIONS)) {
FileUtils.ariaDownload(mContext, mDirName, url, md5);
FileUtils.ariaDownload(mContext, mDirName, cover);
// FileUtils.ariaDownloadCover(mContext, mDirName, cover, md5);
} else {
showPermissionsDialog(mContext);
}
}
break;
case -1:
case 0:
case 2:
case 3:
case 5:
case 6:
case 7:
case 4:
} holder.video_image.setOnClickListener(new View.OnClickListener() {
} @Override
} public void onClick(View v) {
}); if (file.exists()) {
holder.video_image.setOnClickListener(new View.OnClickListener() { if (file.getAbsolutePath().endsWith(".hnv")) {
@Override Intent intent = new Intent(mContext, DecryptionPlayerActivity.class);
public void onClick(View v) { intent.putExtra("url", file.getAbsolutePath());
if (file.exists()) { mContext.startActivity(intent);
if (file.getAbsolutePath().endsWith(".hnv")) { } else {
Intent intent = new Intent(mContext, DecryptionPlayerActivity.class); Intent intent = new Intent(mContext, VideoPreviewActivity.class);
intent.putExtra("url", file.getAbsolutePath()); intent.putExtra("cover", cover);
mContext.startActivity(intent); intent.putExtra("url", file.getAbsolutePath());
mContext.startActivity(intent);
}
} else { } else {
Intent intent = new Intent(mContext, VideoPreviewActivity.class); Toaster.show("请先下载视频");
intent.putExtra("cover", cover);
intent.putExtra("url", file.getAbsolutePath());
mContext.startActivity(intent);
} }
} else {
Toaster.show("请先下载视频");
} }
} });
}); }
} }
private PermissionsDialog mPermissionsDialog; private PermissionsDialog mPermissionsDialog;
@@ -284,6 +344,34 @@ public class CategoryVideoAdapter extends RecyclerView.Adapter<CategoryVideoAdap
dialog.show(); dialog.show();
} }
private void showDeleteLocalDialog(String path, int position) {
CustomDialog dialog = new CustomDialog(mContext);
dialog.setTitle("删除文件")
.setMessage("确定要删除文件 " + FileUtils.getFileName(path) + "")
.setPositive("确定")
.setNegtive("取消")
.setOnClickBottomListener(new CustomDialog.OnClickBottomListener() {
@Override
public void onPositiveClick() {
dialog.dismiss();
File file = new File(path);
if (file.delete()) {
mLocalVideoInfos.remove(position);
notifyDataSetChanged();
ToastUtil.show("删除成功");
} else {
ToastUtil.show("删除失败,检查权限是否开启");
}
}
@Override
public void onNegtiveClick() {
dialog.dismiss();
}
});
dialog.show();
}
public void removeItem(int position) { public void removeItem(int position) {
if (null != mLocalVideoInfos.get(position)) { if (null != mLocalVideoInfos.get(position)) {
mLocalVideoInfos.remove(position); mLocalVideoInfos.remove(position);

View File

@@ -101,6 +101,7 @@ public class VideoAdapter extends RecyclerView.Adapter<VideoAdapter.VideoHolder>
final String localPath = localVideoInfo.getLocalPath(); final String localPath = localVideoInfo.getLocalPath();
Log.e(TAG, "onBindViewHolder: " + localPath); Log.e(TAG, "onBindViewHolder: " + localPath);
String videoFileName = FileUtils.getFileName(localPath); String videoFileName = FileUtils.getFileName(localPath);
String videoFileNameWithoutEx = FileUtils.getFileNameWithoutExtension(localPath);
holder.title.setText(videoFileName); holder.title.setText(videoFileName);
File file = new File(localPath); File file = new File(localPath);
@@ -109,6 +110,7 @@ public class VideoAdapter extends RecyclerView.Adapter<VideoAdapter.VideoHolder>
if (file.getName().endsWith(".hnv")) { if (file.getName().endsWith(".hnv")) {
String dirPaht = file.getParent(); String dirPaht = file.getParent();
Log.e(TAG, "onBindViewHolder: dirPaht = " + dirPaht); Log.e(TAG, "onBindViewHolder: dirPaht = " + dirPaht);
File localCoverFile = new File(dirPaht + File.separator + videoFileNameWithoutEx + ".png");
if (mCoverMap != null) { if (mCoverMap != null) {
String coverUrl = mCoverMap.get(videoFileName); String coverUrl = mCoverMap.get(videoFileName);
Log.e(TAG, "onBindViewHolder: coverUrl = " + coverUrl); Log.e(TAG, "onBindViewHolder: coverUrl = " + coverUrl);
@@ -123,10 +125,18 @@ public class VideoAdapter extends RecyclerView.Adapter<VideoAdapter.VideoHolder>
FileUtils.ariaDownloadCover(mContext, coverFile.getParent(), coverUrl); FileUtils.ariaDownloadCover(mContext, coverFile.getParent(), coverUrl);
} }
} else { } else {
holder.video_image.setImageDrawable(mContext.getDrawable(R.drawable.picture_split)); if (localCoverFile.exists()) {
Glide.with(mContext).load(localCoverFile).centerCrop().error(R.drawable.picture_split).into(holder.video_image);
} else {
holder.video_image.setImageDrawable(mContext.getDrawable(R.drawable.picture_split));
}
} }
} else { } else {
holder.video_image.setImageDrawable(mContext.getDrawable(R.drawable.picture_split)); if (localCoverFile.exists()) {
Glide.with(mContext).load(localCoverFile).centerCrop().error(R.drawable.picture_split).into(holder.video_image);
} else {
holder.video_image.setImageDrawable(mContext.getDrawable(R.drawable.picture_split));
}
} }
} else { } else {
Glide.with(mContext).load(file).centerCrop().error(R.drawable.picture_split).into(holder.video_image); Glide.with(mContext).load(file).centerCrop().error(R.drawable.picture_split).into(holder.video_image);

View File

@@ -2,6 +2,7 @@ package com.hainaos.vc.utils;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri;
import android.os.Environment; import android.os.Environment;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
@@ -17,6 +18,8 @@ import com.hainaos.vc.service.DownloadService;
import java.io.File; import java.io.File;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.HashSet;
import java.util.Set;
public class FileUtils { public class FileUtils {
private static final String TAG = "FileUtils"; private static final String TAG = "FileUtils";
@@ -40,6 +43,77 @@ public class FileUtils {
return name; return name;
} }
public static boolean isLocalFileUriType(String uriString) {
if (TextUtils.isEmpty(uriString)) {
return false;
}
if (uriString.startsWith("/")) {
return true;
}
// 将字符串解析为 Uri 对象
Uri uri = Uri.parse(uriString);
// 获取 Scheme
String scheme = uri.getScheme();
if (scheme != null) {
Log.e(TAG, "isLocalFileUriType: " + scheme);
switch (scheme.toLowerCase()) {
case "http":
case "https":
return false;
case "content":
case "file":
return true;
default:
return false;
}
} else {
return false;
}
}
private static Set<String> videoFormat = new HashSet<String>() {{
this.add(".hnv");
this.add(".mp4");
this.add(".avi");
this.add(".mkv");
this.add(".flv");
}};
private static Set<String> pictureFormat = new HashSet<String>() {{
this.add(".png");
this.add(".jpg");
this.add(".jpeg");
this.add(".bmp");
}};
public static boolean isVideoFile(String fileName) {
if (TextUtils.isEmpty(fileName)) {
return false;
} else {
if (!fileName.startsWith(".")) {
return videoFormat.contains(getFileType(fileName));
} else {
return videoFormat.contains(fileName);
}
}
}
public static String getFileType(String url) {
if (TextUtils.isEmpty(url)) {
return "";
}
Log.e(TAG, "getFileType: " + url);
if (!url.contains("/")) {
if (url.contains(".")) {
return url.substring(url.indexOf("."));
} else {
return "";
}
} else {
String fileName = url.substring(url.lastIndexOf("/"));
return fileName.substring(fileName.indexOf("."));
}
}
/** /**
* 转换文件大小 MB * 转换文件大小 MB
*/ */