diff --git a/app/build.gradle b/app/build.gradle index 82e7456..3d09f37 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -18,8 +18,8 @@ android { //There are no CERT files because If the mini sdk version is 23+, the AGP will ignore the V1 scheme signature. minSdkVersion 23 targetSdkVersion 29 - versionCode 16 - versionName "1.1.5" + versionCode 19 + versionName "1.1.8" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/java/com/hainaos/vc/activity/category/list/CategoryListActivity.java b/app/src/main/java/com/hainaos/vc/activity/category/list/CategoryListActivity.java index 613b8e6..2f421bc 100644 --- a/app/src/main/java/com/hainaos/vc/activity/category/list/CategoryListActivity.java +++ b/app/src/main/java/com/hainaos/vc/activity/category/list/CategoryListActivity.java @@ -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 { private static final String TAG = "CategoryListActivity"; @@ -46,8 +50,26 @@ public class CategoryListActivity extends BaseMvvmActivity() { + @Override + public void onChanged(VideoUpdate videoUpdate) { + if (videoUpdate.getHas_new() == 1) { + mViewModel.getCategoryUpdate(); + } + } + }); + + mViewModel.mCategoryUpdateInfoListData.observe(this, new Observer>() { + @Override + public void onChanged(List categoryUpdateInfos) { + Map map = categoryUpdateInfos.stream().collect(Collectors.toMap(CategoryUpdateInfo::getCategory_uuid, CategoryUpdateInfo::getCount)); + mCategoryAdapter.setCategoryUpdateInfoMap(map); + } + }); + + mViewModel.getCategoryList(); + mViewModel.getVideoUpdate(); } diff --git a/app/src/main/java/com/hainaos/vc/activity/category/list/CategoryListViewModel.java b/app/src/main/java/com/hainaos/vc/activity/category/list/CategoryListViewModel.java index a972ff8..bec3760 100644 --- a/app/src/main/java/com/hainaos/vc/activity/category/list/CategoryListViewModel.java +++ b/app/src/main/java/com/hainaos/vc/activity/category/list/CategoryListViewModel.java @@ -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,9 +25,10 @@ import io.reactivex.rxjava3.core.Observer; import io.reactivex.rxjava3.disposables.Disposable; public class CategoryListViewModel extends BaseViewModel { - private static final String TAG = "CategoryViewModel"; + private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE); + @Override public ActivityCategoryListBinding getVDBinding() { return binding; @@ -66,4 +72,75 @@ public class CategoryListViewModel extends BaseViewModel 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>() { + @Override + public void onSubscribe(@NonNull Disposable d) { + Log.e("getVideoUpdate", "onSubscribe: "); + } + + @Override + public void onNext(@NonNull BaseResponse 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> 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>>() { + @Override + public void onSubscribe(@NonNull Disposable d) { + Log.e("getCategoryUpdate", "onSubscribe: "); + } + + @Override + public void onNext(@NonNull BaseResponse> listBaseResponse) { + Log.e("getCategoryUpdate", "onNext: " + listBaseResponse); + if (listBaseResponse.code == 200) { + List categoryUpdateInfos = listBaseResponse.data; + mCategoryUpdateInfoListData.setValue(categoryUpdateInfos); + } + } + + @Override + public void onError(@NonNull Throwable e) { + Log.e("getCategoryUpdate", "onError: " + e.getMessage()); + } + + @Override + public void onComplete() { + Log.e("getCategoryUpdate", "onComplete: "); + } + }); + } } diff --git a/app/src/main/java/com/hainaos/vc/activity/category/local/LocalCategoryActivity.java b/app/src/main/java/com/hainaos/vc/activity/category/local/LocalCategoryActivity.java index dff26c3..6c12cf9 100644 --- a/app/src/main/java/com/hainaos/vc/activity/category/local/LocalCategoryActivity.java +++ b/app/src/main/java/com/hainaos/vc/activity/category/local/LocalCategoryActivity.java @@ -17,6 +17,7 @@ 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; @@ -135,6 +136,18 @@ public class LocalCategoryActivity extends BaseMvvmActivity() { + @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 { diff --git a/app/src/main/java/com/hainaos/vc/activity/category/local/LocalCategoryViewModel.java b/app/src/main/java/com/hainaos/vc/activity/category/local/LocalCategoryViewModel.java index ec4572f..bf9b055 100644 --- a/app/src/main/java/com/hainaos/vc/activity/category/local/LocalCategoryViewModel.java +++ b/app/src/main/java/com/hainaos/vc/activity/category/local/LocalCategoryViewModel.java @@ -8,10 +8,14 @@ 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.TimeUtils; import com.hainaos.vc.utils.VideoUtils; +import com.tencent.mmkv.MMKV; import com.trello.rxlifecycle4.RxLifecycle; import com.trello.rxlifecycle4.android.ActivityEvent; @@ -33,9 +37,10 @@ import io.reactivex.rxjava3.schedulers.Schedulers; import wseemann.media.FFmpegMediaMetadataRetriever; public class LocalCategoryViewModel extends BaseViewModel { - private static final String TAG = "LocalCategoryViewModel"; + private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE); + @Override public ActivityCategoryLocalBinding getVDBinding() { return binding; @@ -49,6 +54,7 @@ public class LocalCategoryViewModel extends BaseViewModel> 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>() { @@ -138,6 +144,7 @@ public class LocalCategoryViewModel extends BaseViewModel>() { @Override public void onSubscribe(@NonNull Disposable d) { @@ -164,4 +171,40 @@ public class LocalCategoryViewModel extends BaseViewModel 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>() { + @Override + public void onSubscribe(@NonNull Disposable d) { + Log.e("getVideoUpdate", "onSubscribe: "); + } + + @Override + public void onNext(@NonNull BaseResponse 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: "); + } + }); + } } diff --git a/app/src/main/java/com/hainaos/vc/activity/category/online/CategoryVideoActivity.java b/app/src/main/java/com/hainaos/vc/activity/category/online/CategoryVideoActivity.java index 18cf5b5..59a1c41 100644 --- a/app/src/main/java/com/hainaos/vc/activity/category/online/CategoryVideoActivity.java +++ b/app/src/main/java/com/hainaos/vc/activity/category/online/CategoryVideoActivity.java @@ -17,6 +17,7 @@ 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.VideoUpdate; import com.hainaos.vc.config.CommonConfig; import com.hainaos.vc.databinding.ActivityCategoryVideoBinding; import com.hainaos.vc.fragment.passwd.PasswdDialogFragment; @@ -146,6 +147,17 @@ public class CategoryVideoActivity extends BaseMvvmActivity() { + @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.getUuid(), mPasswd); } diff --git a/app/src/main/java/com/hainaos/vc/activity/category/online/CategoryVideoViewModel.java b/app/src/main/java/com/hainaos/vc/activity/category/online/CategoryVideoViewModel.java index 63c2a22..475bdbb 100644 --- a/app/src/main/java/com/hainaos/vc/activity/category/online/CategoryVideoViewModel.java +++ b/app/src/main/java/com/hainaos/vc/activity/category/online/CategoryVideoViewModel.java @@ -8,9 +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.ActivityCategoryVideoBinding; import com.hainaos.vc.network.NetInterfaceManager; import com.hainaos.vc.utils.FileUtils; +import com.hainaos.vc.utils.TimeUtils; +import com.tencent.mmkv.MMKV; import com.trello.rxlifecycle4.RxLifecycle; import com.trello.rxlifecycle4.android.ActivityEvent; @@ -29,6 +33,8 @@ public class CategoryVideoViewModel extends BaseViewModel> 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>() { @@ -97,4 +104,40 @@ public class CategoryVideoViewModel extends BaseViewModel 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>() { + @Override + public void onSubscribe(@NonNull Disposable d) { + Log.e("getVideoUpdate", "onSubscribe: "); + } + + @Override + public void onNext(@NonNull BaseResponse 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: "); + } + }); + } } diff --git a/app/src/main/java/com/hainaos/vc/activity/main/MainActivity.java b/app/src/main/java/com/hainaos/vc/activity/main/MainActivity.java index 941d4c6..13ec959 100644 --- a/app/src/main/java/com/hainaos/vc/activity/main/MainActivity.java +++ b/app/src/main/java/com/hainaos/vc/activity/main/MainActivity.java @@ -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 { private static final String TAG = "MainActivity"; @@ -61,6 +73,10 @@ public class MainActivity extends BaseMvvmActivity clickSubject = PublishSubject.create(); + private CompositeDisposable disposables = new CompositeDisposable(); + + @Override public boolean setNightMode() { return true; @@ -96,6 +112,16 @@ public class MainActivity extends BaseMvvmActivity { + // 这里执行需要防抖的操作,例如网络请求、计算等 + performAction(); + })); + mViewModel.mAppUpdateInfoUiUiOSData.observe(this, new Observer() { @Override public void onChanged(AppUpdateInfo appUpdateInfo) { @@ -115,8 +141,26 @@ public class MainActivity extends BaseMvvmActivity() { + @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 { 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>() { @Override public void onSubscribe(@NonNull Disposable d) { - Log.e("checkUpdate", "onSubscribe: "); + Log.e("checkUpdateUiUiOS", "onSubscribe: "); } @Override public void onNext(@NonNull BaseResponse 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 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>() { + @Override + public void onSubscribe(@NonNull Disposable d) { + Log.e("getVideoUpdate", "onSubscribe: "); + } + + @Override + public void onNext(@NonNull BaseResponse 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: "); + } + }); + } } diff --git a/app/src/main/java/com/hainaos/vc/activity/player/DecryptionPlayerActivity.java b/app/src/main/java/com/hainaos/vc/activity/player/DecryptionPlayerActivity.java index 7cd423a..b7c347e 100644 --- a/app/src/main/java/com/hainaos/vc/activity/player/DecryptionPlayerActivity.java +++ b/app/src/main/java/com/hainaos/vc/activity/player/DecryptionPlayerActivity.java @@ -5,6 +5,7 @@ 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; @@ -12,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 { @@ -46,6 +48,8 @@ public class DecryptionPlayerActivity extends BaseMvvmActivity { 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); @@ -131,6 +135,13 @@ public class DecryptionPlayerActivity extends BaseMvvmActivity mCategoryUpdateInfoMap; + + public void setCategoryUpdateInfoMap(Map categoryUpdateInfoMap) { + mCategoryUpdateInfoMap = categoryUpdateInfoMap; + notifyDataSetChanged(); + } + @NonNull @Override public Holder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { @@ -61,6 +69,11 @@ public class CategoryAdapter extends RecyclerView.Adapter> 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> getVideoUpdateObservable(String current_time) { + String bearerToken = LoginUtils.getInstance().getBearerToken(); + return getVideoApi() + .getVideoUpdate(bearerToken, current_time) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()); + } + + public Observable>> getCategoryUpdateObservable(String current_time) { + String bearerToken = LoginUtils.getInstance().getBearerToken(); + return getVideoApi() + .categoryUpdateNumber(bearerToken, current_time) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()); + } + public Observable> getCheckUpdateObservable() { return mRetrofit.create(AppApi.class) .checkUpdate(BuildConfig.APPLICATION_ID) diff --git a/app/src/main/java/com/hainaos/vc/network/UrlAddress.java b/app/src/main/java/com/hainaos/vc/network/UrlAddress.java index 079c191..fa8dd3f 100644 --- a/app/src/main/java/com/hainaos/vc/network/UrlAddress.java +++ b/app/src/main/java/com/hainaos/vc/network/UrlAddress.java @@ -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"; /*获取修改密码验证码*/ diff --git a/app/src/main/java/com/hainaos/vc/network/api/VideoApi.java b/app/src/main/java/com/hainaos/vc/network/api/VideoApi.java index 40de544..1b056bd 100644 --- a/app/src/main/java/com/hainaos/vc/network/api/VideoApi.java +++ b/app/src/main/java/com/hainaos/vc/network/api/VideoApi.java @@ -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; @@ -34,4 +36,23 @@ public interface VideoApi { @Header("Authorization") String token, @Query("uuid") String uuid ); + + @GET(UrlAddress.VIDEOS_CHECK_UPDATE) + Observable> getVideoUpdate( + @Header("Authorization") String token, + @Query("category_uuid") String category_uuid, + @Query("current_time") String current_time + ); + + @GET(UrlAddress.VIDEOS_CHECK_UPDATE) + Observable> getVideoUpdate( + @Header("Authorization") String token, + @Query("current_time") String current_time + ); + + @GET(UrlAddress.VIDEOS_CATEGORY_UPDATE) + Observable>> categoryUpdateNumber( + @Header("Authorization") String token, + @Query("current_time") String current_time + ); } diff --git a/app/src/main/java/com/hainaos/vc/utils/TimeUtils.java b/app/src/main/java/com/hainaos/vc/utils/TimeUtils.java index 5b89582..d16413d 100644 --- a/app/src/main/java/com/hainaos/vc/utils/TimeUtils.java +++ b/app/src/main/java/com/hainaos/vc/utils/TimeUtils.java @@ -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); + } } diff --git a/app/src/main/java/com/hainaos/vc/video/AesDataSource2.java b/app/src/main/java/com/hainaos/vc/video/AesDataSource2.java index 6be0322..58282e6 100644 --- a/app/src/main/java/com/hainaos/vc/video/AesDataSource2.java +++ b/app/src/main/java/com/hainaos/vc/video/AesDataSource2.java @@ -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 = 100,blockIndex = 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); } } } \ No newline at end of file diff --git a/app/src/main/res/drawable-hdpi/icon_download_manager.png b/app/src/main/res/drawable-hdpi/icon_download_manager.png index 2065548..6b23070 100644 Binary files a/app/src/main/res/drawable-hdpi/icon_download_manager.png and b/app/src/main/res/drawable-hdpi/icon_download_manager.png differ diff --git a/app/src/main/res/drawable-hdpi/icon_user_center.png b/app/src/main/res/drawable-hdpi/icon_user_center.png index 3504f34..ee2bc0d 100644 Binary files a/app/src/main/res/drawable-hdpi/icon_user_center.png and b/app/src/main/res/drawable-hdpi/icon_user_center.png differ diff --git a/app/src/main/res/drawable/ic_video_update.xml b/app/src/main/res/drawable/ic_video_update.xml new file mode 100644 index 0000000..b403d44 --- /dev/null +++ b/app/src/main/res/drawable/ic_video_update.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/layout/activity_category_local.xml b/app/src/main/res/layout/activity_category_local.xml index 078592a..62afa2c 100644 --- a/app/src/main/res/layout/activity_category_local.xml +++ b/app/src/main/res/layout/activity_category_local.xml @@ -63,6 +63,19 @@ app:layout_constraintTop_toTopOf="parent" tools:text="海纳美业学习机" /> + + - + + + android:layout_height="match_parent" /> - + - - - - - + + + diff --git a/app/src/main/res/layout/activity_category_video.xml b/app/src/main/res/layout/activity_category_video.xml index 8db3131..2b53e8a 100644 --- a/app/src/main/res/layout/activity_category_video.xml +++ b/app/src/main/res/layout/activity_category_video.xml @@ -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" /> + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_category.xml b/app/src/main/res/layout/item_category.xml index 35255d5..95fdd36 100644 --- a/app/src/main/res/layout/item_category.xml +++ b/app/src/main/res/layout/item_category.xml @@ -28,6 +28,18 @@ app:layout_constraintTop_toTopOf="parent" app:srcCompat="@drawable/icon_category" /> + +