Compare commits

...

4 Commits

49 changed files with 1823 additions and 396 deletions

View File

@@ -18,8 +18,10 @@ android {
//There are no CERT files because If the mini sdk version is 23+, the AGP will ignore the V1 scheme signature.
minSdkVersion 23
targetSdkVersion 29
versionCode 14
versionName "1.1.3"
versionCode 20
versionName "1.1.9"
// versionCode 21
// versionName "1.2.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -188,6 +190,8 @@ dependencies {
annotationProcessor 'com.arialyy.aria:compiler:3.8.15'
//MMKV
implementation 'com.tencent:mmkv-static:1.2.13'
implementation 'com.tencent.bugly:crashreport:4.1.9.2'
implementation 'com.iqiyi.xcrash:xcrash-android-lib:3.0.0'
//工具类
implementation 'com.blankj:utilcodex:1.31.0'
//沉浸状态栏

View File

@@ -4,6 +4,7 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:name=".base.BaseApplication"

View File

@@ -9,9 +9,13 @@ import com.hainaos.vc.R;
import com.hainaos.vc.adapter.CategoryAdapter;
import com.hainaos.vc.base.mvvm.BaseMvvmActivity;
import com.hainaos.vc.bean.CategoryInfo;
import com.hainaos.vc.bean.CategoryUpdateInfo;
import com.hainaos.vc.bean.VideoUpdate;
import com.hainaos.vc.databinding.ActivityCategoryListBinding;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class CategoryListActivity extends BaseMvvmActivity<CategoryListViewModel, ActivityCategoryListBinding> {
private static final String TAG = "CategoryListActivity";
@@ -46,8 +50,26 @@ public class CategoryListActivity extends BaseMvvmActivity<CategoryListViewModel
mCategoryAdapter.setCategoryInfos(categoryInfos);
}
});
mViewModel.getCategoryList();
mViewModel.mVideoUpdateMutableLiveData.observe(this, new Observer<VideoUpdate>() {
@Override
public void onChanged(VideoUpdate videoUpdate) {
if (videoUpdate.getHas_new() == 1) {
mViewModel.getCategoryUpdate();
}
}
});
mViewModel.mCategoryUpdateInfoListData.observe(this, new Observer<List<CategoryUpdateInfo>>() {
@Override
public void onChanged(List<CategoryUpdateInfo> categoryUpdateInfos) {
Map<String, Integer> map = categoryUpdateInfos.stream().collect(Collectors.toMap(CategoryUpdateInfo::getCategory_uuid, CategoryUpdateInfo::getCount));
mCategoryAdapter.setCategoryUpdateInfoMap(map);
}
});
mViewModel.getCategoryList();
mViewModel.getVideoUpdate();
}

View File

@@ -7,9 +7,14 @@ import androidx.lifecycle.MutableLiveData;
import com.hainaos.vc.base.mvvm.BaseViewModel;
import com.hainaos.vc.bean.BaseResponse;
import com.hainaos.vc.bean.CategoryInfo;
import com.hainaos.vc.bean.CategoryUpdateInfo;
import com.hainaos.vc.bean.VideoUpdate;
import com.hainaos.vc.config.CommonConfig;
import com.hainaos.vc.databinding.ActivityCategoryListBinding;
import com.hainaos.vc.network.NetInterfaceManager;
import com.hainaos.vc.utils.TimeUtils;
import com.hjq.toast.Toaster;
import com.tencent.mmkv.MMKV;
import com.trello.rxlifecycle4.RxLifecycle;
import com.trello.rxlifecycle4.android.ActivityEvent;
@@ -20,8 +25,9 @@ import io.reactivex.rxjava3.core.Observer;
import io.reactivex.rxjava3.disposables.Disposable;
public class CategoryListViewModel extends BaseViewModel<ActivityCategoryListBinding, ActivityEvent> {
private static final String TAG = "CategoryListViewModel";
private static final String TAG = "CategoryViewModel";
private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
@Override
public ActivityCategoryListBinding getVDBinding() {
@@ -66,4 +72,76 @@ public class CategoryListViewModel extends BaseViewModel<ActivityCategoryListBin
}
});
}
public MutableLiveData<VideoUpdate> mVideoUpdateMutableLiveData = new MutableLiveData<>();
public void getVideoUpdate() {
String currentTime = TimeUtils.transferMillisecondToDate(System.currentTimeMillis());
String time = mMMKV.decodeString(CommonConfig.CATEGORY_VIDEOS_CHECK_UPDATE_TIME, currentTime);
Log.e(TAG, "getVideoUpdate: " + time);
NetInterfaceManager.getInstance().getVideoUpdateObservable(time)
.compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY))
.subscribe(new Observer<BaseResponse<VideoUpdate>>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
Log.e("getVideoUpdate", "onSubscribe: ");
}
@Override
public void onNext(@NonNull BaseResponse<VideoUpdate> baseResponse) {
Log.e("getVideoUpdate", "onNext: " + baseResponse);
mMMKV.encode(CommonConfig.CATEGORY_VIDEOS_CHECK_UPDATE_TIME, currentTime);
if (baseResponse.code == 200) {
VideoUpdate videoUpdate = baseResponse.data;
mVideoUpdateMutableLiveData.setValue(videoUpdate);
}
}
@Override
public void onError(@NonNull Throwable e) {
Log.e("getVideoUpdate", "onError: " + e.getMessage());
}
@Override
public void onComplete() {
Log.e("getVideoUpdate", "onComplete: ");
}
});
}
public MutableLiveData<List<CategoryUpdateInfo>> mCategoryUpdateInfoListData = new MutableLiveData<>();
public void getCategoryUpdate() {
String currentTime = TimeUtils.transferMillisecondToDate(System.currentTimeMillis());
String time = mMMKV.decodeString(CommonConfig.CHECK_CATEGORY_UPDATE_NUMBER_TIME, currentTime);
Log.e(TAG, "getCategoryUpdate: " + time);
NetInterfaceManager.getInstance().getCategoryUpdateObservable(time)
.compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY))
.subscribe(new Observer<BaseResponse<List<CategoryUpdateInfo>>>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
Log.e("getCategoryUpdate", "onSubscribe: ");
}
@Override
public void onNext(@NonNull BaseResponse<List<CategoryUpdateInfo>> listBaseResponse) {
Log.e("getCategoryUpdate", "onNext: " + listBaseResponse);
if (listBaseResponse.code == 200) {
List<CategoryUpdateInfo> categoryUpdateInfos = listBaseResponse.data;
mCategoryUpdateInfoListData.setValue(categoryUpdateInfos);
}
mMMKV.encode(CommonConfig.CHECK_CATEGORY_UPDATE_NUMBER_TIME, currentTime);
}
@Override
public void onError(@NonNull Throwable e) {
Log.e("getCategoryUpdate", "onError: " + e.getMessage());
}
@Override
public void onComplete() {
Log.e("getCategoryUpdate", "onComplete: ");
}
});
}
}

View File

@@ -1,20 +1,23 @@
package com.hainaos.vc.activity.category.local;
import android.content.Intent;
import android.os.Build;
import android.text.TextUtils;
import android.view.View;
import androidx.annotation.RequiresApi;
import androidx.lifecycle.Observer;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.hainaos.vc.R;
import com.hainaos.vc.adapter.VideoAdapter;
import com.hainaos.vc.base.mvvm.BaseMvvmActivity;
import com.hainaos.vc.bean.BaseResponse;
import com.hainaos.vc.bean.CategoryInfo;
import com.hainaos.vc.bean.CategoryVideoInfo;
import com.hainaos.vc.bean.LocalVideoInfo;
import com.hainaos.vc.bean.VideoListData;
import com.hainaos.vc.bean.VideoUpdate;
import com.hainaos.vc.databinding.ActivityCategoryLocalBinding;
import com.hainaos.vc.fragment.passwd.PasswdDialogFragment;
import com.hainaos.vc.utils.FileUtils;
@@ -27,6 +30,9 @@ import com.hjq.toast.Toaster;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class LocalCategoryActivity extends BaseMvvmActivity<LocalCategoryViewModel, ActivityCategoryLocalBinding> {
private static final String TAG = "LocalCategoryActivity";
@@ -72,13 +78,13 @@ public class LocalCategoryActivity extends BaseMvvmActivity<LocalCategoryViewMod
});
mViewDataBinding.rvVideo.setAdapter(mVideoAdapter);
mViewDataBinding.swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
mViewDataBinding.swipeRefreshLayout.setRefreshing(true);
mViewModel.getVideoList(mCategoryInfo.getFolder());
}
});
// mViewDataBinding.swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
// @Override
// public void onRefresh() {
// mViewDataBinding.swipeRefreshLayout.setRefreshing(true);
// mViewModel.getVideoList(mCategoryInfo.getFolder());
// }
// });
}
@Override
@@ -104,13 +110,17 @@ public class LocalCategoryActivity extends BaseMvvmActivity<LocalCategoryViewMod
mViewDataBinding.rvVideo.setVisibility(View.VISIBLE);
}
mVideoAdapter.setData(localVideoInfos);
mViewDataBinding.swipeRefreshLayout.setRefreshing(false);
// mViewDataBinding.swipeRefreshLayout.setRefreshing(false);
}
});
mViewModel.mCategoryVideoInfoListData.observe(this, new Observer<BaseResponse<VideoListData>>() {
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public void onChanged(BaseResponse<VideoListData> baseResponse) {
if (baseResponse.code == 200) {
List<CategoryVideoInfo> categoryVideoInfos = baseResponse.data.getData();
Map<String, String> coverMap = categoryVideoInfos.stream().collect(Collectors.toMap(categoryVideoInfo -> FileUtils.getFileNamefromURL(categoryVideoInfo.getFile_url()), CategoryVideoInfo::getCover));
mVideoAdapter.setCoverMap(coverMap);
mViewModel.getVideoList(mCategoryInfo.getFolder());
} else {
Toaster.show("密码错误,请重新输入");
@@ -126,6 +136,18 @@ public class LocalCategoryActivity extends BaseMvvmActivity<LocalCategoryViewMod
}
}
});
mViewModel.mVideoUpdateMutableLiveData.observe(this, new Observer<VideoUpdate>() {
@Override
public void onChanged(VideoUpdate videoUpdate) {
if (videoUpdate.getHas_new() == 1) {
mViewDataBinding.ivUpdate.setVisibility(View.VISIBLE);
} else {
mViewDataBinding.ivUpdate.setVisibility(View.GONE);
}
}
});
if (TextUtils.isEmpty(mPasswd)) {
mViewModel.getVideoList(mCategoryInfo.getFolder());
} else {

View File

@@ -8,10 +8,13 @@ import com.hainaos.vc.base.mvvm.BaseViewModel;
import com.hainaos.vc.bean.BaseResponse;
import com.hainaos.vc.bean.LocalVideoInfo;
import com.hainaos.vc.bean.VideoListData;
import com.hainaos.vc.bean.VideoUpdate;
import com.hainaos.vc.config.CommonConfig;
import com.hainaos.vc.databinding.ActivityCategoryLocalBinding;
import com.hainaos.vc.network.NetInterfaceManager;
import com.hainaos.vc.utils.FileUtils;
import com.hainaos.vc.utils.VideoUtils;
import com.tencent.mmkv.MMKV;
import com.trello.rxlifecycle4.RxLifecycle;
import com.trello.rxlifecycle4.android.ActivityEvent;
@@ -33,8 +36,9 @@ import io.reactivex.rxjava3.schedulers.Schedulers;
import wseemann.media.FFmpegMediaMetadataRetriever;
public class LocalCategoryViewModel extends BaseViewModel<ActivityCategoryLocalBinding, ActivityEvent> {
private static final String TAG = "LocalCategoryViewModel";
private static final String TAG = "CategoryViewModel";
private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
@Override
public ActivityCategoryLocalBinding getVDBinding() {
@@ -49,6 +53,7 @@ public class LocalCategoryViewModel extends BaseViewModel<ActivityCategoryLocalB
public MutableLiveData<BaseResponse<VideoListData>> mCategoryVideoInfoListData = new MutableLiveData<>();
public void getVideoList(String uuid, String password) {
// getVideoUpdate(uuid);
NetInterfaceManager.getInstance().getVideoListObservable(uuid, password)
.compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY))
.subscribe(new Observer<BaseResponse<VideoListData>>() {
@@ -113,6 +118,9 @@ public class LocalCategoryViewModel extends BaseViewModel<ActivityCategoryLocalB
localVideoInfo.setFile_name(FileUtils.getFileNameWithoutExtension(s));
localVideoInfo.setLocalPath(videoFile.getAbsolutePath());
if (s.endsWith(".hnv")) {
localVideoInfo.setDuration(0);
} else {
FFmpegMediaMetadataRetriever mmr = new FFmpegMediaMetadataRetriever();
try {
long time = System.currentTimeMillis();
@@ -126,6 +134,7 @@ public class LocalCategoryViewModel extends BaseViewModel<ActivityCategoryLocalB
mmr.release();//释放资源
}
}
return localVideoInfo;
}
}).collect(Collectors.toCollection(ArrayList::new));
@@ -134,6 +143,7 @@ public class LocalCategoryViewModel extends BaseViewModel<ActivityCategoryLocalB
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY))
.subscribe(new Observer<ArrayList<LocalVideoInfo>>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
@@ -160,4 +170,40 @@ public class LocalCategoryViewModel extends BaseViewModel<ActivityCategoryLocalB
});
}
public MutableLiveData<VideoUpdate> mVideoUpdateMutableLiveData = new MutableLiveData<>();
// public void getVideoUpdate(String uuid) {
// String currentTime = TimeUtils.transferMillisecondToDate(System.currentTimeMillis());
// String time = mMMKV.decodeString(uuid, currentTime);
// Log.e(TAG, "getVideoUpdate: " + time);
// NetInterfaceManager.getInstance().getVideoUpdateObservable(time)
// .compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY))
// .subscribe(new Observer<BaseResponse<VideoUpdate>>() {
// @Override
// public void onSubscribe(@NonNull Disposable d) {
// Log.e("getVideoUpdate", "onSubscribe: ");
// }
//
// @Override
// public void onNext(@NonNull BaseResponse<VideoUpdate> baseResponse) {
// Log.e("getVideoUpdate", "onNext: " + baseResponse);
// mMMKV.encode(uuid, currentTime);
// if (baseResponse.code == 200) {
// VideoUpdate videoUpdate = baseResponse.data;
// mVideoUpdateMutableLiveData.setValue(videoUpdate);
// }
// }
//
// @Override
// public void onError(@NonNull Throwable e) {
// Log.e("getVideoUpdate", "onError: " + e.getMessage());
// }
//
// @Override
// public void onComplete() {
// Log.e("getVideoUpdate", "onComplete: ");
// }
// });
// }
}

View File

@@ -13,10 +13,11 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.hainaos.vc.R;
import com.hainaos.vc.adapter.CategoryVideoAdapter;
import com.hainaos.vc.base.mvvm.BaseMvvmActivity;
import com.hainaos.vc.bean.BaseResponse;
import com.hainaos.vc.bean.CategoryInfo;
import com.hainaos.vc.bean.CategoryVideoInfo;
import com.hainaos.vc.bean.VideoListData;
import com.hainaos.vc.bean.PasswdInfo;
import com.hainaos.vc.bean.VideoUpdate;
import com.hainaos.vc.config.CommonConfig;
import com.hainaos.vc.databinding.ActivityCategoryVideoBinding;
import com.hainaos.vc.fragment.passwd.PasswdDialogFragment;
import com.hainaos.vc.utils.FileUtils;
@@ -24,6 +25,7 @@ import com.hainaos.vc.utils.ScreenUtils;
import com.hainaos.vc.view.CustomDialog;
import com.hainaos.vc.view.EquallyDividedItemDecoration;
import com.hjq.toast.Toaster;
import com.tencent.mmkv.MMKV;
import java.io.File;
import java.util.List;
@@ -31,6 +33,9 @@ import java.util.List;
public class CategoryVideoActivity extends BaseMvvmActivity<CategoryVideoViewModel, ActivityCategoryVideoBinding> {
private static final String TAG = "CategoryActivity";
private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
private CategoryInfo mCategoryInfo;
private String mPasswd = "";
private CategoryVideoAdapter mCategoryVideoAdapter;
@@ -69,7 +74,7 @@ public class CategoryVideoActivity extends BaseMvvmActivity<CategoryVideoViewMod
@Override
public void onRefresh() {
mViewDataBinding.swipeRefreshLayout.setRefreshing(true);
mViewModel.getVideoList(mCategoryInfo.getUuid(), mPasswd);
mViewModel.getVideoList(mCategoryInfo.getFolder(), mCategoryInfo.getUuid(), mPasswd);
}
});
mViewDataBinding.rvVideo.addOnScrollListener(new RecyclerView.OnScrollListener() {
@@ -106,14 +111,14 @@ public class CategoryVideoActivity extends BaseMvvmActivity<CategoryVideoViewMod
// });
// mViewModel.getViedoList(mCategoryInfo.getUuid());
mViewModel.mCategoryVideoInfoListData.observe(this, new Observer<BaseResponse<VideoListData>>() {
mViewModel.mCategoryVideoInfoListData.observe(this, new Observer<List<CategoryVideoInfo>>() {
@Override
public void onChanged(BaseResponse<VideoListData> baseResponse) {
if (baseResponse.code == 200) {
VideoListData videoListData = baseResponse.data;
List<CategoryVideoInfo> categoryVideoInfos = videoListData.getData();
public void onChanged(List<CategoryVideoInfo> categoryVideoInfos) {
mCategoryVideoAdapter.setData(categoryVideoInfos);
if (categoryVideoInfos == null || categoryVideoInfos.isEmpty()) {
mViewDataBinding.clNodata.setVisibility(View.VISIBLE);
mViewDataBinding.rvVideo.setVisibility(View.GONE);
@@ -122,25 +127,40 @@ public class CategoryVideoActivity extends BaseMvvmActivity<CategoryVideoViewMod
mViewDataBinding.rvVideo.setVisibility(View.VISIBLE);
mViewDataBinding.tvTotal.setText(String.format(getString(R.string.video_total), categoryVideoInfos.size()));
}
} else {
Toaster.show(baseResponse.msg);
}
});
mViewModel.mPasswdCorrect.observe(this, new Observer<PasswdInfo>() {
@Override
public void onChanged(PasswdInfo passwdInfo) {
mViewDataBinding.swipeRefreshLayout.setRefreshing(false);
if (!passwdInfo.isCorrect()) {
Toaster.show(passwdInfo.getMsg());
PasswdDialogFragment passwdDialogFragment = new PasswdDialogFragment(mCategoryInfo);
passwdDialogFragment.setConfimCallback(new PasswdDialogFragment.ConfimCallback() {
@Override
public void onConfig(String passwd) {
mPasswd = passwd;
mViewModel.getVideoList(mCategoryInfo.getUuid(), mPasswd);
mViewModel.getVideoList(mCategoryInfo.getFolder(), mCategoryInfo.getUuid(), mPasswd);
}
});
passwdDialogFragment.show(getSupportFragmentManager(), "PasswdDialogFragment");
}
mViewDataBinding.swipeRefreshLayout.setRefreshing(false);
}
});
mViewModel.getVideoList(mCategoryInfo.getUuid(), mPasswd);
mViewModel.mVideoUpdateMutableLiveData.observe(this, new Observer<VideoUpdate>() {
@Override
public void onChanged(VideoUpdate videoUpdate) {
if (videoUpdate.getHas_new() == 1) {
mViewDataBinding.ivUpdate.setVisibility(View.VISIBLE);
} else {
mViewDataBinding.ivUpdate.setVisibility(View.GONE);
}
}
});
mViewModel.getVideoList(mCategoryInfo.getFolder(), mCategoryInfo.getUuid(), mPasswd);
}

View File

@@ -6,28 +6,42 @@ import androidx.lifecycle.MutableLiveData;
import com.hainaos.vc.base.mvvm.BaseViewModel;
import com.hainaos.vc.bean.BaseResponse;
import com.hainaos.vc.bean.LocalVideoInfo;
import com.hainaos.vc.bean.CategoryVideoInfo;
import com.hainaos.vc.bean.PasswdInfo;
import com.hainaos.vc.bean.VideoListData;
import com.hainaos.vc.bean.VideoUpdate;
import com.hainaos.vc.config.CommonConfig;
import com.hainaos.vc.databinding.ActivityCategoryVideoBinding;
import com.hainaos.vc.network.NetInterfaceManager;
import com.hainaos.vc.utils.FileUtils;
import com.tencent.mmkv.MMKV;
import com.trello.rxlifecycle4.RxLifecycle;
import com.trello.rxlifecycle4.android.ActivityEvent;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.annotations.NonNull;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Observer;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.functions.BiFunction;
import io.reactivex.rxjava3.schedulers.Schedulers;
public class CategoryVideoViewModel extends BaseViewModel<ActivityCategoryVideoBinding, ActivityEvent> {
private static final String TAG = "CategoryViewModel";
private static final String TAG = "CategoryVideoViewModel";
private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
@Override
public ActivityCategoryVideoBinding getVDBinding() {
@@ -40,7 +54,7 @@ public class CategoryVideoViewModel extends BaseViewModel<ActivityCategoryVideoB
}
@Deprecated
public MutableLiveData<ArrayList<LocalVideoInfo>> mLocalVideoInfosData = new MutableLiveData<>();
public MutableLiveData<ArrayList<CategoryVideoInfo>> mLocalVideoInfosData = new MutableLiveData<>();
@Deprecated
public void getViedoList(String dir) {
@@ -50,12 +64,12 @@ public class CategoryVideoViewModel extends BaseViewModel<ActivityCategoryVideoB
Log.e(TAG, "getViedoList: " + Arrays.toString(strings));
if (strings != null) {
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
public LocalVideoInfo apply(String s) {
LocalVideoInfo localVideoInfo = new LocalVideoInfo();
localVideoInfo.setFile_name(FileUtils.getFileNameWithoutExtension(s));
localVideoInfo.setLocalPath(new File(file.getAbsolutePath() + File.separator + s).getAbsolutePath());
public CategoryVideoInfo apply(String s) {
CategoryVideoInfo localVideoInfo = new CategoryVideoInfo();
localVideoInfo.setName(FileUtils.getFileNameWithoutExtension(s));
localVideoInfo.setFile_url(new File(file.getAbsolutePath() + File.separator + s).getAbsolutePath());
return localVideoInfo;
}
}).collect(Collectors.toCollection(ArrayList::new));
@@ -68,22 +82,96 @@ public class CategoryVideoViewModel extends BaseViewModel<ActivityCategoryVideoB
}
}
public MutableLiveData<BaseResponse<VideoListData>> mCategoryVideoInfoListData = new MutableLiveData<>();
private Observable<List<CategoryVideoInfo>> getLocalCategoryVideoInfo(String dirName) {
return Observable.fromCallable(new Callable<List<CategoryVideoInfo>>() {
@Override
public List<CategoryVideoInfo> call() throws Exception {
ArrayList<CategoryVideoInfo> localVideoInfos = new ArrayList<>();
File file = new File(FileUtils.getHainaVideoPath(getCtx()) + dirName);
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));
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));
}
}
return localVideoInfos;
}
});
}
public void getVideoList(String uuid, String password) {
NetInterfaceManager.getInstance().getVideoListObservable(uuid, password)
public MutableLiveData<List<CategoryVideoInfo>> mCategoryVideoInfoListData = new MutableLiveData<>();
public MutableLiveData<PasswdInfo> mPasswdCorrect = new MutableLiveData<>();
public void getVideoList(String dir, String uuid, String password) {
Observable.zip(getLocalCategoryVideoInfo(dir), NetInterfaceManager.getInstance().getVideoListObservable(uuid, password),
new BiFunction<List<CategoryVideoInfo>, BaseResponse<VideoListData>, List<CategoryVideoInfo>>() {
@Override
public List<CategoryVideoInfo> apply(List<CategoryVideoInfo> localCategoryVideoInfos, BaseResponse<VideoListData> baseResponse) throws Throwable {
List<CategoryVideoInfo> allCategoryVideoInfos = new ArrayList<>();
PasswdInfo passwdInfo = new PasswdInfo();
if (baseResponse.code == 200) {
VideoListData videoListData = baseResponse.data;
passwdInfo.setCorrect(true);
List<CategoryVideoInfo> onlineCategoryVideoInfos = videoListData.getData();
allCategoryVideoInfos.addAll(onlineCategoryVideoInfos);
// 1. 提取线上所有元素的name存入Set去重+O(1)查找,效率最高)
Set<String> onlineNames = new HashSet<>();
for (CategoryVideoInfo onlineInfo : onlineCategoryVideoInfos) {
if (onlineInfo != null && onlineInfo.getName() != null) {
onlineNames.add(onlineInfo.getName());
}
if (onlineInfo != null && onlineInfo.getFile_url() != null) {
onlineNames.add(FileUtils.getFileNameWithoutExtension(onlineInfo.getFile_url()));
}
}
// 2. 遍历本地列表删除name存在于线上Set中的元素
// 注意使用迭代器删除避免普通for循环删除报ConcurrentModificationException
localCategoryVideoInfos.removeIf(info ->
info != null && info.getName() != null && onlineNames.contains(info.getName())
);
allCategoryVideoInfos.addAll(localCategoryVideoInfos);
} else {
passwdInfo.setCorrect(false);
passwdInfo.setMsg(baseResponse.msg);
}
mPasswdCorrect.setValue(passwdInfo);
return allCategoryVideoInfos;
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY))
.subscribe(new Observer<BaseResponse<VideoListData>>() {
.subscribe(new Observer<List<CategoryVideoInfo>>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
Log.e("getVideoList", "onSubscribe: ");
}
@Override
public void onNext(@NonNull BaseResponse<VideoListData> listBaseResponse) {
Log.e("getVideoList", "onNext: ");
mCategoryVideoInfoListData.setValue(listBaseResponse);
public void onNext(@NonNull List<CategoryVideoInfo> categoryVideoInfos) {
Log.e("getVideoList", "onNext: " + categoryVideoInfos.size());
mCategoryVideoInfoListData.setValue(categoryVideoInfos);
}
@Override
@@ -96,5 +184,76 @@ public class CategoryVideoViewModel extends BaseViewModel<ActivityCategoryVideoB
Log.e("getVideoList", "onComplete: ");
}
});
// NetInterfaceManager.getInstance().getVideoListObservable(uuid, password)
// .compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY))
// .subscribe(new Observer<BaseResponse<VideoListData>>() {
// @Override
// public void onSubscribe(@NonNull Disposable d) {
// Log.e("getVideoList", "onSubscribe: ");
// }
//
// @Override
// public void onNext(@NonNull BaseResponse<VideoListData> baseResponse) {
// Log.e("getVideoList", "onNext: ");
// PasswdInfo passwdInfo = new PasswdInfo();
// if (baseResponse.code == 200) {
// VideoListData videoListData = baseResponse.data;
// List<CategoryVideoInfo> categoryVideoInfos = videoListData.getData();
// mCategoryVideoInfoListData.setValue(categoryVideoInfos);
// passwdInfo.setCorrect(true);
// } else {
// passwdInfo.setCorrect(false);
// passwdInfo.setMsg(baseResponse.msg);
// }
// mPasswdCorrect.setValue(passwdInfo);
// }
//
// @Override
// public void onError(@NonNull Throwable e) {
// Log.e("getVideoList", "onError: " + e.getMessage());
// }
//
// @Override
// public void onComplete() {
// Log.e("getVideoList", "onComplete: ");
// }
// });
}
public MutableLiveData<VideoUpdate> mVideoUpdateMutableLiveData = new MutableLiveData<>();
// public void getVideoUpdate(String uuid) {
// String currentTime = TimeUtils.transferMillisecondToDate(System.currentTimeMillis());
// String time = mMMKV.decodeString(uuid, currentTime);
// Log.e(TAG, "getVideoUpdate: " + time);
// NetInterfaceManager.getInstance().getVideoUpdateObservable(time)
// .compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY))
// .subscribe(new Observer<BaseResponse<VideoUpdate>>() {
// @Override
// public void onSubscribe(@NonNull Disposable d) {
// Log.e("getVideoUpdate", "onSubscribe: ");
// }
//
// @Override
// public void onNext(@NonNull BaseResponse<VideoUpdate> baseResponse) {
// Log.e("getVideoUpdate", "onNext: " + baseResponse);
// mMMKV.encode(uuid, currentTime);
// if (baseResponse.code == 200) {
// VideoUpdate videoUpdate = baseResponse.data;
// mVideoUpdateMutableLiveData.setValue(videoUpdate);
// }
// }
//
// @Override
// public void onError(@NonNull Throwable e) {
// Log.e("getVideoUpdate", "onError: " + e.getMessage());
// }
//
// @Override
// public void onComplete() {
// Log.e("getVideoUpdate", "onComplete: ");
// }
// });
// }
}

View File

@@ -10,8 +10,11 @@ import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Toast;
import androidx.core.app.ActivityCompat;
@@ -21,15 +24,18 @@ import androidx.lifecycle.Observer;
import com.hainaos.vc.BuildConfig;
import com.hainaos.vc.R;
import com.hainaos.vc.activity.category.list.CategoryListActivity;
import com.hainaos.vc.activity.login.LoginActivity;
import com.hainaos.vc.base.BaseFragmentPagerAdapter;
import com.hainaos.vc.base.mvvm.BaseMvvmActivity;
import com.hainaos.vc.bean.VideoUpdate;
import com.hainaos.vc.bean.uiuios.AppUpdateInfo;
import com.hainaos.vc.config.CommonConfig;
import com.hainaos.vc.config.Permissions;
import com.hainaos.vc.databinding.ActivityMainBinding;
import com.hainaos.vc.dialog.PermissionsDialog;
import com.hainaos.vc.dialog.PrivacyPolicyDialog;
import com.hainaos.vc.dialog.UpdateDialog;
import com.hainaos.vc.fragment.app.AppFragment;
import com.hainaos.vc.fragment.category.CategoryFragment;
import com.hainaos.vc.utils.ApkUtils;
@@ -40,6 +46,8 @@ import com.hainaos.vc.utils.VideoUtils;
import com.hjq.permissions.OnPermissionCallback;
import com.hjq.permissions.XXPermissions;
import com.tencent.mmkv.MMKV;
import com.trello.rxlifecycle4.RxLifecycle;
import com.trello.rxlifecycle4.android.ActivityEvent;
import java.io.File;
import java.io.FileInputStream;
@@ -47,8 +55,12 @@ import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.annotations.NonNull;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.subjects.PublishSubject;
public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBinding> {
private static final String TAG = "MainActivity";
@@ -61,6 +73,10 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
private static final int REQUEST_PERMISSION_CODE = 200;
private PublishSubject<Object> clickSubject = PublishSubject.create();
private CompositeDisposable disposables = new CompositeDisposable();
@Override
public boolean setNightMode() {
return true;
@@ -96,6 +112,16 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
@Override
public void initData() {
// 设置防抖500毫秒内只响应最后一次点击
disposables.add(clickSubject
.throttleFirst(10, TimeUnit.MINUTES) // 防抖时间窗口
.observeOn(AndroidSchedulers.mainThread())
.compose(RxLifecycle.bindUntilEvent(lifecycle(), ActivityEvent.DESTROY))
.subscribe(o -> {
// 这里执行需要防抖的操作,例如网络请求、计算等
performAction();
}));
mViewModel.mAppUpdateInfoUiUiOSData.observe(this, new Observer<AppUpdateInfo>() {
@Override
public void onChanged(AppUpdateInfo appUpdateInfo) {
@@ -115,8 +141,26 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
}
}
});
mViewModel.mVideoUpdateMutableLiveData.observe(this, new Observer<VideoUpdate>() {
@Override
public void onChanged(VideoUpdate videoUpdate) {
if (videoUpdate.getHas_new() == 1) {
showUpdateDialog();
} else {
}
}
});
clickSubject.onNext(new Object());
}
private void performAction() {
Log.e(TAG, "performAction: ");
mViewModel.checkUpdateUiUiOS(BuildConfig.APPLICATION_ID);
mViewModel.checkUpdate();
mViewModel.getVideoUpdate();
}
private void initDatas() {
@@ -126,11 +170,51 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
}
private UpdateDialog mUpdateDialog;
private void showUpdateDialog() {
mUpdateDialog = new UpdateDialog(this);
mUpdateDialog.setMessage("更新了新的视频,请去下载")
.setTitle("公告")
.setPositive("点击查看")
.setSingle(true)
.setOnClickBottomListener(new UpdateDialog.OnClickBottomListener() {
@Override
public void onPositiveClick() {
startActivity(new Intent(MainActivity.this, CategoryListActivity.class));
mUpdateDialog.dismiss();
}
@Override
public void onNegtiveClick() {
mUpdateDialog.dismiss();
}
});
Window window = mUpdateDialog.getWindow();
if (window != null) {
window.setGravity(Gravity.CENTER);
window.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
}
mUpdateDialog.setCancelable(false);
if (!mUpdateDialog.isShowing()) {
mUpdateDialog.show();
}
}
@Override
protected void onResume() {
super.onResume();
Log.e(TAG, "onResume: ");
checkEULA();
clickSubject.onNext(new Object());
}
@Override
protected void onDestroy() {
super.onDestroy();
disposables.clear(); // 避免内存泄漏
}
@Override

View File

@@ -13,10 +13,14 @@ import com.hainaos.vc.base.mvvm.BaseViewModel;
import com.hainaos.vc.bean.AppInfo;
import com.hainaos.vc.bean.BaseResponse;
import com.hainaos.vc.bean.HomeAppInfo;
import com.hainaos.vc.bean.VideoUpdate;
import com.hainaos.vc.bean.uiuios.AppUpdateInfo;
import com.hainaos.vc.config.CommonConfig;
import com.hainaos.vc.databinding.ActivityMainBinding;
import com.hainaos.vc.network.NetInterfaceManager;
import com.hainaos.vc.utils.TimeUtils;
import com.hjq.toast.Toaster;
import com.tencent.mmkv.MMKV;
import com.trello.rxlifecycle4.RxLifecycle;
import com.trello.rxlifecycle4.android.ActivityEvent;
@@ -40,6 +44,8 @@ import io.reactivex.rxjava3.schedulers.Schedulers;
public class MainViewModel extends BaseViewModel<ActivityMainBinding, ActivityEvent> {
private static final String TAG = "MainViewModel";
private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
@Override
public ActivityMainBinding getVDBinding() {
return binding;
@@ -84,12 +90,12 @@ public class MainViewModel extends BaseViewModel<ActivityMainBinding, ActivityEv
.subscribe(new Observer<BaseResponse<AppUpdateInfo>>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
Log.e("checkUpdate", "onSubscribe: ");
Log.e("checkUpdateUiUiOS", "onSubscribe: ");
}
@Override
public void onNext(@NonNull BaseResponse<AppUpdateInfo> appUpdateInfoBaseResponse) {
Log.e("checkUpdate", "onNext: " + appUpdateInfoBaseResponse);
Log.e("checkUpdateUiUiOS", "onNext: " + appUpdateInfoBaseResponse);
if (appUpdateInfoBaseResponse.code == 200) {
AppUpdateInfo appUpdateInfo = appUpdateInfoBaseResponse.data;
mAppUpdateInfoUiUiOSData.setValue(appUpdateInfo);
@@ -100,13 +106,13 @@ public class MainViewModel extends BaseViewModel<ActivityMainBinding, ActivityEv
@Override
public void onError(@NonNull Throwable e) {
Log.e("checkUpdate", "onError: ");
Log.e("checkUpdateUiUiOS", "onError: ");
Toaster.show("网络连接失败");
}
@Override
public void onComplete() {
Log.e("checkUpdate", "onComplete: ");
Log.e("checkUpdateUiUiOS", "onComplete: ");
}
});
}
@@ -206,4 +212,40 @@ public class MainViewModel extends BaseViewModel<ActivityMainBinding, ActivityEv
});
}
public MutableLiveData<VideoUpdate> mVideoUpdateMutableLiveData = new MutableLiveData<>();
public void getVideoUpdate() {
String currentTime = TimeUtils.transferMillisecondToDate(System.currentTimeMillis());
String time = mMMKV.decodeString(CommonConfig.MAIN_VIDEOS_CHECK_UPDATE_TIME, currentTime);
Log.e(TAG, "getVideoUpdate: " + time);
NetInterfaceManager.getInstance().getVideoUpdateObservable(time)
.compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY))
.subscribe(new Observer<BaseResponse<VideoUpdate>>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
Log.e("getVideoUpdate", "onSubscribe: ");
}
@Override
public void onNext(@NonNull BaseResponse<VideoUpdate> baseResponse) {
Log.e("getVideoUpdate", "onNext: " + baseResponse);
mMMKV.encode(CommonConfig.MAIN_VIDEOS_CHECK_UPDATE_TIME, currentTime);
if (baseResponse.code == 200) {
VideoUpdate videoUpdate = baseResponse.data;
mVideoUpdateMutableLiveData.setValue(videoUpdate);
}
}
@Override
public void onError(@NonNull Throwable e) {
Log.e("getVideoUpdate", "onError: " + e.getMessage());
}
@Override
public void onComplete() {
Log.e("getVideoUpdate", "onComplete: ");
}
});
}
}

View File

@@ -3,7 +3,9 @@ package com.hainaos.vc.activity.player;
import android.content.Intent;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.MediaItem;
@@ -11,12 +13,13 @@ import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.FileDataSource;
import com.google.android.exoplayer2.util.Util;
import com.hainaos.vc.R;
import com.hainaos.vc.base.mvvm.BaseMvvmActivity;
import com.hainaos.vc.databinding.ActivityDecryptionPlayerBinding;
import com.hainaos.vc.utils.JgyUtils;
import com.hainaos.vc.video.AesDataSource;
import com.hainaos.vc.video.AesDataSource2;
import com.hjq.toast.Toaster;
public class DecryptionPlayerActivity extends BaseMvvmActivity<DecryptionPlayerViewModel, ActivityDecryptionPlayerBinding> {
@@ -45,6 +48,8 @@ public class DecryptionPlayerActivity extends BaseMvvmActivity<DecryptionPlayerV
@Override
protected void initView() {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
mExoPlayer = new SimpleExoPlayer.Builder(this).build();
mViewDataBinding.playerView.setPlayer(mExoPlayer);
@@ -71,10 +76,11 @@ public class DecryptionPlayerActivity extends BaseMvvmActivity<DecryptionPlayerV
// 创建自定义 Factory
DataSource.Factory dataSourceFactory = () -> {
try {
return new AesDataSource(key, iv);
// DataSource upstream = new FileDataSource();
// return new AesDataSource2(upstream, key, iv);
// return new AesDataSource(key, iv);
DataSource upstream = new FileDataSource();
return new AesDataSource2(upstream, key, iv);
} catch (Exception e) {
Log.e(TAG, "initData: " + e.getMessage());
throw new RuntimeException(e);
}
};
@@ -129,6 +135,13 @@ public class DecryptionPlayerActivity extends BaseMvvmActivity<DecryptionPlayerV
}
}
@Override
protected void onDestroy() {
super.onDestroy();
// 可选:清除标志以恢复正常息屏行为
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
private void initializePlayer() {
// if (mExoPlayer == null) {
// // 1. 创建 ExoPlayer 实例

View File

@@ -64,6 +64,8 @@ public class TikTokActivity extends BaseMvvmActivity<TikTokViewModel, ActivityTi
@Override
protected void initView() {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
Configuration config = getResources().getConfiguration();
oldOrientation = config.orientation;
Log.e(TAG, "orientation first:" + oldOrientation);
@@ -232,5 +234,6 @@ public class TikTokActivity extends BaseMvvmActivity<TikTokViewModel, ActivityTi
mTikTokRecyclerViewAdapter.unRegister();
mTikTokRecyclerViewAdapter = null;
}
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
}

View File

@@ -100,7 +100,7 @@ public class UserActivity extends BaseMvvmActivity<UserViewModel, ActivityUserBi
protected void onResume() {
super.onResume();
mViewDataBinding.tvSn.setText(LenovoCsdkUtil.getInstance().getSerial());
mViewDataBinding.tvStorge.setText("" + Utils.getRemnantSize(this) + "/" + Utils.getDataTotalSize(this));
mViewDataBinding.tvStorge.setText("" + Utils.getRemnantSize(this) + "/" + Utils.getDataTotalSize(this));
}
private ActivityResultLauncher<Intent> mLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback<ActivityResult>() {

View File

@@ -21,6 +21,7 @@ import com.hainaos.vc.fragment.passwd.PasswdDialogFragment;
import com.hainaos.vc.utils.GlideLoadUtils;
import java.util.List;
import java.util.Map;
import me.jessyan.autosize.AutoSizeCompat;
@@ -35,6 +36,13 @@ public class CategoryAdapter extends RecyclerView.Adapter<CategoryAdapter.Holder
notifyDataSetChanged();
}
private Map<String, Integer> mCategoryUpdateInfoMap;
public void setCategoryUpdateInfoMap(Map<String, Integer> categoryUpdateInfoMap) {
mCategoryUpdateInfoMap = categoryUpdateInfoMap;
notifyDataSetChanged();
}
@NonNull
@Override
public Holder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
@@ -61,6 +69,11 @@ public class CategoryAdapter extends RecyclerView.Adapter<CategoryAdapter.Holder
}
String uuid = categoryInfo.getUuid();
if (mCategoryUpdateInfoMap != null && mCategoryUpdateInfoMap.get(uuid) != null && mCategoryUpdateInfoMap.get(uuid) != 0) {
holder.iv_update.setVisibility(View.VISIBLE);
} else {
holder.iv_update.setVisibility(View.GONE);
}
String icon = categoryInfo.getIcon();
GlideLoadUtils.getInstance().glideLoad(mContext, icon, holder.iv_icon, R.drawable.icon_category);
@@ -91,7 +104,7 @@ public class CategoryAdapter extends RecyclerView.Adapter<CategoryAdapter.Holder
ConstraintLayout root;
TextView tv_app_name;
ImageView iv_icon, iv_lock_icon;
ImageView iv_icon, iv_lock_icon, iv_update;
public Holder(@NonNull View itemView) {
super(itemView);
@@ -100,6 +113,7 @@ public class CategoryAdapter extends RecyclerView.Adapter<CategoryAdapter.Holder
tv_app_name = itemView.findViewById(R.id.tv_app_name);
iv_icon = itemView.findViewById(R.id.iv_icon);
iv_lock_icon = itemView.findViewById(R.id.iv_lock_icon);
iv_update = itemView.findViewById(R.id.iv_update);
}
}

View File

@@ -24,12 +24,14 @@ import com.arialyy.annotations.Download;
import com.arialyy.aria.core.Aria;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.task.DownloadTask;
import com.bumptech.glide.Glide;
import com.hainaos.vc.R;
import com.hainaos.vc.activity.player.DecryptionPlayerActivity;
import com.hainaos.vc.activity.preview.VideoPreviewActivity;
import com.hainaos.vc.bean.CategoryVideoInfo;
import com.hainaos.vc.config.Permissions;
import com.hainaos.vc.dialog.PermissionsDialog;
import com.hainaos.vc.utils.FFmpegUtils;
import com.hainaos.vc.utils.FileUtils;
import com.hainaos.vc.utils.GlideLoadUtils;
import com.hainaos.vc.utils.TimeUtils;
@@ -43,6 +45,8 @@ import com.shehuan.niv.NiceImageView;
import java.io.File;
import java.util.List;
import io.reactivex.rxjava3.core.Observer;
import io.reactivex.rxjava3.disposables.Disposable;
import me.jessyan.autosize.AutoSizeCompat;
public class CategoryVideoAdapter extends RecyclerView.Adapter<CategoryVideoAdapter.VideoHolder> {
@@ -82,30 +86,176 @@ public class CategoryVideoAdapter extends RecyclerView.Adapter<CategoryVideoAdap
if (!TextUtils.isEmpty(name)) {
holder.tv_title.setText(name);
}
String url = categoryVideoInfo.getFile_url();
if (FileUtils.isLocalFileUriType(url)) {
File file = new File(url);
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.setVisibility(View.GONE);
holder.tv_delete.setVisibility(View.VISIBLE);
holder.tv_delete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showDeleteLocalDialog(file.getAbsolutePath(), position);
}
});
// holder.tv_download.setText("已下载");
// holder.tv_download.setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View v) {
//// if (file.exists()) {
// showDeleteLocalDialog(file.getAbsolutePath(), position);
//// }
// }
// });
}
} else {
String cover = categoryVideoInfo.getCover();
String md5 = categoryVideoInfo.getMd5();
long sizeBytes = categoryVideoInfo.getFile_size();
String fileSize = Formatter.formatFileSize(mContext, sizeBytes);
holder.tv_size.setText("视频大小: " + fileSize);
String fileName = FileUtils.getFileNamefromURL(url);
String fileExName = FileUtils.getFileType(url);
File file = new File(FileUtils.getHainaVideoPath(mContext) + mDirName + File.separator + fileName);
File downloadFile = new File(FileUtils.getHainaVideoPath(mContext) + mDirName + File.separator + fileName);
File localVideoFile = new File(FileUtils.getHainaVideoPath(mContext) + mDirName + File.separator + name + fileExName);
File file;
if (localVideoFile.exists()) {
file = localVideoFile;
} else {
file = downloadFile;
}
DownloadEntity entity = Aria.download(mContext).getFirstDownloadEntity(url);
if (entity == null) {
if (file.exists()) {
holder.tv_delete.setVisibility(View.VISIBLE);
holder.tv_download.setText("已下载");
} else {
holder.tv_delete.setVisibility(View.GONE);
holder.tv_download.setText("下载");
holder.tv_download.setBackground(mContext.getDrawable(R.drawable.bg_download_button));
}
} else {
int state = entity.getState();
switch (state) {
case 1:
if (file.exists()) {
holder.tv_download.setText("删除");
holder.tv_download.setBackground(mContext.getDrawable(R.drawable.bg_delete_button));
holder.tv_delete.setVisibility(View.VISIBLE);
holder.tv_download.setText("已下载");
} else {
holder.tv_delete.setVisibility(View.GONE);
holder.tv_download.setText("下载");
}
break;
case 2:
holder.tv_delete.setVisibility(View.GONE);
holder.tv_download.setText("停止");
holder.tv_download.setBackground(mContext.getDrawable(R.drawable.bg_download_button));
break;
case 3:
holder.tv_delete.setVisibility(View.GONE);
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_delete.setVisibility(View.GONE);
holder.tv_download.setText("下载");
holder.tv_download.setBackground(mContext.getDrawable(R.drawable.bg_download_button));
break;
case 4:
holder.tv_delete.setVisibility(View.GONE);
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_delete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (localVideoFile.exists()) {
showDialog(localVideoFile.getAbsolutePath(), position);
} else if (downloadFile.exists()) {
showDialog(downloadFile.getAbsolutePath(), position);
}
}
});
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, categoryVideoInfo);
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, categoryVideoInfo);
FileUtils.ariaDownload(mContext, mDirName, cover);
// FileUtils.ariaDownloadCover(mContext, mDirName, cover, md5);
} else {
showPermissionsDialog(mContext);
}
}
break;
case -1:
@@ -115,39 +265,13 @@ public class CategoryVideoAdapter extends RecyclerView.Adapter<CategoryVideoAdap
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;
}
}
String cover = categoryVideoInfo.getCover();
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 (file.exists()) {
showDialog(file.getAbsolutePath(), position);
} else {
if (XXPermissions.isGranted(mContext, Permissions.STORAGE_PERMISSIONS)) {
FileUtils.ariaDownload(mContext, mDirName, url, md5);
// FileUtils.ariaDownloadCover(mContext, mDirName, cover, md5);
} else {
showPermissionsDialog(mContext);
}
}
}
});
holder.video_image.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -169,6 +293,8 @@ public class CategoryVideoAdapter extends RecyclerView.Adapter<CategoryVideoAdap
});
}
}
private PermissionsDialog mPermissionsDialog;
private void showPermissionsDialog(Context context) {
@@ -244,6 +370,34 @@ public class CategoryVideoAdapter extends RecyclerView.Adapter<CategoryVideoAdap
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) {
if (null != mLocalVideoInfos.get(position)) {
mLocalVideoInfos.remove(position);
@@ -268,7 +422,7 @@ public class CategoryVideoAdapter extends RecyclerView.Adapter<CategoryVideoAdap
static class VideoHolder extends RecyclerView.ViewHolder {
NiceImageView video_image;
TextView tv_title, tv_duration, tv_download, tv_size;
TextView tv_title, tv_duration, tv_delete, tv_download, tv_size;
ConstraintLayout root;
public VideoHolder(@NonNull View itemView) {
@@ -277,6 +431,7 @@ public class CategoryVideoAdapter extends RecyclerView.Adapter<CategoryVideoAdap
video_image = itemView.findViewById(R.id.video_image);
tv_title = itemView.findViewById(R.id.tv_title);
tv_duration = itemView.findViewById(R.id.tv_duration);
tv_delete = itemView.findViewById(R.id.tv_delete);
tv_download = itemView.findViewById(R.id.tv_download);
tv_size = itemView.findViewById(R.id.tv_size);
}

View File

@@ -6,6 +6,7 @@ import android.graphics.Bitmap;
import android.media.MediaMetadataRetriever;
import android.os.AsyncTask;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -16,6 +17,9 @@ import androidx.annotation.NonNull;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.recyclerview.widget.RecyclerView;
import com.arialyy.annotations.Download;
import com.arialyy.aria.core.Aria;
import com.arialyy.aria.core.task.DownloadTask;
import com.bumptech.glide.Glide;
import com.google.gson.Gson;
import com.google.gson.JsonParser;
@@ -23,19 +27,28 @@ import com.hainaos.vc.R;
import com.hainaos.vc.activity.player.DecryptionPlayerActivity;
import com.hainaos.vc.activity.tiktok.TikTokActivity;
import com.hainaos.vc.bean.LocalVideoInfo;
import com.hainaos.vc.config.CommonConfig;
import com.hainaos.vc.utils.FileUtils;
import com.hainaos.vc.utils.TimeUtils;
import com.shehuan.niv.NiceImageView;
import com.tencent.mmkv.MMKV;
import java.io.File;
import java.util.ArrayList;
import java.util.Map;
public class VideoAdapter extends RecyclerView.Adapter<VideoAdapter.VideoHolder> {
private static final String TAG = "VideoAdapter";
private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
private Activity mContext;
private ArrayList<LocalVideoInfo> mLocalVideoInfos;
private onItemLongClickListener onItemLongClickListener;
private Map<String, String> mCoverMap;
public VideoAdapter() {
}
@@ -49,6 +62,11 @@ public class VideoAdapter extends RecyclerView.Adapter<VideoAdapter.VideoHolder>
this.mLocalVideoInfos = path;
}
public void setCoverMap(Map<String, String> coverMap) {
mCoverMap = coverMap;
notifyDataSetChanged();
}
private static class VideoResult {
long time;
Bitmap frame;
@@ -72,6 +90,7 @@ public class VideoAdapter extends RecyclerView.Adapter<VideoAdapter.VideoHolder>
@NonNull
@Override
public VideoHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
Aria.download(this).register();
VideoHolder holder = new VideoHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_video_file, parent, false));
return holder;
}
@@ -81,16 +100,50 @@ public class VideoAdapter extends RecyclerView.Adapter<VideoAdapter.VideoHolder>
LocalVideoInfo localVideoInfo = mLocalVideoInfos.get(position);
final String localPath = localVideoInfo.getLocalPath();
Log.e(TAG, "onBindViewHolder: " + localPath);
holder.title.setText(FileUtils.getFileName(localPath));
String videoFileName = FileUtils.getFileName(localPath);
String videoFileNameWithoutEx = FileUtils.getFileNameWithoutExtension(localPath);
holder.title.setText(videoFileName);
File file = new File(localPath);
if (file.exists()) {
holder.iv_status.setVisibility(View.GONE);
Glide.with(mContext).load(file).error(R.mipmap.ic_launcher).into(holder.video_image);
if (file.getName().endsWith(".hnv")) {
String dirPaht = file.getParent();
Log.e(TAG, "onBindViewHolder: dirPaht = " + dirPaht);
File localCoverFile = new File(dirPaht + File.separator + videoFileNameWithoutEx + ".png");
if (mCoverMap != null) {
String coverUrl = mCoverMap.get(videoFileName);
Log.e(TAG, "onBindViewHolder: coverUrl = " + coverUrl);
if (!TextUtils.isEmpty(coverUrl)) {
String coverName = FileUtils.getFileNamefromURL(coverUrl);
Log.e(TAG, "onBindViewHolder: coverName = " + coverName);
File coverFile = new File(dirPaht + File.separator + coverName);
Log.e(TAG, "onBindViewHolder: coverFile = " + coverFile.getAbsolutePath());
if (coverFile.exists()) {
Glide.with(mContext).load(coverFile).centerCrop().error(R.drawable.picture_split).into(holder.video_image);
} else {
FileUtils.ariaDownloadCover(mContext, coverFile.getParent(), coverUrl);
}
} else {
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 {
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 {
Glide.with(mContext).load(file).centerCrop().error(R.drawable.picture_split).into(holder.video_image);
holder.duration.setText(TimeUtils.TimeFormat(localVideoInfo.getDuration() * 1000));
}
} else {
holder.iv_status.setVisibility(View.VISIBLE);
}
holder.iv_status.setOnClickListener(new View.OnClickListener() {
@@ -194,4 +247,29 @@ public class VideoAdapter extends RecyclerView.Adapter<VideoAdapter.VideoHolder>
void onScanCompleted(Bitmap bitmap);
}
//在这里处理任务执行中的状态,如进度进度条的刷新
@Download.onTaskRunning
void running(DownloadTask task) {
Log.e(TAG, "running: " + "正在下载:" + task.getState() + "-" + task.getPercent() + "--" + task.getExtendField());
notifyDataSetChanged();
}
@Download.onTaskComplete
void taskComplete(DownloadTask task) {
//在这里处理任务完成的状态
Log.e(TAG, "taskComplete: " + task.getFilePath());
notifyDataSetChanged();
}
@Download.onTaskFail
void taskFail(DownloadTask task, Exception e) {
try {
Log.e(TAG, "taskFail: e " + e.getMessage());
} catch (Exception ex) {
Log.e(TAG, "taskFail: ex " + ex.getMessage());
}
notifyDataSetChanged();
}
}

View File

@@ -14,6 +14,7 @@ import com.hainaos.vc.utils.LenovoCsdkUtil;
import com.hainaos.vc.utils.LoginUtils;
import com.hainaos.vc.utils.ToastUtil;
import com.hjq.toast.Toaster;
import com.tencent.bugly.crashreport.CrashReport;
import com.tencent.mmkv.MMKV;
public class BaseApplication extends Application {
@@ -31,7 +32,13 @@ public class BaseApplication extends Application {
String rootDir = MMKV.initialize(this);
Log.i(TAG, "mmkv root: " + rootDir);
LenovoCsdkUtil.init(this);
CrashReport.initCrashReport(getApplicationContext(), "3398d86ad9", false);
CrashReport.setDeviceId(this, LenovoCsdkUtil.getInstance().getSerial());
xcrash.XCrash.init(this);
JgyUtils.init(this);
Aria.init(this);
ConnectManager.init(this);

View File

@@ -0,0 +1,26 @@
package com.hainaos.vc.bean;
import java.io.Serializable;
public class CategoryUpdateInfo implements Serializable {
private static final long serialVersionUID = 8109570355122467961L;
String category_uuid;
int count;
public String getCategory_uuid() {
return category_uuid;
}
public void setCategory_uuid(String category_uuid) {
this.category_uuid = category_uuid;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}

View File

@@ -12,7 +12,7 @@ public class CategoryVideoInfo implements Serializable {
String name;
String cover;
long duration;
long file_size;
int file_size;
String md5;
int status;
int download;
@@ -75,11 +75,11 @@ public class CategoryVideoInfo implements Serializable {
this.duration = duration;
}
public long getFile_size() {
public int getFile_size() {
return file_size;
}
public void setFile_size(long file_size) {
public void setFile_size(int file_size) {
this.file_size = file_size;
}

View File

@@ -0,0 +1,26 @@
package com.hainaos.vc.bean;
import java.io.Serializable;
public class PasswdInfo implements Serializable {
private static final long serialVersionUID = -2630428449697230101L;
boolean correct;
String msg;
public boolean isCorrect() {
return correct;
}
public void setCorrect(boolean correct) {
this.correct = correct;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}

View File

@@ -0,0 +1,26 @@
package com.hainaos.vc.bean;
import java.io.Serializable;
public class VideoUpdate implements Serializable {
private static final long serialVersionUID = -1708177371148075587L;
int has_new;
int video_num;
public int getHas_new() {
return has_new;
}
public void setHas_new(int has_new) {
this.has_new = has_new;
}
public int getVideo_num() {
return video_num;
}
public void setVideo_num(int video_num) {
this.video_num = video_num;
}
}

View File

@@ -6,4 +6,13 @@ public class CommonConfig {
/*是否同意隐私协议*/
public static final String AGREED_THE_PRIVACY_AGREEMENT = "agreed_the_privacy_agreement";
/*主页检测是否有视频更新*/
public static final String MAIN_VIDEOS_CHECK_UPDATE_TIME = "MAIN_VIDEOS_CHECK_UPDATE_TIME";
/*分类页面检测是否有视频更新*/
public static final String CATEGORY_VIDEOS_CHECK_UPDATE_TIME = "CATEGORY_VIDEOS_CHECK_UPDATE_TIME";
/*检查分类更新详情时间*/
public static final String CHECK_CATEGORY_UPDATE_NUMBER_TIME = "CHECK_CATEGORY_UPDATE_NUMBER_TIME";
}

View File

@@ -0,0 +1,194 @@
package com.hainaos.vc.dialog;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import com.hainaos.vc.R;
/**
* description:自定义dialog
*/
public class UpdateDialog extends AlertDialog {
/**
* 显示的标题
*/
private TextView titleTv;
/**
* 显示的消息
*/
private TextView messageTv;
/**
* 确认和取消按钮
*/
private TextView positiveBn;
/**
* 按钮之间的分割线
*/
// private View columnLineView;
private Context mContext;
public UpdateDialog(Context context) {
super(context, R.style.CustomDialog);
this.mContext = context;
}
/**
* 都是内容数据
*/
private String message;
private String title;
private String positive;
private int imageResId = -1;
/**
* 底部是否只有一个按钮
*/
private boolean isSingle = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.dialog_update);
//按空白处不能取消动画
setCanceledOnTouchOutside(false);
//初始化界面控件
initView();
//初始化界面数据
refreshView();
//初始化界面控件的事件
initEvent();
}
/**
* 初始化界面的确定和取消监听器
*/
private void initEvent() {
//设置确定按钮被点击后,向外界提供监听
positiveBn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (onClickBottomListener != null) {
onClickBottomListener.onPositiveClick();
}
}
});
}
/**
* 初始化界面控件的显示数据
*/
private void refreshView() {
//如果用户自定了title和message
if (!TextUtils.isEmpty(title)) {
titleTv.setText(title);
titleTv.setVisibility(View.VISIBLE);
} else {
titleTv.setVisibility(View.GONE);
}
if (!TextUtils.isEmpty(message)) {
messageTv.setText(message);
}
//如果设置按钮的文字
if (!TextUtils.isEmpty(positive)) {
positiveBn.setText(positive);
} else {
positiveBn.setText("确定");
}
}
@Override
public void show() {
super.show();
refreshView();
}
/**
* 初始化界面控件
*/
private void initView() {
positiveBn = findViewById(R.id.positive);
titleTv = findViewById(R.id.title);
messageTv = findViewById(R.id.message);
}
/**
* 设置确定取消按钮的回调
*/
private OnClickBottomListener onClickBottomListener;
public void setOnClickBottomListener(OnClickBottomListener onClickBottomListener) {
this.onClickBottomListener = onClickBottomListener;
}
public interface OnClickBottomListener {
/**
* 点击确定按钮事件
*/
void onPositiveClick();
/**
* 点击取消按钮事件
*/
void onNegtiveClick();
}
public String getMessage() {
return message;
}
public UpdateDialog setMessage(String message) {
this.message = message;
return this;
}
public String getTitle() {
return title;
}
public UpdateDialog setTitle(String title) {
this.title = title;
return this;
}
public String getPositive() {
return positive;
}
public UpdateDialog setPositive(String positive) {
this.positive = positive;
return this;
}
public int getImageResId() {
return imageResId;
}
public boolean isSingle() {
return isSingle;
}
public UpdateDialog setSingle(boolean single) {
isSingle = single;
return this;
}
public UpdateDialog setImageResId(int imageResId) {
this.imageResId = imageResId;
return this;
}
@Override
public void dismiss() {
super.dismiss();
}
}

View File

@@ -43,16 +43,16 @@ public class CategoryViewModel extends BaseViewModel<FragmentCategoryBinding, Fr
@Deprecated
public void getDirList() {
List<CategoryInfo> homeAppInfos = new ArrayList<>();
homeAppInfos.add(new CategoryInfo("分类1", "", 1, "a1"));
homeAppInfos.add(new CategoryInfo("分类2", "", 1, "a2"));
homeAppInfos.add(new CategoryInfo("分类3", "", 1, "a3"));
homeAppInfos.add(new CategoryInfo("分类4", "", 1, "a4"));
homeAppInfos.add(new CategoryInfo("分类5", "", 1, "a5"));
homeAppInfos.add(new CategoryInfo("分类6", "", 1, "a6"));
homeAppInfos.add(new CategoryInfo("分类7", "", 1, "a7"));
homeAppInfos.add(new CategoryInfo("分类8", "", 1, "a8"));
homeAppInfos.add(new CategoryInfo("分类9", "", 1, "a9"));
homeAppInfos.add(new CategoryInfo("分类10", "", 1, "a10"));
homeAppInfos.add(new CategoryInfo("美容师招聘系统", "", 1, "a1"));
homeAppInfos.add(new CategoryInfo("美容师培育系统", "", 1, "a2"));
homeAppInfos.add(new CategoryInfo("薪酬设计系统", "", 1, "a3"));
homeAppInfos.add(new CategoryInfo("拓客系统", "", 1, "a4"));
homeAppInfos.add(new CategoryInfo("锁客系统", "", 1, "a5"));
homeAppInfos.add(new CategoryInfo("培育系统", "", 1, "a6"));
homeAppInfos.add(new CategoryInfo("晋级系统", "", 1, "a7"));
homeAppInfos.add(new CategoryInfo("万元达标系统", "", 1, "a8"));
homeAppInfos.add(new CategoryInfo("海纳五大指标", "", 1, "a9"));
homeAppInfos.add(new CategoryInfo("设计课程", "", 1, "a10"));
// homeAppInfos.add(new CategoryInfo("下载视频", HomeAppAdapter.DOWNLOAD_CENTER, 1, ""));
// homeAppInfos.add(new CategoryInfo("用户中心", HomeAppAdapter.USER_CENTER, 1, ""));

View File

@@ -7,10 +7,12 @@ import com.hainaos.vc.BuildConfig;
import com.hainaos.vc.bean.AppInfo;
import com.hainaos.vc.bean.BaseResponse;
import com.hainaos.vc.bean.CategoryInfo;
import com.hainaos.vc.bean.CategoryUpdateInfo;
import com.hainaos.vc.bean.CodeInfo;
import com.hainaos.vc.bean.LoginInfo;
import com.hainaos.vc.bean.UserInfo;
import com.hainaos.vc.bean.VideoListData;
import com.hainaos.vc.bean.VideoUpdate;
import com.hainaos.vc.bean.uiuios.AppUpdateInfo;
import com.hainaos.vc.config.CommonConfig;
import com.hainaos.vc.network.api.AppApi;
@@ -243,7 +245,32 @@ public class NetInterfaceManager {
public Observable<BaseResponse<VideoListData>> getVideoListObservable(String category_uuid, String password) {
String bearerToken = LoginUtils.getInstance().getBearerToken();
return getVideoApi()
.getVideoList(bearerToken, category_uuid, password)
.getVideoList(bearerToken, category_uuid, password, 20)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
@Deprecated
public Observable<BaseResponse<VideoUpdate>> getVideoUpdateObservable(String category_uuid, String current_time) {
String bearerToken = LoginUtils.getInstance().getBearerToken();
return getVideoApi()
.getVideoUpdate(bearerToken, category_uuid, current_time)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
public Observable<BaseResponse<VideoUpdate>> getVideoUpdateObservable(String current_time) {
String bearerToken = LoginUtils.getInstance().getBearerToken();
return getVideoApi()
.getVideoUpdate(bearerToken, current_time)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
public Observable<BaseResponse<List<CategoryUpdateInfo>>> getCategoryUpdateObservable(String current_time) {
String bearerToken = LoginUtils.getInstance().getBearerToken();
return getVideoApi()
.categoryUpdateNumber(bearerToken, current_time)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}

View File

@@ -22,6 +22,10 @@ public class UrlAddress {
public static final String VIDEO_LIST = "videos/video-list";
/*点击下载后统计上报*/
public static final String VIDEOS_DOWNLOAD = "videos/download";
/*检查视频分类更新*/
public static final String VIDEOS_CHECK_UPDATE = "videos/check-update";
/*检查分类列表是否有视频更新*/
public static final String VIDEOS_CATEGORY_UPDATE = "videos/category-update-number";
/*获取修改密码验证码*/

View File

@@ -2,7 +2,9 @@ package com.hainaos.vc.network.api;
import com.hainaos.vc.bean.BaseResponse;
import com.hainaos.vc.bean.CategoryInfo;
import com.hainaos.vc.bean.CategoryUpdateInfo;
import com.hainaos.vc.bean.VideoListData;
import com.hainaos.vc.bean.VideoUpdate;
import com.hainaos.vc.network.UrlAddress;
import java.util.List;
@@ -24,7 +26,8 @@ public interface VideoApi {
Observable<BaseResponse<VideoListData>> getVideoList(
@Header("Authorization") String token,
@Query("category_uuid") String category_uuid,
@Query("password") String password
@Query("password") String password,
@Query("page_size") int page_size
);
@FormUrlEncoded
@@ -33,4 +36,23 @@ public interface VideoApi {
@Header("Authorization") String token,
@Query("uuid") String uuid
);
@GET(UrlAddress.VIDEOS_CHECK_UPDATE)
Observable<BaseResponse<VideoUpdate>> getVideoUpdate(
@Header("Authorization") String token,
@Query("category_uuid") String category_uuid,
@Query("current_time") String current_time
);
@GET(UrlAddress.VIDEOS_CHECK_UPDATE)
Observable<BaseResponse<VideoUpdate>> getVideoUpdate(
@Header("Authorization") String token,
@Query("current_time") String current_time
);
@GET(UrlAddress.VIDEOS_CATEGORY_UPDATE)
Observable<BaseResponse<List<CategoryUpdateInfo>>> categoryUpdateNumber(
@Header("Authorization") String token,
@Query("current_time") String current_time
);
}

View File

@@ -22,8 +22,7 @@ import com.arialyy.aria.core.task.DownloadTask;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.hainaos.vc.R;
import com.hainaos.vc.bean.uiuios.AriaDownloadInfo;
import com.hainaos.vc.utils.LenovoCsdkUtil;
import com.hainaos.vc.bean.CategoryVideoInfo;
import com.hjq.toast.Toaster;
import java.io.File;
@@ -46,9 +45,9 @@ public class DownloadService extends Service {
Aria.download(this).register();
// mNotificationManagerCompat = NotificationManagerCompat.from(this);
mNotificationManagerCompat = NotificationManagerCompat.from(this);
// createNotificationChannel();
// createDownloadNotificationChannel();
createDownloadNotificationChannel();
// sendSimpleNotification();
}
@@ -119,11 +118,11 @@ public class DownloadService extends Service {
}
}
private void sendDownloadRunning(AriaDownloadInfo ariaDownloadInfo, int progress) {
private void sendDownloadRunning(CategoryVideoInfo ariaDownloadInfo, int progress) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_DOWNLOAD_ID)
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
.setContentTitle(ariaDownloadInfo.getAppName())
.setContentTitle(ariaDownloadInfo.getName())
.setContentText("下载中:" + progress + "%")
.setAutoCancel(true)
.setShowWhen(true)
@@ -132,24 +131,26 @@ public class DownloadService extends Service {
.setProgress(100, progress, false)
.setPriority(NotificationCompat.PRIORITY_HIGH);
// notificationId is a unique int for each notification that you must define
mNotificationManagerCompat.notify(ariaDownloadInfo.getAppId(), builder.build());
// startForeground(ariaDownloadInfo.getAppId(), builder.build());
// mNotificationManagerCompat.notify(ariaDownloadInfo.getAppId(), builder.build());
startForeground(ariaDownloadInfo.getFile_size(), builder.build());
}
private void sendDownloadComplete(AriaDownloadInfo ariaDownloadInfo, String path) {
private void sendDownloadComplete(CategoryVideoInfo ariaDownloadInfo, String path) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_DOWNLOAD_ID)
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
.setContentTitle(ariaDownloadInfo.getAppName())
.setContentTitle(ariaDownloadInfo.getName())
.setContentText("下载完成")
.setAutoCancel(true)
.setShowWhen(true)
.setOngoing(false)
.setOnlyAlertOnce(true)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(createIntent(path));
// .setContentIntent(createIntent(path))
;
// notificationId is a unique int for each notification that you must define
mNotificationManagerCompat.notify(ariaDownloadInfo.getAppId(), builder.build());
// mNotificationManagerCompat.notify(ariaDownloadInfo.getAppId(), builder.build());
startForeground(ariaDownloadInfo.getFile_size(), builder.build());
}
/**
@@ -175,11 +176,11 @@ public class DownloadService extends Service {
return pendingIntent;
}
private void sendDownloadFail(AriaDownloadInfo ariaDownloadInfo) {
private void sendDownloadFail(CategoryVideoInfo ariaDownloadInfo) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_DOWNLOAD_ID)
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
.setContentTitle(ariaDownloadInfo.getAppName())
.setContentTitle(ariaDownloadInfo.getName())
.setContentText("下载失败")
.setAutoCancel(true)
.setShowWhen(true)
@@ -187,17 +188,18 @@ public class DownloadService extends Service {
.setOnlyAlertOnce(true)
.setPriority(NotificationCompat.PRIORITY_HIGH);
// notificationId is a unique int for each notification that you must define
mNotificationManagerCompat.notify(ariaDownloadInfo.getAppId(), builder.build());
// mNotificationManagerCompat.notify(ariaDownloadInfo.getAppId(), builder.build());
startForeground(ariaDownloadInfo.getFile_size(), builder.build());
}
@Download.onTaskRunning
void running(DownloadTask task) {
String jsonString = task.getExtendField();
Log.e(TAG, "running: " + "正在下载:" + task.getPercent() + "% " + jsonString);
AriaDownloadInfo ariaDownloadInfo = getAriaDownloadInfo(jsonString);
CategoryVideoInfo ariaDownloadInfo = getAriaDownloadInfo(jsonString);
if (ariaDownloadInfo != null) {
Toaster.show("正在下载: " + ariaDownloadInfo.getAppName() + "\t" + task.getPercent() + "%");
// sendDownloadRunning(ariaDownloadInfo, task.getPercent());
Toaster.show("正在下载: " + ariaDownloadInfo.getName() + "\t" + task.getPercent() + "%");
sendDownloadRunning(ariaDownloadInfo, task.getPercent());
}
}
@@ -205,14 +207,14 @@ public class DownloadService extends Service {
void taskComplete(DownloadTask task) {
String path = task.getFilePath();
Log.e(TAG, "taskComplete: " + path);
if (path.endsWith(".apk")) {
LenovoCsdkUtil.getInstance().installPackage(task.getFilePath());
if (path.endsWith(".hnv")) {
// LenovoCsdkUtil.getInstance().installPackage(task.getFilePath());
String jsonString = task.getExtendField();
Log.e(TAG, "taskComplete: " + "下载完成:" + jsonString);
AriaDownloadInfo ariaDownloadInfo = getAriaDownloadInfo(jsonString);
CategoryVideoInfo ariaDownloadInfo = getAriaDownloadInfo(jsonString);
if (ariaDownloadInfo != null) {
Toaster.show("下载完成: " + "\t" + ariaDownloadInfo.getAppName());
// sendDownloadComplete(ariaDownloadInfo, task.getFilePath());
Toaster.show("下载完成: " + "\t" + ariaDownloadInfo.getName());
sendDownloadComplete(ariaDownloadInfo, task.getFilePath());
}
}
}
@@ -222,19 +224,19 @@ public class DownloadService extends Service {
Log.e(TAG, "taskFail: ");
String jsonString = task.getExtendField();
Log.e(TAG, "taskFail: " + "下载失败:" + jsonString);
AriaDownloadInfo ariaDownloadInfo = getAriaDownloadInfo(jsonString);
CategoryVideoInfo ariaDownloadInfo = getAriaDownloadInfo(jsonString);
if (ariaDownloadInfo != null) {
Toaster.show("下载失败: " + "\t" + ariaDownloadInfo.getAppName());
// sendDownloadFail(ariaDownloadInfo);
Toaster.show("下载失败: " + "\t" + ariaDownloadInfo.getName());
sendDownloadFail(ariaDownloadInfo);
}
}
private AriaDownloadInfo getAriaDownloadInfo(String jsonString) {
private CategoryVideoInfo getAriaDownloadInfo(String jsonString) {
if (!TextUtils.isEmpty(jsonString)) {
Gson gson = new Gson();
Type type = new TypeToken<AriaDownloadInfo>() {
Type type = new TypeToken<CategoryVideoInfo>() {
}.getType();
AriaDownloadInfo ariaDownloadInfo = null;
CategoryVideoInfo ariaDownloadInfo = null;
try {
ariaDownloadInfo = gson.fromJson(jsonString, type);
} catch (Exception e) {

View File

@@ -2,6 +2,8 @@ package com.hainaos.vc.utils;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;
@@ -10,6 +12,7 @@ import androidx.core.content.ContextCompat;
import com.arialyy.aria.core.Aria;
import com.hainaos.vc.bean.AppInfo;
import com.hainaos.vc.bean.CategoryVideoInfo;
import com.hainaos.vc.bean.uiuios.AppUpdateInfo;
import com.hainaos.vc.bean.uiuios.AriaDownloadInfo;
import com.hainaos.vc.gson.GsonUtils;
@@ -17,6 +20,8 @@ import com.hainaos.vc.service.DownloadService;
import java.io.File;
import java.text.DecimalFormat;
import java.util.HashSet;
import java.util.Set;
public class FileUtils {
private static final String TAG = "FileUtils";
@@ -40,6 +45,77 @@ public class FileUtils {
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
*/
@@ -124,6 +200,7 @@ public class FileUtils {
}
}
@Deprecated
public static void ariaDownload(Context context, String dirName, String url, String md5) {
String downLoadPath = getHainaVideoPath(context) + dirName + File.separator;
File dirFile = new File(downLoadPath);
@@ -144,6 +221,12 @@ public class FileUtils {
// .ignoreFilePathOccupy()
.setExtendField(url)
.create(); //启动下载}
Intent intent = new Intent(context, DownloadService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent);
} else {
context.startService(intent);
}
} else {
Log.e("ariaDownload", "fileName = " + fileName + " exists");
}
@@ -154,6 +237,104 @@ public class FileUtils {
Aria.download(context)
.load(url) //读取下载地址
.setFilePath(file.getAbsolutePath())
// .ignoreFilePathOccupy()
.setExtendField(url)
.create(); //启动下载}
Intent intent = new Intent(context, DownloadService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent);
} else {
context.startService(intent);
}
}
}
public static void ariaDownload(Context context, String dirName, CategoryVideoInfo categoryVideoInfo) {
String url = categoryVideoInfo.getFile_url();
String md5 = categoryVideoInfo.getMd5();
String downLoadPath = getHainaVideoPath(context) + dirName + File.separator;
File dirFile = new File(downLoadPath);
if (!dirFile.exists()) {
Log.e(TAG, "ariaDownload: mkdirs = " + dirFile.mkdirs());
}
String fileName = getFileNamefromURL(url);
File file = new File(downLoadPath + fileName);
if (file.exists() && !file.isDirectory()) {
String fileMD5 = com.blankj.utilcode.util.FileUtils.getFileMD5ToString(file);
Log.e("ariaDownload", "fileOnlineMD5=" + md5);
Log.e("ariaDownload", "fileMD5=" + fileMD5);
if (!TextUtils.isEmpty(md5)) {
if (!md5.equals(fileMD5)) {
Aria.download(context)
.load(url) //读取下载地址
.setFilePath(file.getAbsolutePath())
// .ignoreFilePathOccupy()
.setExtendField(GsonUtils.toJSONString(categoryVideoInfo))
.create(); //启动下载}
Intent intent = new Intent(context, DownloadService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent);
} else {
context.startService(intent);
}
} else {
Log.e("ariaDownload", "fileName = " + fileName + " exists");
}
} else {
Log.e("ariaDownload", url + " no md5 params , skip");
}
} else {
Aria.download(context)
.load(url) //读取下载地址
.setFilePath(file.getAbsolutePath())
// .ignoreFilePathOccupy()
.setExtendField(GsonUtils.toJSONString(categoryVideoInfo))
.create(); //启动下载}
Intent intent = new Intent(context, DownloadService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent);
} else {
context.startService(intent);
}
}
}
public static void ariaDownload(Context context, String dirName, String url) {
String downLoadPath = getHainaVideoPath(context) + dirName + File.separator;
File dirFile = new File(downLoadPath);
if (!dirFile.exists()) {
Log.e(TAG, "ariaDownload: mkdirs = " + dirFile.mkdirs());
}
String fileName = getFileNamefromURL(url);
File file = new File(downLoadPath + fileName);
if (file.exists() && !file.isDirectory()) {
Log.e("ariaDownload", url + " pic exists, skip");
} else {
Aria.download(context)
.load(url) //读取下载地址
.setFilePath(file.getAbsolutePath())
// .ignoreFilePathOccupy()
.setExtendField(url)
.create(); //启动下载}
}
}
public static void ariaDownloadCover(Context context, String coverPath, String url) {
File dirFile = new File(coverPath);
if (!dirFile.exists()) {
Log.e(TAG, "ariaDownload: mkdirs = " + dirFile.mkdirs());
}
String fileName = getFileNamefromURL(url);
File file = new File(coverPath + File.separator + fileName);
if (file.exists() && !file.isDirectory()) {
Log.e("ariaDownload", url + " pic exists, skip");
} else {
Aria.download(context)
.load(url) //读取下载地址
.setFilePath(file.getAbsolutePath())
// .ignoreFilePathOccupy()
.setExtendField(url)
.create(); //启动下载}
@@ -204,12 +385,7 @@ public class FileUtils {
.setExtendField(GsonUtils.toJSONString(ariaDownloadInfo))
.create(); //启动下载}
}
Intent intent = new Intent(context, DownloadService.class);
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// context.startForegroundService(intent);
// } else {
context.startService(intent);
// }
}

View File

@@ -29,4 +29,10 @@ public class TimeUtils {
Date date = new Date(second * 1000);
return sdf.format(date);
}
public static String transferMillisecondToDate(long millisecond) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date(millisecond);
return sdf.format(date);
}
}

View File

@@ -1,38 +1,33 @@
package com.hainaos.vc.utils;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Build;
import android.os.StatFs;
import android.text.format.Formatter;
import android.util.Log;
import java.lang.reflect.Method;
public class Utils {
@SuppressLint({"MissingPermission", "HardwareIds"})
public static String getSerial() {
// if (BuildConfig.DEBUG) {
// return "T98005H1024GB32GB";
// @SuppressLint({"MissingPermission", "HardwareIds"})
// public static String getSerial() {
//// if (BuildConfig.DEBUG) {
//// return "T98005H1024GB32GB";
//// }
// String serial = "unknow";
// try {
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {//9.0+
// serial = Build.getSerial();
// } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {//8.0+
// serial = Build.SERIAL;
// } else {//8.0-
// Class<?> c = Class.forName("android.os.SystemProperties");
// Method get = c.getMethod("get", String.class);
// serial = (String) get.invoke(c, "ro.serialno");
// }
// } catch (Exception e) {
// e.printStackTrace();
// Log.e("getSerial", "读取设备序列号异常:" + e.toString());
// }
// return serial;
// }
String serial = "unknow";
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {//9.0+
serial = Build.getSerial();
} else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {//8.0+
serial = Build.SERIAL;
} else {//8.0-
Class<?> c = Class.forName("android.os.SystemProperties");
Method get = c.getMethod("get", String.class);
serial = (String) get.invoke(c, "ro.serialno");
}
} catch (Exception e) {
e.printStackTrace();
Log.e("getSerial", "读取设备序列号异常:" + e.toString());
}
return serial;
}
public static String getDataTotalSize(Context context) {
StatFs sf = new StatFs(context.getCacheDir().getAbsolutePath());

View File

@@ -2,14 +2,15 @@ package com.hainaos.vc.video;
import android.net.Uri;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSourceInputStream;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.TransferListener;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.util.List;
import java.util.Map;
@@ -42,132 +43,80 @@ public class AesDataSource2 implements DataSource {
@Override
public long open(DataSpec dataSpec) throws IOException {
// 1. 获取请求的绝对位置
// 1. 先关闭之前的流(如果存在)
close();
final int AES_BLOCK_SIZE = 16;
long position = dataSpec.position;
// AES 块大小通常为 16 字节
final int AES_BLOCK_SIZE = 16;
// 2. 计算块索引 (Block Index) 和 块内偏移 (Offset inside the block)
// 例如position = 100blockIndex = 6 (96字节处)offset = 4
// 2. 计算 CTR 块索引和块内偏移
long blockIndex = position / AES_BLOCK_SIZE;
int offsetInBlock = (int) (position % AES_BLOCK_SIZE);
// 3. 计算对齐后的起始读取位置 (必须是 16 的倍数)
long startPosition = blockIndex * AES_BLOCK_SIZE;
// 3. 构建新的 DataSpec从 16 字节对齐的位置开始读取
// 如果原始请求了长度,我们需要增加 offsetInBlock 以保证能读够对应的数据
long requestLength = dataSpec.length != C.LENGTH_UNSET
? dataSpec.length + offsetInBlock
: C.LENGTH_UNSET;
DataSpec alignedSpec = dataSpec.buildUpon()
.setPosition(startPosition)
.setLength(requestLength)
.build();
// 4. 打开上层数据源并获取实际可读长度
long upstreamLength = upstream.open(alignedSpec);
try {
// 4. 初始化 Cipher
// 5. 初始化 Cipher
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
SecretKeySpec keySpec = new SecretKeySpec(secretKey, "AES");
// 【关键步骤】:根据 blockIndex 计算新的 IV
// CTR 模式下NewIV = OriginalIV + blockIndex
byte[] newIv = getAdjustedIv(this.iv, blockIndex);
IvParameterSpec ivSpec = new IvParameterSpec(newIv);
// 计算调整后的 IV (NewIV = OriginalIV + blockIndex)
byte[] adjustedIv = getAdjustedIv(this.iv, blockIndex);
IvParameterSpec ivSpec = new IvParameterSpec(adjustedIv);
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
// 5. 让上层数据源 (FileDataSource) 从对齐的位置 (startPosition) 开始读
// 注意:我们修改了 position但保持 length 不变 (或者处理 open ended)
long length = dataSpec.length != C.LENGTH_UNSET ? dataSpec.length + offsetInBlock : C.LENGTH_UNSET;
// 6. 包装成解密流
// 注意:我们直接包装 upstream 提供的 InputStream
cipherInputStream = new CipherInputStream(new UpstreamInputStream(upstream), cipher);
DataSpec newSpec = dataSpec.buildUpon()
.setPosition(startPosition)
.setLength(length)
.build();
// 打开上层流
InputStream inputStream = new DataSourceInputStream(upstream, newSpec);
// 创建解密流
cipherInputStream = new CipherInputStream(inputStream, cipher);
// 6. 【重要】跳过块内的偏移量
// 因为我们要给 ExoPlayer 返回的是从 dataSpec.position 开始的数据,
// 但我们是从 startPosition (前一个16倍数) 开始解密的,所以前面多读的 offsetInBlock 个字节是无用的。
// 7. 丢弃块内偏移量多出的字节
if (offsetInBlock > 0) {
forceSkip(cipherInputStream, offsetInBlock);
forceSkip(offsetInBlock);
}
opened = true;
// 计算剩余长度
// 8. 计算返回给 ExoPlayer 的剩余长度
if (dataSpec.length != C.LENGTH_UNSET) {
bytesRemaining = dataSpec.length;
} else if (upstreamLength != C.LENGTH_UNSET) {
bytesRemaining = upstreamLength - offsetInBlock;
} else {
long upstreamLength = upstream.open(newSpec); // 这一步其实已经在 DataSourceInputStream 里做过了,这里仅作逻辑参考
// 通常 upstream.open 返回的是从 startPosition 开始的长度
// 如果 upstream 支持长度解析:
// bytesRemaining = upstreamLength != C.LENGTH_UNSET ? upstreamLength - offsetInBlock : C.LENGTH_UNSET;
bytesRemaining = C.LENGTH_UNSET;
}
opened = true;
return bytesRemaining;
} catch (Exception e) {
throw new IOException(e);
}
}
/**
* 根据块索引计算新的 IV。
* CTR 模式将 IV 视为一个大整数 (BigEndian),每过一个块,计数器 +1。
*/
private byte[] getAdjustedIv(byte[] originalIv, long blockIndex) {
// 使用 BigInteger 处理大数加法,防止溢出
BigInteger ivVal = new BigInteger(1, originalIv);
BigInteger offset = BigInteger.valueOf(blockIndex);
BigInteger newIvVal = ivVal.add(offset);
byte[] newIv = newIvVal.toByteArray();
// BigInteger.toByteArray() 可能会因为符号位导致长度变为 17 (如果是正数且最高位是1)
// 或者因为数值较小导致长度小于 16。必须确保返回 16 字节。
byte[] result = new byte[16];
int srcOffset = newIv.length > 16 ? newIv.length - 16 : 0;
int dstOffset = newIv.length < 16 ? 16 - newIv.length : 0;
int copyLength = newIv.length > 16 ? 16 : newIv.length;
System.arraycopy(newIv, srcOffset, result, dstOffset, copyLength);
return result;
}
/**
* 强制跳过指定字节数。CipherInputStream 的 skip 有时不可靠,建议循环 read。
*/
private void forceSkip(InputStream stream, int bytesToSkip) throws IOException {
long skipped = 0;
byte[] skipBuffer = new byte[1024];
while (skipped < bytesToSkip) {
int toRead = (int) Math.min(bytesToSkip - skipped, skipBuffer.length);
int read = stream.read(skipBuffer, 0, toRead);
if (read == -1) {
break;
}
skipped += read;
throw new IOException("Failed to initialize AES cipher", e);
}
}
@Override
public int read(byte[] buffer, int offset, int readLength) throws IOException {
if (readLength == 0) {
return 0;
}
int bytesToRead = readLength;
if (bytesRemaining != C.LENGTH_UNSET) {
bytesToRead = (int) Math.min(readLength, bytesRemaining);
}
if (bytesToRead == 0) {
return C.RESULT_END_OF_INPUT;
}
if (readLength == 0) return 0;
// 限制读取长度,不要超过 bytesRemaining
int bytesToRead = (bytesRemaining == C.LENGTH_UNSET)
? readLength
: (int) Math.min(readLength, bytesRemaining);
if (bytesToRead <= 0 && bytesRemaining != C.LENGTH_UNSET) return C.RESULT_END_OF_INPUT;
int bytesRead = cipherInputStream.read(buffer, offset, bytesToRead);
if (bytesRead == -1) {
if (bytesRemaining != C.LENGTH_UNSET) {
// 预期还要读数据但读不到了,抛错
throw new IOException("End of stream reached prematurely");
}
return C.RESULT_END_OF_INPUT;
}
@@ -177,6 +126,41 @@ public class AesDataSource2 implements DataSource {
return bytesRead;
}
private void forceSkip(int bytesToSkip) throws IOException {
int skipped = 0;
byte[] skipBuffer = new byte[Math.min(bytesToSkip, 2048)];
while (skipped < bytesToSkip) {
int toRead = Math.min(bytesToSkip - skipped, skipBuffer.length);
int read = cipherInputStream.read(skipBuffer, 0, toRead);
if (read == -1) throw new EOFException("Check your offset and file size.");
skipped += read;
}
}
private byte[] getAdjustedIv(byte[] originalIv, long blockIndex) {
if (blockIndex == 0) return originalIv;
BigInteger ivVal = new BigInteger(1, originalIv);
BigInteger newIvVal = ivVal.add(BigInteger.valueOf(blockIndex));
byte[] raw = newIvVal.toByteArray();
byte[] result = new byte[16];
int length = Math.min(raw.length, 16);
System.arraycopy(raw, Math.max(0, raw.length - 16), result, 16 - length, length);
return result;
}
@Override
public void close() throws IOException {
if (opened) {
opened = false;
if (cipherInputStream != null) {
cipherInputStream.close();
cipherInputStream = null;
}
upstream.close();
}
}
@Nullable
@Override
public Uri getUri() {
return upstream.getUri();
@@ -187,14 +171,25 @@ public class AesDataSource2 implements DataSource {
return upstream.getResponseHeaders();
}
@Override
public void close() throws IOException {
if (opened) {
opened = false;
if (cipherInputStream != null) {
cipherInputStream.close(); // 这也会关闭 upstream
cipherInputStream = null;
/**
* 一个简单的内部类,将 DataSource 转为 InputStream
*/
private static final class UpstreamInputStream extends java.io.InputStream {
private final DataSource dataSource;
private final byte[] singleByteArray = new byte[1];
public UpstreamInputStream(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public int read() throws IOException {
return read(singleByteArray) == -1 ? -1 : (singleByteArray[0] & 0xFF);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
return dataSource.read(b, off, len);
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="100dp"
android:height="41.4dp"
android:viewportWidth="100"
android:viewportHeight="41.4">
<path
android:pathData="M19.4,19.9h11.7v2.7h-11.7v-2.7Z"
android:fillColor="#d81e06" />
<path
android:pathData="M79.9,0.8H20C9,0.8 0,9.8 0,20.9v16.7c0,2 1.4,3.4 3.4,3.4h76.6c11,0 20.1,-9 20.1,-20.1S91,0.8 79.9,0.8ZM61,13.7h5c-0.3,-0.7 -0.7,-1.3 -1.2,-2.1l1.5,-0.7c0.5,0.8 1,1.6 1.3,2.4l-0.8,0.4h4.9v1.5h-10.9s0,-1.5 0,-1.5ZM60.3,19.1h3.8c-0.7,-1.1 -1.3,-1.9 -1.9,-2.7l1.2,-0.9c0.5,0.6 1.2,1.5 2,2.6l-1.3,0.9h3c0.8,-1.2 1.6,-2.4 2.4,-3.6l1.6,0.9c-0.7,0.9 -1.5,1.9 -2.1,2.7h3.5v1.5h-12.2s0,-1.5 0,-1.4ZM35.7,15.6h-13.9c-0.5,0.9 -1,1.8 -1.6,2.6h12.7v12.4c0,1.9 -0.9,2.9 -2.8,2.9h-3.7c0,-0.6 -0.2,-1.2 -0.4,-1.9 1.4,0 2.5,0.2 3.5,0.2s1.6,-0.6 1.6,-1.6v-1.6h-11.6v5.3h-1.9v-12c-1.1,1.3 -2.3,2.5 -3.7,3.8 -0.3,-0.6 -0.7,-1.1 -1.1,-1.7 3,-2.5 5.3,-5.3 7,-8.3h-6.4v-1.7h7.2c0.5,-0.9 0.8,-1.9 1.2,-3l2,0.5c-0.3,0.9 -0.7,1.7 -1,2.5h13.1s0,1.7 0,1.7ZM58.7,33.4c-6.2,-0.2 -10.6,-0.9 -13.6,-2.2 -1.8,1.2 -4.3,2.1 -7.6,2.9 -0.3,-0.6 -0.7,-1.1 -1.1,-1.6 2.8,-0.4 5.1,-1.2 6.8,-2.2 -1.3,-0.9 -2.5,-2.1 -3.6,-3.6l1.2,-1h-1.8v-9.7h8.1v-2.1h-10v-1.6h21.3v1.6h-9.5v2.1h7.7v10.8h-1.7v-1.2h-6.5c-0.4,1.9 -1.1,3.2 -1.9,4.3 3.1,1.2 7.4,1.7 12.9,1.7 -0.3,0.7 -0.7,1.2 -1,1.8h0ZM60.3,30.6c0.7,-1.2 1.6,-2.7 2.5,-4.7l1.6,0.7c-0.8,1.7 -1.6,3.3 -2.5,4.8 -0.4,-0.3 -1,-0.6 -1.6,-0.8ZM67.4,31c0,1.6 -0.8,2.5 -2.4,2.5h-2.2c0,-0.5 -0.2,-1.1 -0.2,-1.7 0.9,0 1.6,0.2 2.1,0.2 0.7,0 1.1,-0.4 1.1,-1.2v-5.7h-4.8v-1.5h4.8v-2.2h1.6v2.2h4.5v1.5h-4.5v6ZM68.1,27l1.3,-0.9c0.8,1.2 1.6,2.2 2.2,3.3l-1.5,1c-0.6,-1.2 -1.3,-2.3 -2.1,-3.4ZM83.5,21.6h-3.3v12.2h-1.7v-12.2h-3.5v1.2c0,4.8 -1.2,8.5 -3.4,11.1 -0.3,-0.5 -0.8,-1 -1.2,-1.5 2,-2.2 3,-5.4 3,-9.6v-9.7c3.2,0 6.2,-0.4 8.9,-0.9l0.6,1.8c-3,0.4 -5.7,0.7 -7.8,0.8v5.4h8.5v1.6Z"
android:fillColor="#d81e06" />
<path
android:pathData="M19.4,24.2h11.7v2.8h-11.7v-2.8ZM41.2,25.7c1.1,1.5 2.3,2.6 3.9,3.5 0.9,-0.7 1.4,-1.8 1.9,-3.5h-5.7ZM55.2,17.5h-5.9c0,0.9 0,1.8 0,2.5h6v-2.5ZM47.4,21.6h-6.4v2.5h6.1c0.2,-0.7 0.2,-1.6 0.3,-2.5ZM47.5,17.5h-6.4v2.5h6.3c0,-0.7 0,-1.6 0,-2.5ZM55.2,21.6h-6c0,0.9 0,1.7 -0.2,2.5h6.3v-2.5h0Z"
android:fillColor="#d81e06" />
</vector>

View File

@@ -63,6 +63,19 @@
app:layout_constraintTop_toTopOf="parent"
tools:text="海纳美业学习机" />
<ImageView
android:id="@+id/iv_update"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="8dp"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:src="@drawable/ic_video_update"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
@@ -71,10 +84,10 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/cl_title">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- <androidx.swiperefreshlayout.widget.SwipeRefreshLayout-->
<!-- android:id="@+id/swipeRefreshLayout"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="match_parent">-->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
@@ -87,9 +100,9 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_nodata"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
@@ -109,7 +122,7 @@
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<!-- </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>-->
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -70,16 +70,30 @@
android:id="@+id/tv_total"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginEnd="8dp"
android:text="视频:0个"
android:textColor="@color/black"
android:textSize="14sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toStartOf="@+id/iv_update"
app:layout_constraintTop_toTopOf="parent"
tools:text="@string/video_total" />
<ImageView
android:id="@+id/iv_update"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="8dp"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:src="@drawable/ic_video_update"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout

View File

@@ -373,7 +373,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/iv_more6"
app:layout_constraintTop_toTopOf="parent"
tools:text="用28GB/128GB" />
tools:text="用28GB/128GB" />
<ImageView
android:id="@+id/iv_more6"

View File

@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="240dp"
android:layout_height="160dp"
android:layout_centerInParent="true"
android:background="@drawable/bg_dialog"
android:minWidth="240dp"
android:orientation="vertical"
android:paddingTop="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:id="@+id/linearLayout2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center_vertical"
android:textColor="@color/black"
android:textSize="12sp"
android:textStyle="bold"
android:visibility="visible"
tools:text="消息提示" />
</LinearLayout>
<TextView
android:id="@+id/message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:gravity="center"
android:lineSpacingExtra="3dp"
android:lineSpacingMultiplier="1.2"
android:minHeight="50dp"
android:textColor="@color/gray"
android:textSize="10sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="提示消息提示消息提示消息提示消息提示消息" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/linearLayout3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
app:layout_constraintBottom_toBottomOf="parent">
<TextView
android:id="@+id/positive"
android:layout_width="48dp"
android:layout_height="20dp"
android:layout_weight="1"
android:background="@drawable/bg_login_button"
android:gravity="center"
android:singleLine="true"
android:textColor="@color/white"
android:textSize="8sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
tools:text="确定" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -28,6 +28,18 @@
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/icon_category" />
<ImageView
android:id="@+id/iv_update"
android:layout_width="20dp"
android:layout_height="8dp"
android:layout_marginEnd="6dp"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:src="@drawable/ic_video_update"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
@@ -67,14 +79,15 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginStart="4dp"
android:layout_marginStart="2dp"
android:gravity="center_horizontal"
android:maxLines="2"
android:minLines="2"
android:textColor="@color/white"
android:textSize="8sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -28,19 +28,6 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_download"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:background="@drawable/bg_download_button"
android:text="下载"
android:textColor="@color/white"
android:textSize="10sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="0dp"
@@ -51,7 +38,7 @@
android:textColor="@color/white"
android:textSize="15sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/tv_download"
app:layout_constraintEnd_toStartOf="@+id/tv_delete"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/video_image"
app:layout_constraintTop_toTopOf="parent"
@@ -60,7 +47,9 @@
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="0dp"
android:layout_weight="3"
android:gravity="center_vertical"
android:maxLines="3"
android:textColor="@color/black"
android:textSize="13sp"
@@ -70,8 +59,9 @@
<TextView
android:id="@+id/tv_duration"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_height="0dp"
android:layout_weight="2"
android:gravity="center_vertical"
android:maxLines="1"
android:singleLine="true"
android:textColor="@color/text_content"
@@ -81,8 +71,9 @@
<TextView
android:id="@+id/tv_size"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_height="0dp"
android:layout_weight="2"
android:gravity="center_vertical"
android:maxLines="1"
android:singleLine="true"
android:textColor="@color/text_content"
@@ -91,6 +82,39 @@
</LinearLayout>
<TextView
android:id="@+id/tv_delete"
android:layout_width="56dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:gravity="center"
android:background="@drawable/bg_delete_button"
android:text="删除"
android:textColor="@color/white"
android:maxLines="1"
android:singleLine="true"
android:textSize="10sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/tv_download"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_download"
android:layout_width="56dp"
android:gravity="center"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:maxLines="1"
android:singleLine="true"
android:background="@drawable/bg_download_button"
android:text="已下载"
android:textColor="@color/white"
android:textSize="10sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -20,7 +20,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:scaleType="centerInside"
app:is_cover_src="true" />
<com.shehuan.niv.NiceImageView

View File

@@ -1,2 +1,3 @@
SET class_path=target/video-encryptor-1.0-SNAPSHOT.jar;
native-image --no-fallback -H:ConfigurationFileDirectories=META-INF/native-image --allow-incomplete-classpath -classpath %class_path% com.penngo.gralvm.MainSwing
SET class_path=target\video-encryptor-1.0.1.jar;
native-image --no-fallback -H:ConfigurationFileDirectories=src\main\resources\META-INF\native-image --allow-incomplete-classpath -classpath %class_path% com.hnos.video.VideoEncryptorGUI

View File

@@ -1,7 +1,9 @@
@REM 使用idea 的mvn clean package
java -agentlib:native-image-agent=config-output-dir=./META-INF/native-image -jar video-encryptor-1.0.jar
cd target
native-image -jar target\video-encryptor-1.0.jar --no-fallback -H:ConfigurationFileDirectories=.\src\main\resources\META-INF\native-image
java -agentlib:native-image-agent=config-output-dir=src\main\resources\META-INF\native-image -jar video-encryptor-1.0.1.jar
native-image -jar target\video-encryptor-1.0.1.jar --no-fallback -H:ConfigurationFileDirectories=src\main\resources\META-INF\native-image
"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\editbin.exe" /SUBSYSTEM:WINDOWS video-encryptor-1.0.exe

View File

@@ -6,7 +6,7 @@
<groupId>com.hainaos</groupId>
<artifactId>video-encryptor</artifactId>
<version>1.0</version>
<version>1.0.1</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>

View File

@@ -21,8 +21,7 @@ public class VideoEncryptorGUI extends JFrame {
// 加密算法常量保持和VideoEncryptor一致
private static final String ALGORITHM = "AES/CTR/NoPadding";
// 视频文件扩展名列表(可根据需要扩展)
private static final List<String> VIDEO_EXTENSIONS = Arrays.asList("mp4", "avi", "mov", "mkv", "flv", "wmv",
"rmvb");
private static final List<String> VIDEO_EXTENSIONS = List.of("mp4");
// 测试用AES密钥16字节AES-128实际使用请替换为安全生成的密钥
private static final byte[] TEST_KEY = "1234567890123456".getBytes();
// 测试用IV向量16字节CTR模式要求IV长度等于块大小实际使用请随机生成

Binary file not shown.