/**
 * 回放页面
 */
package com.videogo.ui.playback;

import android.annotation.SuppressLint;
import android.app.Application;
import android.app.Dialog;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;

import android.util.DisplayMetrics;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;

import com.videogo.constant.IntentConsts;
import com.videogo.exception.BaseException;
import com.videogo.global.GlobalKit;
import com.videogo.ui.common.EZStreamConfigLoader;
import com.videogo.errorlayer.ErrorInfo;
import com.videogo.exception.ErrorCode;
import com.videogo.openapi.EZConstants.EZPlaybackConstants;
import com.videogo.openapi.EZPlayer;
import com.videogo.openapi.bean.EZCameraInfo;
import com.videogo.openapi.bean.EZCloudRecordFile;
import com.videogo.openapi.bean.EZDeviceInfo;
import com.videogo.openapi.bean.EZDeviceRecordFile;
import com.videogo.openapi.bean.resp.CloudPartInfoFile;
import com.videogo.ui.playback.bean.ClickedListItem;
import com.videogo.ui.playback.querylist.RecordRecyclerAdapter;
import com.videogo.ui.common.EZBusinessTool;
import com.videogo.ui.realplay.fec.FecHelper;
import com.videogo.util.DataManager;
import com.videogo.util.DateTimeUtil;
import ezviz.ezopensdkcommon.common.EZPermissionUtil;
import com.videogo.util.VerifyCodeInput;
import com.videogo.util.LocalInfo;
import com.videogo.util.LogUtil;
import com.videogo.widget.TitleBar;

import java.util.Calendar;
import java.util.Date;

import com.videogo.ui.playback.helper.AICloudStorageHelper;
import com.videogo.ui.playback.helper.DownloadHelper;
import com.videogo.ui.playback.helper.LoadingStateHelper;
import com.videogo.ui.playback.helper.OrientationHelper;
import com.videogo.ui.playback.helper.PlaybackRateHelper;
import com.videogo.ui.playback.helper.RecordCoverHelper;
import com.videogo.ui.playback.helper.RecordDateHelper;
import com.videogo.ui.playback.helper.RecordQueryHelper;
import com.videogo.ui.playback.helper.SurfaceHelper;
import com.videogo.ui.playback.helper.TitleBarHelper;
import com.videogo.ui.playback.helper.UpdateTimerHelper;

import ezviz.ezopensdk.R;

import ezviz.ezopensdkcommon.common.RootActivity;

import static com.videogo.EzvizApplication.getOpenSDK;

@SuppressLint({"DefaultLocale", "HandlerLeak", "NewApi", "NonConstantResourceId"})
public class EZPlayBackListActivity extends RootActivity implements
        Handler.Callback, RecordRecyclerAdapter.OnRecordItemClickListener,
        VerifyCodeInput.VerifyCodeInputListener {

    // region --- 全局变量|global variable ---

    private static final String TAG = EZPlayBackListActivity.class.getSimpleName();// TAG

    public LocalInfo localInfo = null;// 本地信息
    public Rect mRemotePlayBackRect = null;// 状态栏Rect
    
    public TitleBar mPortraitTitleBar;// 竖屏导航栏
    public TitleBar mLandscapeTitleBar;// 横屏导航栏
    public ViewGroup remoteListPage;// 回放页面
    public RelativeLayout mPlaybackPlayRl;// 回放UI父视图

    private TextView streamTypeTv;// 取流方式
    // 播放进度条区域 + 播放工具栏区域
    public LinearLayout controlArea;
    // 播放进度条区域
    private LinearLayout progressArea;// 播放进度条区域
    public TextView playTimeTV;// 播放进度条左侧，录像已播放时长
    private TextView duringTimeTV;// 播放进度条右侧，录像总时长
    public SeekBar progressSeekbar;// 播放进度条
    // 播放工具栏区域
    public ViewGroup mControlBarRL;// 工具栏父视图
    private ImageButton pauseBtn;// 暂停/播放按钮
    private ImageButton soundBtn;// 声音开关按钮

    public ImageView mCloudVideoActiveImg;// 云存储广告图片
    public int mRecordType;// 录像类型
    // 录像item点击、下载相关全局变量
    public ClickedListItem currentClickItemFile;// 当前被点击的item
    public EZDeviceRecordFile mDeviceRecordInfo = null;// item点击时记录录像的时间用，下载的时候需要时间
    public EZCloudRecordFile mCloudRecordInfo = null;// item点击时记录录像的时间用，下载的时候需要时间

    public EZPlayer mPlaybackPlayer = null;
    public Handler mHandler = null;
    public int mStatus = RemoteListContant.STATUS_INIT;// 播放状态
    private boolean notPause = true;// 是否暂停播放，默认为没有暂停

    public boolean isMultiChannelDevice;// 是否是双目设备，如果接入的设备没有双目设备，可以忽略相关逻辑
    public Date queryDate = null;// 查询时间

    // 以下为设备列表页面通过Bundle传递的数据
    public EZDeviceInfo mDeviceInfo = null;
    public EZCameraInfo mCameraInfo = null;
    // 以上为设备列表页面通过Bundle传递的数据

    // Helper
    public DownloadHelper mDownloadHelper;// 录像下载Helper
    public RecordQueryHelper mRecordQueryHelper;// 录像查询Helper
    public UpdateTimerHelper mUpdateTimerHelper;// 定时器管理Helper
    public OrientationHelper mOrientationHelper;// 屏幕旋转/全屏Helper
    public RecordDateHelper mRecordDateHelper;// 日期选择器Helper
    public PlaybackRateHelper mPlaybackRateHelper;// 倍速控制Helper
    public TitleBarHelper mTitleBarHelper;// 导航栏Helper
    private FecHelper mFecHelper;// 鱼眼矫正功能Helper
    public AICloudStorageHelper mAICloudStorageHelper;// AI云存储Helper
    public SurfaceHelper mSurfaceHelper;// Surface生命周期Helper
    public LoadingStateHelper mLoadingStateHelper;// 加载状态Helper
    public RecordCoverHelper mRecordCoverHelper;// SD卡录像封面Helper

    // endregion

    // region --- 页面启动方法|Page startup method ---

    public static void launch(Context context, EZDeviceInfo deviceInfo, EZCameraInfo cameraInfo) {
        Intent intent = new Intent(context, EZPlayBackListActivity.class);
        intent.putExtra(RemoteListContant.QUERY_DATE_INTENT_KEY, DateTimeUtil.getNow());
        intent.putExtra(IntentConsts.EXTRA_CAMERA_INFO, cameraInfo);
        intent.putExtra(IntentConsts.EXTRA_DEVICE_INFO, deviceInfo);
        context.startActivity(intent);
    }

    public static void launch(Context context, String deviceSerial, int cameraNo) {
        EZCameraInfo cameraInfo = new EZCameraInfo();
        cameraInfo.setDeviceSerial(deviceSerial);
        cameraInfo.setCameraNo(cameraNo);
        launch(context, new EZDeviceInfo(), cameraInfo);
    }

    // endregion

    // region --- 生命周期|Life Cycle ---

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.ez_playback_list_page);
        // 保持屏幕常亮 Keep the screen on
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        initData();// 数据初始化
        initView();// UI初始化
        initHelpers();// Helper初始化
        initEZPlayer();// 播放器初始化（依赖 SurfaceHelper，须在 initHelpers 之后）
        // FecHelper 依赖 SurfaceHelper、OrientationHelper、mPlaybackPlayer，须在 initHelpers 和 initEZPlayer 之后创建
        mFecHelper = FecHelper.createFromPlaybackActivity(this);

        mRecordQueryHelper.startQueryCloudRecordFiles();
        // 国内支持SD卡录像封面获取，海外不支持
        mRecordCoverHelper.initFetcher();
        if (!mRecordCoverHelper.isSupportSdCover()) {
            showToast(R.string.device_not_support_sd_record_cover);
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        LogUtil.d(TAG, "onResume()");
        EZPermissionUtil.onResume(this);
        new Handler().postDelayed(() -> {
            InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
            imm.hideSoftInputFromWindow(mSurfaceHelper.getTextureView().getWindowToken(), 0);
        }, 200);
        // 判断是否处理暂停状态
        if (notPause || mStatus == RemoteListContant.STATUS_DECRYPT) {
            onActivityResume();
            mRecordDateHelper.isDateSelected = false;
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        mOrientationHelper.postOnStart();
    }

    @Override
    protected void onStop() {
        super.onStop();
        mOrientationHelper.postOnStop();
        LogUtil.d(TAG, "onStop():" + notPause + " mStatus:" + mStatus);
        if (notPause) {
            closePlayBack();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        closePlayBack();
        getOpenSDK().releasePlayer(mPlaybackPlayer);
        mRecordCoverHelper.stopFetcher();// 断开与设备的链接
        mRecordQueryHelper.stopQueryTask();
        mDownloadHelper.stopAllDownloadTasks();
        // 释放AI云存储资源
        mAICloudStorageHelper.release();
        removeHandler(mHandler);
    }

    @Override
    public void onBackPressed() {
        if (mOrientationHelper.handleBackPressed()) {
            return;
        }
        onExitCurrentPageClick();
        finish();
    }

    @Override
    public void finish() {
        mRecordQueryHelper.clearCloudAdapterData();
        super.finish();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        mOrientationHelper.mOrientation = newConfig.orientation;
        mOrientationHelper.onOrientationChanged();
        super.onConfigurationChanged(newConfig);
    }

    // endregion

    // region --- 播放器消息回调|Player message callback ---

    @Override
    public boolean handleMessage(Message msg) {
        if (this.isFinishing()) {
            return false;
        }
        LogUtil.i(TAG, "handleMessage:" + msg.what);
        switch (msg.what) {
            // 以下消息为SDK发送的消息，用于取流相关消息回调
            case EZPlaybackConstants.MSG_REMOTE_PLAYBACK_PLAY_PREPARED:// 播放准备
                // 如果开始回放时需立即开启倍数，须在此消息回调中调用setPlaybackRate，不要在MSG_REMOTEPLAYBACK_PLAY_START消息回调中设置。
                // 因为setPlaybackRate设置后也会有一个MSG_REMOTEPLAYBACK_PLAY_START消息通知，在MSG_REMOTEPLAYBACK_PLAY_START消息回调中设置会陷入循环
//                    mPlaybackPlayer.setPlaybackRate(EZConstants.EZPlaybackRate.EZ_PLAYBACK_RATE_4);

                // 如果打开了纯音频播放配置，需要在此处打开声音
                if (GlobalKit.getInstance().enablePureAudio) {
                    updateSoundState(true);
                }
                break;
            case EZPlaybackConstants.MSG_REMOTEPLAYBACK_PLAY_START:// 录像回放开始|seek成功
                handlePlaybackStart(msg);
                break;
            case EZPlaybackConstants.MSG_REMOTEPLAYBACK_PLAY_FINISH:// 录像回放完成，回放完成后回调
                handlePlaybackFinish();
                break;
            case EZPlaybackConstants.MSG_REMOTE_PLAYBACK_RATE_LOWER:// 回放倍速降低消息
                showToast("回放倍速降速通知");
                // TODO 4倍速以上则直接降速到4倍速，4倍速及其以下则直接降速到1倍速
                break;
            case EZPlaybackConstants.MSG_REMOTEPLAYBACK_PLAY_FAIL:// 录像回放失败
                ErrorInfo errorInfo = (ErrorInfo) msg.obj;
                handlePlaybackFail(errorInfo);
                break;
            // 以上消息为SDK发送的消息，用于取流相关消息回调
            default:
                break;
        }
        return false;
    }

    /**
     * 播放失败处理
     *
     * @param errorInfo
     */
    private void handlePlaybackFail(ErrorInfo errorInfo) {
        LogUtil.d(TAG, "handlePlaybackFail. Playback failed. error info is " + errorInfo.toString());
        mStatus = RemoteListContant.STATUS_STOP;
        mPlaybackPlayer.stopPlayback();
        switch (errorInfo.errorCode) {
            // 收到这两个错误码，弹出对话框，让用户输入密码后，重新取流
            case ErrorCode.ERROR_INNER_VERIFYCODE_NEED:// 需要验证码
            case ErrorCode.ERROR_INNER_VERIFYCODE_ERROR: {// 验证码错误
                mLoadingStateHelper.setLoadingFail("");
                Dialog verifyDialog = VerifyCodeInput.VerifyCodeInputDialog(this, this);
                verifyDialog.show();
            }
                break;
            default: {// 显示错误码信息
                String errorInfoText = getErrorTip(R.string.remoteplayback_fail, errorInfo.errorCode);
                mLoadingStateHelper.setLoadingFail(errorInfoText);
                showToast(errorInfo.toString());
            }
                break;
        }
    }

    /**
     * 录像回放开始，更新UI
     */
    private void handlePlaybackStart(Message msg) {
        if (msg.arg1 != 0) {
            mOrientationHelper.updateRealRatio(msg.arg1, msg.arg2);
        }
        mStatus = RemoteListContant.STATUS_PLAYING;// 记录播放状态：播放中
        mLoadingStateHelper.setLoadingSuccess();// 加载成功，隐藏 Loading 区域
        progressArea.setVisibility(View.VISIBLE);
        setPlaybackControlButtonsEnabled(true);// 暂停/播放、声音、下载、倍数按钮恢复为可点击状态
        mOrientationHelper.setRemoteListSvLayout();
        mOrientationHelper.enableSensorOrientation();// 横竖屏感应
        // 播放器声音开启或关闭
        if (mPlaybackPlayer != null) {
            updateSoundState(localInfo.isSoundOpen());
        }
        mUpdateTimerHelper.startUpdateTimer();
        showStreamFetchType();
        // AI云存储录像播放成功，通知AI云存储页面显示播放动画
        mAICloudStorageHelper.notifyPlaybackStarted();
        mFecHelper.onPlaySucceed();
    }

    /**
     * 执行播放完毕的操作：如停止player、更新界面
     * execute some operations when play has finished. such as stop player, update UI
     */
    private void handlePlaybackFinish() {
        LogUtil.d(TAG, "handlePlaybackFinish");
        mUpdateTimerHelper.stopUpdateTimer();
        mPlaybackPlayer.stopPlayback();
        mStatus = RemoteListContant.STATUS_STOP;
        progressArea.setVisibility(View.INVISIBLE);// 播放完毕隐藏进度条（当前片段播放时长、进度条、总时长）
        setPlaybackControlButtonsEnabled(false);// 暂停/播放、声音、下载、倍数按钮设置为不可点击状态
        mLoadingStateHelper.setPlaybackFinish(getString(R.string.tip_playback_finished));
        // AI云存储录像播放完成，通知AI云存储页面重置播放图标
        mAICloudStorageHelper.notifyPlaybackFinished();
        mFecHelper.onPlayStopped();
    }

    /**
     * 根据当前播放的录像片段，更新下载用的录像信息对象（mDeviceRecordInfo / mCloudRecordInfo）
     * 确保下载按钮始终下载当前正在播放的片段
     */
    public void updateRecordInfoByCloudPartFile(CloudPartInfoFile cloudFile) {
        if (mRecordType == RemoteListContant.TYPE_LOCAL) {
            mDeviceRecordInfo = new EZDeviceRecordFile();
            EZBusinessTool.convertCloudPartInfoFile2EZDeviceRecordFile(mDeviceRecordInfo, cloudFile);
            mCloudRecordInfo = null;
        } else if (mRecordType == RemoteListContant.TYPE_CLOUD || mRecordType == RemoteListContant.TYPE_SDKCLOUD) {
            mCloudRecordInfo = new EZCloudRecordFile();
            EZBusinessTool.convertCloudPartInfoFile2EZCloudRecordFile(mCloudRecordInfo, cloudFile);
            mDeviceRecordInfo = null;
        }
    }

    // endregion

    // region --- Click点击事件|Click event ---

    /**
     * 播放|暂停Click
     */
    private void onPlayPauseClick() {
        if (mPlaybackPlayer == null) {
            showToast(getString(R.string.please_operate_after_select_any_record));
            return;
        }
        if (notPause) {
            // 暂停播放
            notPause = false;
            pauseBtn.setImageResource(R.drawable.ez_play_selector);
            if (mStatus == RemoteListContant.STATUS_PLAYING) {
                mStatus = RemoteListContant.STATUS_PAUSE;
                if (mPlaybackPlayer != null) {
                    // 加保护，规避CAS库小概率出现的10S死锁导致的ANR问题
                    getTaskManager().submit(() -> mPlaybackPlayer.pausePlayback());
                }
            }
        } else {
            notPause = true;
            pauseBtn.setImageResource(R.drawable.ez_pause_selector);
            if (mStatus == RemoteListContant.STATUS_PAUSE) {
                if (mPlaybackPlayer != null) {
                    // 加保护，规避CAS库小概率出现的10S死锁导致的ANR问题
                    getTaskManager().submit(() -> mPlaybackPlayer.resumePlayback());
                }
                mOrientationHelper.enableSensorOrientation();
                mStatus = RemoteListContant.STATUS_PLAYING;
            }
        }
    }

    /**
     * 开启/关闭声音Click
     */
    private void onSoundBtnClick() {
        if (mPlaybackPlayer == null) {
            return;
        }
        // 切换声音状态
        boolean newSoundState = !localInfo.isSoundOpen();
        localInfo.setSoundOpen(newSoundState);
        updateSoundState(newSoundState);
    }

    /**
     * 云存储图片Click
     */
    private void onCloudAdImageClick() {
        String errorInfo = "fail to call openCloudPage!";
        if (mCameraInfo == null) {
            showToast(errorInfo);
            return;
        }
        try {
            getOpenSDK().openCloudPage(mCameraInfo.getDeviceSerial(), mCameraInfo.getCameraNo());
        } catch (BaseException e) {
            showToast(errorInfo);
            e.printStackTrace();
        }
    }

    /**
     * 左上角返回Click
     */
    public void onExitCurrentPageClick() {
        notPause = true;
        mRecordQueryHelper.stopQueryTask();
        closePlayBack();
    }

    // endregion

    // region --- 回放进度条拖动事件|Replay progress bar drag event ---

    private final OnSeekBarChangeListener mSeekBarChangeListener = new OnSeekBarChangeListener() {
        /**
         * 拖动条停止拖动的时候调用
         */
        @Override
        public void onStopTrackingTouch(SeekBar arg0) {
            int progress = arg0.getProgress();
            if (progress == RemoteListContant.PROGRESS_MAX_VALUE) {// 拖动到最后
                handlePlaybackFinish();
                return;
            }
            if (currentClickItemFile != null) {
                // 计算拖动后的时间
                long beginTime = currentClickItemFile.getBeginTime();
                long endTime = currentClickItemFile.getEndTime();
                long avg = (endTime - beginTime) / RemoteListContant.PROGRESS_MAX_VALUE;
                long trackTime = beginTime + (progress * avg);
                LogUtil.i(TAG,
                        "onSeekBarStopTracking, begin time:" + DateTimeUtil.formatTimeToYMDHMSString(beginTime) +
                                " endtime:" + DateTimeUtil.formatTimeToYMDHMSString(endTime) +
                                " avg:" + avg +
                                " MAX:" + RemoteListContant.PROGRESS_MAX_VALUE +
                                " trackTime:" + DateTimeUtil.formatTimeToYMDHMSString(trackTime));
                mUpdateTimerHelper.stopUpdateTimer();
                if (mPlaybackPlayer != null) {
                    Calendar seekTime = Calendar.getInstance();
                    seekTime.setTime(new Date(trackTime));
                    mPlaybackPlayer.seekPlayback(seekTime);
                }
                seekInit();
            }
        }

        /**
         * 拖动条开始拖动的时候调用
         */
        @Override
        public void onStartTrackingTouch(SeekBar arg0) {
        }

        /**
         * 拖动条进度改变的时候调用
         */
        @Override
        public void onProgressChanged(SeekBar arg0, int arg1, boolean arg2) {
            if (currentClickItemFile != null) {
                long time = currentClickItemFile.getEndTime() - currentClickItemFile.getBeginTime();
                int diffSeconds = (int) (time * arg1 / 1000) / 1000;
                String convToUIDuration = RemoteListUtil.convToUIDuration(diffSeconds);
                playTimeTV.setText(convToUIDuration);
            }
        }
    };

    // endregion

    // region --- 验证码校验|CAPTCHA Verification ---

    @Override
    public void onInputVerifyCode(final String verifyCode) {
        LogUtil.d(TAG, "verify code is " + verifyCode);
        DataManager.getInstance().setDeviceSerialVerifyCode(mCameraInfo.getDeviceSerial(), verifyCode);
        if (mPlaybackPlayer != null) {
            mPlaybackPlayer.setPlayVerifyCode(verifyCode);
            mLoadingStateHelper.setStartLoading();
            startPlaybackByRecordCategory();
        }
    }

    // endregion

    // region --- OnHikItemClickListener 录像片段点击|OnHikItemClickListener video clip click ---

    @Override
    public void onRecordItemClick(CloudPartInfoFile cloudFile, ClickedListItem playClickItem) {
        mAICloudStorageHelper.setAICloudStoragePlayback(false);
        // 判断取流链路已经存在，如果是则用 seekPlayback 跳转到选中片段
        boolean isStreamConnected = (mStatus == RemoteListContant.STATUS_PLAYING || mStatus == RemoteListContant.STATUS_PAUSE);
        if (isStreamConnected) {
            currentClickItemFile = playClickItem;
            setPlaybackProgressUI(playClickItem.getBeginTime(), playClickItem.getEndTime());
            // 更新列表选中状态
            mRecordQueryHelper.updateAdapterSelection(playClickItem.getPosition());
            // 同步更新下载用的录像信息对象，避免seek后下载的仍是上一个片段
            updateRecordInfoByCloudPartFile(cloudFile);
            // seek 到选中片段的开始时间
            Calendar seekTime = Calendar.getInstance();
            seekTime.setTimeInMillis(playClickItem.getBeginTime());
            mUpdateTimerHelper.stopUpdateTimer();
            mPlaybackPlayer.seekPlayback(seekTime);
            seekInit();
            return;
        }

        stopCurrentPlayback();
        setPlaybackProgressUI(playClickItem.getBeginTime(), playClickItem.getEndTime());

        currentClickItemFile = playClickItem;
        mDeviceRecordInfo = null;
        mCloudRecordInfo = null;
        if (mRecordType == RemoteListContant.TYPE_LOCAL) {
            mDeviceRecordInfo = new EZDeviceRecordFile();
            EZBusinessTool.convertCloudPartInfoFile2EZDeviceRecordFile(mDeviceRecordInfo, cloudFile);
            // 将结束时间设置为列表中第一个录像（最近时间）的结束时间，实现连续回放
            Calendar latestStopTime = EZBusinessTool.getLatestStopTimeFromList(mRecordQueryHelper.getAdapterData(RemoteListContant.TYPE_LOCAL));
            if (latestStopTime != null) {
                mDeviceRecordInfo.setStopTime(latestStopTime);
            }
            mRecordQueryHelper.updateAdapterSelection(playClickItem.getPosition());
        } else if (mRecordType == RemoteListContant.TYPE_CLOUD) {
            mCloudRecordInfo = new EZCloudRecordFile();
            EZBusinessTool.convertCloudPartInfoFile2EZCloudRecordFile(mCloudRecordInfo, cloudFile);
            // 深拷贝后将结束时间设置为列表中第一个录像（最近时间）的结束时间，实现连续回放
            Calendar latestStopTime = EZBusinessTool.getLatestStopTimeFromList(mRecordQueryHelper.getAdapterData(RemoteListContant.TYPE_CLOUD));
            if (latestStopTime != null) {
                mCloudRecordInfo.setStopTime(latestStopTime);
            }
            mRecordQueryHelper.updateAdapterSelection(playClickItem.getPosition());
        } else if (mRecordType == RemoteListContant.TYPE_SDKCLOUD) {
            mCloudRecordInfo = new EZCloudRecordFile();
            EZBusinessTool.convertCloudPartInfoFile2EZCloudRecordFile(mCloudRecordInfo, cloudFile);
            // 深拷贝后将结束时间设置为列表中第一个录像（最近时间）的结束时间，实现连续回放
            Calendar latestStopTime2 = EZBusinessTool.getLatestStopTimeFromList(mRecordQueryHelper.getAdapterData(RemoteListContant.TYPE_SDKCLOUD));
            if (latestStopTime2 != null) {
                mCloudRecordInfo.setStopTime(latestStopTime2);
            }
            mRecordQueryHelper.updateAdapterSelection(playClickItem.getPosition());
        }
        startPlaybackByRecordCategory();
    }

    // endregion

    // region --- 自定义方法|custom method ---

    private void initEZPlayer() {
        // 1.创建播放器
        mPlaybackPlayer = getOpenSDK().createPlayer(mCameraInfo.getDeviceSerial(), mCameraInfo.getCameraNo());
        // 2.配置播放器：设置回调，设置验证码，设置播放视图等配置
        mPlaybackPlayer.setHandler(mHandler);
        mPlaybackPlayer.setSurfaceEx(mSurfaceHelper.getSurfaceTexture());
        if (isMultiChannelDevice) {// 双目设备
            mPlaybackPlayer.setSurfaceEx(mSurfaceHelper.getSecondSurfaceTexture(), 1);
        }
        if (mDeviceInfo.getIsEncrypt() == 1) {
            mPlaybackPlayer.setPlayVerifyCode(DataManager.getInstance().getDeviceSerialVerifyCode(mCameraInfo.getDeviceSerial()));
        }
    }

    /**
     * 根据录像类别开始回放
     * 调用EZPlayer.startPlayback接口开始回放（验证码输入后调用）
     * call EZPlayer.startPlayback to start playback
     */
    public void startPlaybackByRecordCategory() {
        if (mPlaybackPlayer == null) {
            return;
        }
        if (mDeviceRecordInfo != null) {
            // 加载SD卡回放特殊配置，一般开发者可不用配置，按需设置
            EZStreamConfigLoader.loadPlaybackSpecialConfigToPlayer(mPlaybackPlayer);
            mPlaybackPlayer.startPlayback(mDeviceRecordInfo);
        } else if (mCloudRecordInfo != null) {
            mPlaybackPlayer.startPlayback(mCloudRecordInfo);
        }
    }

    private void closePlayBack() {
        if (mStatus == RemoteListContant.STATUS_EXIT_PAGE) {
            return;
        }
        LogUtil.d(TAG, "停止运行.........");
        mPlaybackPlayer.stopPlayback();
        mUpdateTimerHelper.stopUpdateTimer();
        mStatus = RemoteListContant.STATUS_EXIT_PAGE;
    }

    /**
     * 统一更新声音状态（按钮UI + 播放器声音开关）
     *
     * @param soundOpen true-打开声音，false-关闭声音
     */
    private void updateSoundState(boolean soundOpen) {
        if (soundOpen) {
            soundBtn.setImageResource(R.drawable.ez_sound_on_selector);
            if (mPlaybackPlayer != null) {
                mPlaybackPlayer.openSound();
            }
        } else {
            soundBtn.setImageResource(R.drawable.ez_sound_off_selector);
            if (mPlaybackPlayer != null) {
                mPlaybackPlayer.closeSound();
            }
        }
    }

    // 页面可见
    private void onActivityResume() {
        if (!mRecordDateHelper.isDateSelected && currentClickItemFile != null) {
            if (mStatus == RemoteListContant.STATUS_EXIT_PAGE || mStatus == RemoteListContant.STATUS_DECRYPT) {
                newPlayInit();
                setPlaybackProgressUI(currentClickItemFile.getBeginTime(), currentClickItemFile.getEndTime());
                startPlaybackByRecordCategory();
            }
        }
    }

    /**
     * 数据初始化
     */
    private void initData() {
        localInfo = LocalInfo.getInstance();
        mHandler = new Handler(this);
        Bundle bundle = getIntent().getExtras();
        if (bundle != null) {
            queryDate = (Date) bundle.getSerializable(RemoteListContant.QUERY_DATE_INTENT_KEY);
            mCameraInfo = getIntent().getParcelableExtra(IntentConsts.EXTRA_CAMERA_INFO);
            mDeviceInfo = getIntent().getParcelableExtra(IntentConsts.EXTRA_DEVICE_INFO);
            isMultiChannelDevice = EZBusinessTool.isSupportMultiChannel(mDeviceInfo, mCameraInfo);
        }
        Application application = getApplication();
        DisplayMetrics metric = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(metric);
        localInfo.setScreenWidthHeight(metric.widthPixels, metric.heightPixels);
        localInfo.setNavigationBarHeight((int) Math.ceil(25 * getResources().getDisplayMetrics().density));
        if (mCameraInfo == null) {
            LogUtil.d(TAG, "cameraInfo is null");
            finish();
        }
    }

    /**
     * 暴露 TaskManager 供 Helper 使用
     */
    public void submitTask(Runnable task) {
        getTaskManager().submit(task);
    }

    /**
     * 供 Helper 调用的 dismissPopWindow 包装（父类 protected 方法对外暴露）
     */
    public void dismissPopWindowPublic(android.widget.PopupWindow popupWindow) {
        dismissPopWindow(popupWindow);
    }

    // endregion

    // region --- UI ---

    public void initView() {
        mCloudVideoActiveImg = (ImageView) findViewById(R.id.img_active_cloud_video);
        mCloudVideoActiveImg.setOnClickListener(v -> onCloudAdImageClick());// 云存储图片事件
        remoteListPage = (ViewGroup) findViewById(R.id.remote_list_page);
        /** 测量状态栏高度 Measure the mStatus bar height**/
        ViewTreeObserver viewTreeObserver = remoteListPage.getViewTreeObserver();
        viewTreeObserver.addOnGlobalLayoutListener(() -> {
            if (mRemotePlayBackRect == null) {
                // 获取状况栏高度
                mRemotePlayBackRect = new Rect();
                getWindow().getDecorView().getWindowVisibleDisplayFrame(mRemotePlayBackRect);
            }
        });
        mPlaybackPlayRl = (RelativeLayout) findViewById(R.id.playback_play_rl);
        duringTimeTV = (TextView) findViewById(R.id.during_time_tv);
        progressSeekbar = (SeekBar) findViewById(R.id.progress_seekbar);
        progressSeekbar.setMax(RemoteListContant.PROGRESS_MAX_VALUE);
        progressSeekbar.setOnSeekBarChangeListener(mSeekBarChangeListener);// 回放进度条拖动事件
        playTimeTV = (TextView) findViewById(R.id.play_time_tv);
        if (GlobalKit.isJCTest) {
            playTimeTV.setVisibility(View.VISIBLE);
        }
        controlArea = (LinearLayout) findViewById(R.id.control_area);
        progressArea = (LinearLayout) findViewById(R.id.progress_area);
        pauseBtn = (ImageButton) findViewById(R.id.remote_playback_pause_btn);
        pauseBtn.setOnClickListener(v -> onPlayPauseClick());// 暂停播放按钮事件
        soundBtn = (ImageButton) findViewById(R.id.remote_playback_sound_btn);
        soundBtn.setOnClickListener(v -> onSoundBtnClick());// 声音按钮事件
        streamTypeTv = (TextView) findViewById(R.id.tv_stream_type);
        notPause = true;
        mControlBarRL = (ViewGroup) findViewById(R.id.control_bar);
    }

    /**
     * 初始化各功能 Helper
     */
    private void initHelpers() {
        mTitleBarHelper = new TitleBarHelper(this);// 导航栏Helper初始化
        mOrientationHelper = new OrientationHelper(this);// 屏幕旋转Helper初始化
        if (isMultiChannelDevice) {// 双目设备调整画面比例
            mOrientationHelper.setMultiChannelRatio();
        }
        // SurfaceHelper 必须在 OrientationHelper 之后初始化，因为双目设备时 SurfaceHelper.initView() 会调用
        // mOrientationHelper.hideFullscreenButton() 隐藏全屏按钮
        mSurfaceHelper = new SurfaceHelper(this);// Surface生命周期Helper初始化
        mOrientationHelper.setRemoteListSvLayout();
        mLoadingStateHelper = new LoadingStateHelper(this);// 加载状态Helper初始化
        mRecordQueryHelper = new RecordQueryHelper(this);// 录像查询Helper初始化
        mUpdateTimerHelper = new UpdateTimerHelper(this);// 定时器管理Helper初始化
        mPlaybackRateHelper = new PlaybackRateHelper(this);// 倍速控制Helper初始化
        mDownloadHelper = new DownloadHelper(this);// 录像下载Helper初始化
        mAICloudStorageHelper = new AICloudStorageHelper(this);// AI云存储Helper初始化
        mRecordDateHelper = new RecordDateHelper(this);// 日期选择Helper初始化
        mRecordCoverHelper = new RecordCoverHelper(this);// SD卡录像封面Helper初始化
    }

    /**
     * 停止当前回放并重置播放UI
     * stop current playback and reset play UI
     */
    public void stopCurrentPlayback() {
        mUpdateTimerHelper.stopUpdateTimer();
        mPlaybackPlayer.stopPlayback();
        mStatus = RemoteListContant.STATUS_STOP;
        newPlayInit();
    }

    /**
     * 开始播放前，UI重置
     */
    private void newPlayInit() {
        mLoadingStateHelper.setStartLoading();
        mPlaybackRateHelper.resetRateText();
        resetPauseBtnUI();// 暂停按钮初始化
        progressSeekbar.setProgress(0);
    }

    /**
     * 录像播放进度UI初始化（已播放时长、总时长、SeekBar进度）
     */
    public void setPlaybackProgressUI(long beginTime, long endTime) {
        int diffSeconds = (int) (endTime - beginTime) / 1000;
        String convToUIDuration = RemoteListUtil.convToUIDuration(diffSeconds);
        duringTimeTV.setText(convToUIDuration);
        playTimeTV.setText(RemoteListContant.VIDEO_DUAR_BEGIN_INIT);
        progressSeekbar.setProgress(0);
    }

    /**
     * 设置回放控制按钮（暂停/播放、声音、下载、倍数）的可用状态
     * @param enabled true-可点击，false-不可点击（显示禁用态图标）
     */
    private void setPlaybackControlButtonsEnabled(boolean enabled) {
        pauseBtn.setEnabled(enabled);
        soundBtn.setEnabled(enabled);
        mDownloadHelper.setDownloadBtnEnabled(enabled);
        mPlaybackRateHelper.setPlaybackRateBtnEnabled(enabled);
    }

    /**
     * 设置取流方式
     */
    private void showStreamFetchType() {
        int type = mPlaybackPlayer.getStreamFetchType();
        LogUtil.i(TAG, "getStreamFetchType: " + type);
        String streamTypeMsg = getApplicationContext().getString(R.string.stream_type) + EZBusinessTool.getStreamType(type);
        streamTypeTv.setText(streamTypeMsg);
        streamTypeTv.setVisibility(View.VISIBLE);
    }

    /**
     * 回放进度条拖动后，UI重置
     */
    private void seekInit() {
        mLoadingStateHelper.resetSeekPlayUI();
        resetPauseBtnUI();
    }

    /**
     * 暂停按钮 UI和状态值重置
     */
    private void resetPauseBtnUI() {
        notPause = true;
        pauseBtn.setImageResource(R.drawable.ez_pause_selector);
    }

    /**
     * 重试按钮点击回调（供 LoadingStateHelper 调用）
     */
    public void onRetryPlayback() {
        newPlayInit();
        setPlaybackProgressUI(currentClickItemFile.getBeginTime(), currentClickItemFile.getEndTime());
        startPlaybackByRecordCategory();
    }

    // endregion

    // region --- 动态权限申请|Dynamic permission application ---

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        EZPermissionUtil.handleResult(requestCode, permissions, grantResults);
    }

    // endregion

}
