diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0d90cce768..e0aa3ad29f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -82,7 +82,7 @@ diff --git a/app/src/main/java/com/gh/base/fragment/BaseLazyFragment.kt b/app/src/main/java/com/gh/base/fragment/BaseLazyFragment.kt new file mode 100644 index 0000000000..2aeb8df957 --- /dev/null +++ b/app/src/main/java/com/gh/base/fragment/BaseLazyFragment.kt @@ -0,0 +1,163 @@ +package com.gh.base.fragment + +import android.os.Bundle +import com.gh.gamecenter.normal.NormalFragment + +/** + * 懒加载(支持多层嵌套) + */ +abstract class BaseLazyFragment : NormalFragment() { + + private var mIsFirstVisible = true + + private var isViewCreated = false + + private var isSupportVisible = false + + /** + * 用于分发可见时间的时候父获取 fragment 是否隐藏 + * + * @return true fragment 不可见, false 父 fragment 可见 + */ + private val isParentInvisible: Boolean + get() { + val parentFragment = parentFragment + return if (parentFragment is BaseLazyFragment) { + val fragment = parentFragment as BaseLazyFragment? + !fragment!!.isSupportVisible + } else { + false + } + } + + override fun setUserVisibleHint(isVisibleToUser: Boolean) { + super.setUserVisibleHint(isVisibleToUser) + // 对于默认 tab 和 间隔 checked tab 需要等到 isViewCreated = true 后才可以通过此通知用户可见 + // 这种情况下第一次可见不是在这里通知 因为 isViewCreated = false 成立,等从别的界面回到这里后会使用 onFragmentResume 通知可见 + // 对于非默认 tab mIsFirstVisible = true 会一直保持到选择则这个 tab 的时候,因为在 onActivityCreated 会返回 false + if (isViewCreated) { + if (isVisibleToUser && !isSupportVisible) { + dispatchUserVisibleHint(true) + } else if (!isVisibleToUser && isSupportVisible) { + dispatchUserVisibleHint(false) + } + } + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + + isViewCreated = true + // !isHidden() 默认为 true 在调用 hide show 的时候可以使用 + if (!isHidden && userVisibleHint) { + dispatchUserVisibleHint(true) + } + + } + + override fun onHiddenChanged(hidden: Boolean) { + super.onHiddenChanged(hidden) + + if (hidden) { + dispatchUserVisibleHint(false) + } else { + dispatchUserVisibleHint(true) + } + } + + override fun onResume() { + super.onResume() + if (!mIsFirstVisible) { + if (!isHidden && !isSupportVisible && userVisibleHint) { + dispatchUserVisibleHint(true) + } + } + } + + override fun onPause() { + super.onPause() + // 当前 Fragment 包含子 Fragment 的时候 dispatchUserVisibleHint 内部本身就会通知子 Fragment 不可见 + // 子 fragment 走到这里的时候自身又会调用一遍 ? + if (isSupportVisible && userVisibleHint) { + dispatchUserVisibleHint(false) + } + } + + + /** + * 统一处理 显示隐藏 + * + * @param visible + */ + private fun dispatchUserVisibleHint(visible: Boolean) { + //当前 Fragment 是 child 时候 作为缓存 Fragment 的子 fragment getUserVisibleHint = true + //但当父 fragment 不可见所以 currentVisibleState = false 直接 return 掉 + // 这里限制则可以限制多层嵌套的时候子 Fragment 的分发 + if (visible && isParentInvisible) return + + //此处是对子 Fragment 不可见的限制,因为 子 Fragment 先于父 Fragment回调本方法 currentVisibleState 置位 false + // 当父 dispatchChildVisibleState 的时候第二次回调本方法 visible = false 所以此处 visible 将直接返回 + if (isSupportVisible == visible) { + return + } + + isSupportVisible = visible + + if (visible) { + if (mIsFirstVisible) { + mIsFirstVisible = false + onFragmentFirstVisible() + } + onFragmentResume() + dispatchChildVisibleState(true) + } else { + dispatchChildVisibleState(false) + onFragmentPause() + } + } + + /** + * 当前 Fragment 是 child 时候 作为缓存 Fragment 的子 fragment 的唯一或者嵌套 VP 的第一 fragment 时 getUserVisibleHint = true + * 但是由于父 Fragment 还未进入可见状态所以自身也是不可见的, 这个方法可以存在是因为庆幸的是 父 fragment 的生命周期回调总是先于子 Fragment + * 所以在父 fragment 设置完成当前不可见状态后,需要通知子 Fragment 我不可见,你也不可见, + * + * + * 因为 dispatchUserVisibleHint 中判断了 isParentInvisible 所以当 子 fragment 走到了 onActivityCreated 的时候直接 return 掉了 + * + * + * 当真正的外部 Fragment 可见的时候,走 setVisibleHint (VP 中)或者 onActivityCreated (hide show) 的时候 + * 从对应的生命周期入口调用 dispatchChildVisibleState 通知子 Fragment 可见状态 + * + * @param visible + */ + private fun dispatchChildVisibleState(visible: Boolean) { + val childFragmentManager = childFragmentManager + val fragments = childFragmentManager.fragments + if (!fragments.isEmpty()) { + for (child in fragments) { + if (child is BaseLazyFragment && !child.isHidden() && child.getUserVisibleHint()) { + child.dispatchUserVisibleHint(visible) + } + } + } + } + + open fun onFragmentFirstVisible() { + //ULog.e("对用户第一次可见") + } + + open fun onFragmentResume() { + //ULog.e("对用户可见") + } + + open fun onFragmentPause() { + //ULog.e("对用户不可见") + } + + override fun onDestroyView() { + super.onDestroyView() + isViewCreated = false + mIsFirstVisible = true + } + +} diff --git a/app/src/main/java/com/gh/common/history/HistoryDatabase.kt b/app/src/main/java/com/gh/common/history/HistoryDatabase.kt index dbe19bcbc7..f4b515807c 100644 --- a/app/src/main/java/com/gh/common/history/HistoryDatabase.kt +++ b/app/src/main/java/com/gh/common/history/HistoryDatabase.kt @@ -49,7 +49,7 @@ abstract class HistoryDatabase : RoomDatabase() { } val MIGRATION_4_5:Migration=object : Migration(4, 5) { override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("CREATE TABLE MyVideoEntity(id TEXT NOT NULL PRIMARY KEY,poster TEXT NOT NULL DEFAULT '',url TEXT NOT NULL DEFAULT '',vote INTEGER NOT NULL DEFAULT 0,length INTEGER NOT NULL DEFAULT 0,time INTEGER NOT NULL DEFAULT 0)") + database.execSQL("CREATE TABLE MyVideoEntity(id TEXT NOT NULL PRIMARY KEY,poster TEXT NOT NULL DEFAULT '',url TEXT NOT NULL DEFAULT '',vote INTEGER NOT NULL DEFAULT 0,length INTEGER NOT NULL DEFAULT 0,time INTEGER NOT NULL DEFAULT 0,videoStreamRecord INTEGER NOT NULL DEFAULT 0)") } } diff --git a/app/src/main/java/com/gh/common/util/EntranceUtils.java b/app/src/main/java/com/gh/common/util/EntranceUtils.java index 7439491812..d5e0243e33 100644 --- a/app/src/main/java/com/gh/common/util/EntranceUtils.java +++ b/app/src/main/java/com/gh/common/util/EntranceUtils.java @@ -135,6 +135,8 @@ public class EntranceUtils { public static final String KEY_IMAGE_CROP_RATIO = "imageCropRatio"; public static final String KEY_OPEN_VIDEO_STREAMING = "openVideoStreaming"; public static final String KEY_REFERER = "referer"; + public static final String KEY_UUID = "uuid"; + public static final String KEY_IS_HOME_VIDEO = "isHomeVideo"; public static void jumpActivity(Context context, Bundle bundle) { diff --git a/app/src/main/java/com/gh/common/util/Extensions.kt b/app/src/main/java/com/gh/common/util/Extensions.kt index 0e8ceccc3b..a20e40dac1 100644 --- a/app/src/main/java/com/gh/common/util/Extensions.kt +++ b/app/src/main/java/com/gh/common/util/Extensions.kt @@ -267,6 +267,10 @@ fun Map.createRequestBody(): RequestBody { val json = GsonUtils.toJson(this) return RequestBody.create(MediaType.parse("application/json"), json) } +fun Map.createRequestBodyAny(): RequestBody { + val json = GsonUtils.toJson(this) + return RequestBody.create(MediaType.parse("application/json"), json) +} // 对在浏览器(WebView)显示的路径进行转码 fun String.decodeURI(): String { diff --git a/app/src/main/java/com/gh/gamecenter/MainActivity.java b/app/src/main/java/com/gh/gamecenter/MainActivity.java index 9e0f52c329..99aa4c4ff8 100644 --- a/app/src/main/java/com/gh/gamecenter/MainActivity.java +++ b/app/src/main/java/com/gh/gamecenter/MainActivity.java @@ -124,6 +124,7 @@ import java.util.Map; import java.util.Set; import java.util.Timer; import java.util.TimerTask; +import java.util.UUID; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -164,6 +165,7 @@ public class MainActivity extends BaseActivity { private boolean isSkipped = false; public static boolean isNewFirstLaunch; + public String uuid= UUID.randomUUID().toString(); private Handler handler = new Handler(); // 黄壮华 添加观察者 修改2015/8/15 @@ -616,13 +618,15 @@ public class MainActivity extends BaseActivity { this::finish); return true; } - System.arraycopy(mHits, 1, mHits, 0, mHits.length - 1); - mHits[mHits.length - 1] = SystemClock.uptimeMillis(); - if (mHits[0] >= (SystemClock.uptimeMillis() - 1000)) { - finish(); - } else { - toast("再按一次就退出光环助手了哦"); - return true; + if(!mMainWrapperFragment.onHandleBackPressed()) { + System.arraycopy(mHits, 1, mHits, 0, mHits.length - 1); + mHits[mHits.length - 1] = SystemClock.uptimeMillis(); + if (mHits[0] >= (SystemClock.uptimeMillis() - 1000)) { + finish(); + } else { + toast("再按一次就退出光环助手了哦"); + return true; + } } } return super.onKeyDown(keyCode, event); diff --git a/app/src/main/java/com/gh/gamecenter/entity/MyVideoEntity.kt b/app/src/main/java/com/gh/gamecenter/entity/MyVideoEntity.kt index cc05c4d8ed..7ffb2e24d1 100644 --- a/app/src/main/java/com/gh/gamecenter/entity/MyVideoEntity.kt +++ b/app/src/main/java/com/gh/gamecenter/entity/MyVideoEntity.kt @@ -17,5 +17,6 @@ class MyVideoEntity( var vote: Int = 0, var length: Long = 0, - var time: Long = 0//浏览时间 + var time: Long = 0,//浏览时间 + var videoStreamRecord: Int = 0//是否在视频流中记录的,1是 0不是 ) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/fragment/MainWrapperFragment.java b/app/src/main/java/com/gh/gamecenter/fragment/MainWrapperFragment.java index d9b4c1d1b6..a6ed6efce8 100644 --- a/app/src/main/java/com/gh/gamecenter/fragment/MainWrapperFragment.java +++ b/app/src/main/java/com/gh/gamecenter/fragment/MainWrapperFragment.java @@ -2,14 +2,17 @@ package com.gh.gamecenter.fragment; import android.os.Bundle; import android.view.View; +import android.view.ViewGroup; import com.facebook.drawee.view.SimpleDraweeView; import com.gh.base.OnDoubleTapListener; import com.gh.base.fragment.BaseFragment_ViewPager_Checkable; import com.gh.common.constant.Config; import com.gh.common.util.DataUtils; +import com.gh.common.util.EntranceUtils; import com.gh.common.util.ImageUtils; import com.gh.download.DownloadManager; +import com.gh.gamecenter.MainActivity; import com.gh.gamecenter.R; import com.gh.gamecenter.db.GameTrendsDao; import com.gh.gamecenter.db.info.GameTrendsInfo; @@ -26,6 +29,9 @@ import com.gh.gamecenter.home.HomeFragment; import com.gh.gamecenter.manager.UserManager; import com.gh.gamecenter.personal.PersonalFragment; import com.gh.gamecenter.qa.CommunityFragment; +import com.gh.gamecenter.video.detail.VideoDetailContainerFragment; +import com.gh.gamecenter.video.detail.VideoDetailContainerViewModel; +import com.lightgame.listeners.OnBackPressedListener; import com.lightgame.view.NoScrollableViewPager; import org.greenrobot.eventbus.EventBus; @@ -48,7 +54,7 @@ import static com.gh.gamecenter.MainActivity.EB_SKIP_MAIN; * @Time 3:26 PM */ -public class MainWrapperFragment extends BaseFragment_ViewPager_Checkable { +public class MainWrapperFragment extends BaseFragment_ViewPager_Checkable implements OnBackPressedListener { @BindView(R.id.main_tab_game) View mMainTab; @@ -59,8 +65,8 @@ public class MainWrapperFragment extends BaseFragment_ViewPager_Checkable { @BindView(R.id.main_tab_game_icon) SimpleDraweeView mTabGameIcon; - @BindView(R.id.iv_discovery_hint_dot) - protected View mDiscoveryHintIv; +// @BindView(R.id.iv_discovery_hint_dot) +// protected View mDiscoveryHintIv; public static final int INDEX_HOME = 0; public static final int INDEX_GAME = 1; @@ -75,6 +81,7 @@ public class MainWrapperFragment extends BaseFragment_ViewPager_Checkable { private MainWrapperViewModel mViewModel; private GameTrendsDao mGameTrendsDao; + private VideoDetailContainerFragment videoDetailContainerFragment; @Override protected int getLayoutId() { @@ -105,8 +112,16 @@ public class MainWrapperFragment extends BaseFragment_ViewPager_Checkable { mGameWrapperFragment.setArguments(homeArgs); fragments.add(mGameWrapperFragment); + videoDetailContainerFragment = new VideoDetailContainerFragment(); + Bundle videoArgs = new Bundle(); + videoArgs.putString(EntranceUtils.KEY_LOCATION, VideoDetailContainerViewModel.Location.VIDEO_CHOICENESS.getValue()); + videoArgs.putString(EntranceUtils.KEY_UUID, ((MainActivity) requireContext()).uuid); + videoArgs.putBoolean(EntranceUtils.KEY_IS_HOME_VIDEO, true); + videoDetailContainerFragment.setArguments(videoArgs); + fragments.add(videoDetailContainerFragment); + fragments.add(new CommunityFragment()); - fragments.add(new DiscoverFragment()); + //fragments.add(new DiscoverFragment()); fragments.add(new PersonalFragment()); } @@ -188,6 +203,25 @@ public class MainWrapperFragment extends BaseFragment_ViewPager_Checkable { } } + @Override + protected boolean handleOnClick(View view) { + final int toCheck = mCheckableGroup.indexOfChild(view); + if (toCheck == 2) { + changeTabBarVisibility(false); + } else { + changeTabBarVisibility(true); + } + return super.handleOnClick(view); + } + + private void changeTabBarVisibility(boolean isShowIcon) { + int childCount = mCheckableGroup.getChildCount(); + for (int i = 0; i < childCount; i++) { + ViewGroup childAt = (ViewGroup) mCheckableGroup.getChildAt(i); + childAt.getChildAt(0).setVisibility(isShowIcon ? View.VISIBLE : View.GONE); + } + } + @Override protected void onPageChanged(int index) { super.onPageChanged(index); @@ -241,7 +275,7 @@ public class MainWrapperFragment extends BaseFragment_ViewPager_Checkable { if (mMessageHintIv != null) { mMessageHintIv.setVisibility(View.VISIBLE); } - } else if (DiscoverFragment.SHOW_DISCOVERY_DOT.equals(reuse.getType())) { + }/* else if (DiscoverFragment.SHOW_DISCOVERY_DOT.equals(reuse.getType())) { if (mDiscoveryHintIv != null) { mDiscoveryHintIv.setVisibility(View.VISIBLE); } @@ -257,7 +291,7 @@ public class MainWrapperFragment extends BaseFragment_ViewPager_Checkable { if (mDiscoveryHintIv != null) { mViewModel.getDiscoveryData(true); } - } else if ("Refresh".equals(reuse.getType())) { + } */ else if ("Refresh".equals(reuse.getType())) { SettingsEntity settings = Config.getSettings(); if (settings != null && !settings.showCommunityEntrance()) { mTabCommunity.setVisibility(View.GONE); @@ -270,9 +304,9 @@ public class MainWrapperFragment extends BaseFragment_ViewPager_Checkable { // 关注事件 @Subscribe(threadMode = ThreadMode.MAIN) public void onEventMainThread(EBConcernChanged changed) { - if (mDiscoveryHintIv != null) { + /*if (mDiscoveryHintIv != null) { mViewModel.getDiscoveryData(true); - } + }*/ } public void setCurrentItem(int page) { @@ -282,4 +316,12 @@ public class MainWrapperFragment extends BaseFragment_ViewPager_Checkable { public int getCurrentItem() { return mViewPager.getCurrentItem(); } + + @Override + public boolean onHandleBackPressed() { + if (videoDetailContainerFragment != null) { + return videoDetailContainerFragment.onHandleBackPressed(); + } + return false; + } } diff --git a/app/src/main/java/com/gh/gamecenter/retrofit/service/ApiService.java b/app/src/main/java/com/gh/gamecenter/retrofit/service/ApiService.java index d7efa24c40..34c27ed944 100644 --- a/app/src/main/java/com/gh/gamecenter/retrofit/service/ApiService.java +++ b/app/src/main/java/com/gh/gamecenter/retrofit/service/ApiService.java @@ -1881,6 +1881,12 @@ public interface ApiService { @GET("videos/{video_id}") Single> getVideoDetailList(@Path("video_id") String videoId, @Query("filter") String filter); + /** + * 视频流播放算法 + */ + @POST("videos/stream?page_size=20") + Single> getVideoStream(@Query("type") String type, @Body RequestBody cacheVideoIds, @Query("video_id") String videoId, @Query("cache_id") String cacheId, @Query("page") int page); + /** * 点赞视频 */ diff --git a/app/src/main/java/com/gh/gamecenter/room/dao/VideoHistoryDao.kt b/app/src/main/java/com/gh/gamecenter/room/dao/VideoHistoryDao.kt index c6127c56c5..cc4bd18c8c 100644 --- a/app/src/main/java/com/gh/gamecenter/room/dao/VideoHistoryDao.kt +++ b/app/src/main/java/com/gh/gamecenter/room/dao/VideoHistoryDao.kt @@ -12,6 +12,9 @@ interface VideoHistoryDao { @Query("select * from MyVideoEntity order by time desc limit :pageSize offset :offset ") fun getVideoWithOffset(pageSize: Int, offset: Int): Single> + @Query("select * from MyVideoEntity where videoStreamRecord=1 order by time desc limit :pageSize offset :offset ") + fun getVideoStreamRecord(pageSize: Int, offset: Int): Single> + @Delete fun deleteVideo(video: MyVideoEntity) } \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/video/detail/CustomManager.java b/app/src/main/java/com/gh/gamecenter/video/detail/CustomManager.java index 6203a7678a..4cb3c58cf6 100644 --- a/app/src/main/java/com/gh/gamecenter/video/detail/CustomManager.java +++ b/app/src/main/java/com/gh/gamecenter/video/detail/CustomManager.java @@ -57,7 +57,7 @@ public class CustomManager extends GSYVideoBaseManager { /** * 暂停播放 */ - public void onPause(String key) { + public static void onPause(String key) { if (getCustomManager(key).listener() != null) { getCustomManager(key).listener().onVideoPause(); } @@ -66,7 +66,7 @@ public class CustomManager extends GSYVideoBaseManager { /** * 恢复播放 */ - public void onResume(String key) { + public static void onResume(String key) { if (getCustomManager(key).listener() != null) { getCustomManager(key).listener().onVideoResume(); } @@ -110,7 +110,7 @@ public class CustomManager extends GSYVideoBaseManager { public static void onPauseAll() { if (sMap.size() > 0) { for (Map.Entry header : sMap.entrySet()) { - header.getValue().onPause(header.getKey()); + onPause(header.getKey()); } } } @@ -119,6 +119,7 @@ public class CustomManager extends GSYVideoBaseManager { if (sMap.size() > 0) { for (Map.Entry header : sMap.entrySet()) { header.getValue().onResume(header.getKey()); + onResume(header.getKey()); } } } diff --git a/app/src/main/java/com/gh/gamecenter/video/detail/DetailPlayerView.kt b/app/src/main/java/com/gh/gamecenter/video/detail/DetailPlayerView.kt index 5683470bfb..933b87c210 100644 --- a/app/src/main/java/com/gh/gamecenter/video/detail/DetailPlayerView.kt +++ b/app/src/main/java/com/gh/gamecenter/video/detail/DetailPlayerView.kt @@ -14,6 +14,7 @@ import com.gh.common.runOnIoThread import com.gh.common.util.* import com.gh.download.cache.CacheManager import com.gh.gamecenter.GameDetailActivity +import com.gh.gamecenter.MainActivity import com.gh.gamecenter.R import com.gh.gamecenter.entity.VideoEntity import com.gh.gamecenter.manager.UserManager @@ -60,8 +61,8 @@ class DetailPlayerView @JvmOverloads constructor(context: Context, attrs: Attrib } else { hideBackBtn() } - unMute() - volume.setOnClickListener { toggleMute() } +// unMute() +// volume.setOnClickListener { toggleMute() } } override fun getLayoutId(): Int { @@ -70,6 +71,8 @@ class DetailPlayerView @JvmOverloads constructor(context: Context, attrs: Attrib fun setViewModel(viewModel: VideoDetailContainerViewModel) { mViewModel = viewModel + unMute() + volume.setOnClickListener { toggleMute() } } @SuppressLint("CheckResult") @@ -170,7 +173,7 @@ class DetailPlayerView @JvmOverloads constructor(context: Context, attrs: Attrib uploadVideoStreamingPlaying("打开评论弹窗") when (videoEntity.status) { "pass" -> { - (context as VideoDetailActivity).isPauseVideo = false + (context as? VideoDetailActivity)?.isPauseVideo = false val intent = CommentActivity.getVideoCommentIntent(context, videoEntity.id, videoEntity.commentCount, "", false) (context as Activity).startActivityForResult(intent, CommentActivity.REQUEST_CODE) } @@ -321,8 +324,8 @@ class DetailPlayerView @JvmOverloads constructor(context: Context, attrs: Attrib setViewShowState(mBottomProgressBar, View.VISIBLE) setViewShowState(mBottomContainer, View.GONE) } else { - setViewShowState(mBottomProgressBar, View.GONE) - setViewShowState(mBottomContainer, View.VISIBLE) + setViewShowState(mBottomContainer, if (isBottomContainerShow) View.VISIBLE else View.GONE) + setViewShowState(mBottomProgressBar, if (isBottomContainerShow) View.GONE else View.VISIBLE) } } @@ -489,9 +492,16 @@ class DetailPlayerView @JvmOverloads constructor(context: Context, attrs: Attrib } override fun getGSYVideoManager(): GSYVideoViewBridge { - val uuid = (context as VideoDetailActivity).uuid - CustomManager.getCustomManager("detail_$uuid").initContext(context.applicationContext) - return CustomManager.getCustomManager("detail_$uuid") + CustomManager.getCustomManager(getKey()).initContext(context.applicationContext) + return CustomManager.getCustomManager(getKey()) + } + + private fun getKey(): String { + return if (context is MainActivity) { + "detail_${(context as MainActivity).uuid}" + } else { + "detail_${(context as VideoDetailActivity).uuid}" + } } override fun updateStartImage() { @@ -594,7 +604,7 @@ class DetailPlayerView @JvmOverloads constructor(context: Context, attrs: Attrib mContentLength = CacheManager.getInstance().getContentLength(mVideoEntity!!.url) / 1024.0 / 1024.0 } LogUtils.uploadVideoStreamingPlaying(action, msg, mViewModel!!.path, - mViewModel!!.entranceDetail, mVideoEntity!!.id, (context as VideoDetailActivity).uuid.toString(), mContentLength, + mViewModel!!.entranceDetail, mVideoEntity!!.id, mViewModel!!.uuid, mContentLength, duration / 1000, currentPositionWhenPlaying / 1000, videoPlayStatus()) } } @@ -607,7 +617,7 @@ class DetailPlayerView @JvmOverloads constructor(context: Context, attrs: Attrib } } - fun getGameTitleY():Int{ + fun getGameTitleY(): Int { val location = IntArray(2) gameName.getLocationInWindow(location) return location[1] diff --git a/app/src/main/java/com/gh/gamecenter/video/detail/VideoAdapter.kt b/app/src/main/java/com/gh/gamecenter/video/detail/VideoAdapter.kt index 384ef62fcb..8ff37b9365 100644 --- a/app/src/main/java/com/gh/gamecenter/video/detail/VideoAdapter.kt +++ b/app/src/main/java/com/gh/gamecenter/video/detail/VideoAdapter.kt @@ -2,13 +2,16 @@ package com.gh.gamecenter.video.detail import android.app.Activity import android.content.Context +import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import com.gh.common.constant.Constants import com.gh.common.util.LogUtils import com.gh.common.util.MtaHelper +import com.gh.common.util.goneIf import com.gh.download.cache.CacheManager import com.gh.download.cache.CacheObserver +import com.gh.gamecenter.R import com.gh.gamecenter.entity.VideoEntity import com.halo.assistant.HaloApp import com.shuyu.gsyvideoplayer.builder.GSYVideoOptionBuilder @@ -30,10 +33,10 @@ class VideoAdapter(val mContext: Context, val mViewModel: VideoDetailContainerVi override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { val videoView = (holder.itemView as DetailPlayerView) - + videoView.findViewById(R.id.placeholderView)?.goneIf(!mViewModel.mIsHomeVideo) val mOrientationUtils = OrientationUtils(mContext as Activity, videoView) mOrientationUtils.isEnable = false - + videoView.setViewModel(mViewModel) GSYVideoOptionBuilder() .setIsTouchWiget(false) .setUrl(videoList[position].url) @@ -47,8 +50,7 @@ class VideoAdapter(val mContext: Context, val mViewModel: VideoDetailContainerVi .setVideoAllCallBack(object : GSYSampleCallBack() { override fun onPrepared(url: String?, vararg objects: Any?) { super.onPrepared(url, *objects) - val uuid = (mContext as VideoDetailActivity).uuid - val duration = CustomManager.getCustomManager("detail_$uuid").duration + val duration = CustomManager.getCustomManager("detail_${mViewModel.uuid}").duration videoView.setIsBottomContainerShow(duration > 30 * 1000) } @@ -61,7 +63,6 @@ class VideoAdapter(val mContext: Context, val mViewModel: VideoDetailContainerVi }) .build(videoView) - videoView.setViewModel(mViewModel) videoView.updateViewDetail(videoList[position]) videoView.updateThumb(videoList[position].getThumb()) videoView.updateMuteStatus() @@ -74,6 +75,7 @@ class VideoAdapter(val mContext: Context, val mViewModel: VideoDetailContainerVi mOrientationUtils.resolveByClick() val fullVideoPlayer = videoView.startWindowFullscreen(mContext, true, true) as DetailPlayerView fullVideoPlayer.setIsBottomContainerShow(videoView.isBottomContainerShow) + fullVideoPlayer.setViewModel(mViewModel) fullVideoPlayer.hideAllButton(true) fullVideoPlayer.updateViewDetail(videoList[position]) fullVideoPlayer.updateMuteStatus() @@ -89,9 +91,8 @@ class VideoAdapter(val mContext: Context, val mViewModel: VideoDetailContainerVi CacheManager.getInstance().download(videoList[position + 1].url, object : CacheObserver() {}) } videoView.startButton.performClick() - val videoDetailActivity = mContext as VideoDetailActivity LogUtils.uploadVideoStreamingPlaying("开始播放-入口进入", "", mViewModel.path, - mViewModel.entranceDetail, videoList[position].id, videoDetailActivity.uuid.toString(), 0.0, 0, 0, "play") + mViewModel.entranceDetail, videoList[position].id, mViewModel.uuid, 0.0, 0, 0, "play") mViewModel.addHistoryRecord(videoList[position]) } } @@ -100,8 +101,7 @@ class VideoAdapter(val mContext: Context, val mViewModel: VideoDetailContainerVi if (videoView?.getCurrentIsFullscreen() == true) { videoView.getVideoAllCallBack().onQuitFullscreen("") } - val uuid = (mContext as VideoDetailActivity).uuid - if (CustomManager.backFromWindowFull(mContext, "detail_$uuid")) { + if (CustomManager.backFromWindowFull(mContext, "detail_${mViewModel.uuid}")) { return true } return false diff --git a/app/src/main/java/com/gh/gamecenter/video/detail/VideoDetailActivity.kt b/app/src/main/java/com/gh/gamecenter/video/detail/VideoDetailActivity.kt index c9cd7470b3..a61c3c6f4d 100644 --- a/app/src/main/java/com/gh/gamecenter/video/detail/VideoDetailActivity.kt +++ b/app/src/main/java/com/gh/gamecenter/video/detail/VideoDetailActivity.kt @@ -31,7 +31,9 @@ class VideoDetailActivity : BaseActivity() { IjkPlayerManager.setLogLevel(IjkMediaPlayer.IJK_LOG_SILENT) - val containerFragment = VideoDetailContainerFragment().with(intent.extras) + val extras = intent.extras + extras.putString("uuid",uuid.toString()) + val containerFragment = VideoDetailContainerFragment().with(extras) supportFragmentManager.beginTransaction().replace(R.id.layout_activity_content, containerFragment).commitNowAllowingStateLoss() @@ -58,14 +60,14 @@ class VideoDetailActivity : BaseActivity() { override fun onPause() { if (isPauseVideo) { - CustomManager.getCustomManager("detail_$uuid").onPause("detail_$uuid") + CustomManager.onPause("detail_$uuid") } super.onPause() } override fun onResume() { if (isPauseVideo) { - CustomManager.getCustomManager("detail_$uuid").onResume("detail_$uuid") + CustomManager.onResume("detail_$uuid") } else { isPauseVideo = true } diff --git a/app/src/main/java/com/gh/gamecenter/video/detail/VideoDetailContainerFragment.kt b/app/src/main/java/com/gh/gamecenter/video/detail/VideoDetailContainerFragment.kt index ce02fba5d4..69a530a96e 100644 --- a/app/src/main/java/com/gh/gamecenter/video/detail/VideoDetailContainerFragment.kt +++ b/app/src/main/java/com/gh/gamecenter/video/detail/VideoDetailContainerFragment.kt @@ -14,6 +14,7 @@ import android.widget.RelativeLayout import android.widget.TextView import androidx.recyclerview.widget.OrientationHelper import androidx.recyclerview.widget.RecyclerView +import com.gh.base.fragment.BaseLazyFragment import com.gh.common.constant.Constants import com.gh.common.util.* import com.gh.common.view.vertical_recycler.OnPagerListener @@ -24,7 +25,6 @@ import com.gh.download.cache.CacheObserver import com.gh.gamecenter.R import com.gh.gamecenter.entity.VideoEntity import com.gh.gamecenter.manager.UserManager -import com.gh.gamecenter.normal.NormalFragment import com.gh.gamecenter.qa.comment.CommentActivity import com.lightgame.download.DataWatcher import com.lightgame.download.DownloadEntity @@ -36,9 +36,9 @@ import kotlinx.android.synthetic.main.fragment_video_detail_container.* import kotlinx.android.synthetic.main.reuse_no_connection.* import kotlinx.android.synthetic.main.reuse_none_data.* -class VideoDetailContainerFragment : NormalFragment(), OnBackPressedListener { +class VideoDetailContainerFragment : BaseLazyFragment(), OnBackPressedListener { - private lateinit var mViewModel: VideoDetailContainerViewModel + lateinit var mViewModel: VideoDetailContainerViewModel private lateinit var mAdapter: VideoAdapter lateinit var mInitialVideoId: String @@ -50,6 +50,7 @@ class VideoDetailContainerFragment : NormalFragment(), OnBackPressedListener { context, OrientationHelper.VERTICAL) private var mLastPosition = 0 private var mAdCountDownTimer: Disposable? = null + private var mIsHomeVideo = false//是否是视频总入口 private var mLastDownloadStatus: DownloadStatus? = null @@ -72,13 +73,18 @@ class VideoDetailContainerFragment : NormalFragment(), OnBackPressedListener { mViewModel = viewModelProviderFromParent() mViewModel.path = arguments?.getString(EntranceUtils.KEY_PATH) ?: "" mViewModel.entranceDetail = arguments?.getString(EntranceUtils.KEY_REFERER) ?: "" - mViewModel.getVideoDetailList(mInitialVideoId, mLocation, isLoadNext = true) + mViewModel.uuid = arguments?.getString(EntranceUtils.KEY_UUID) ?: "" + mIsHomeVideo = arguments?.getBoolean(EntranceUtils.KEY_IS_HOME_VIDEO) ?: false mViewModel.showComment = arguments?.getBoolean(EntranceUtils.KEY_SHOW_COMMENT) ?: false + mViewModel.mLocation = mLocation + mViewModel.mIsHomeVideo = mIsHomeVideo + + mViewModel.getVideoDetailList(mInitialVideoId, mLocation, isLoadNext = true) ClassicsFooter.REFRESH_FOOTER_NOTHING = "没有内容了" } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) + override fun onFragmentFirstVisible() { + super.onFragmentFirstVisible() initListener() if (mLocation == VideoDetailContainerViewModel.Location.USER_UPLOADED_VIDEO.value) { titleTv.text = "" @@ -95,8 +101,10 @@ class VideoDetailContainerFragment : NormalFragment(), OnBackPressedListener { } } } - toolbar.setNavigationIcon(R.drawable.ic_toolbar_back_white) - toolbar.setNavigationOnClickListener { requireActivity().finish() } + if (!mIsHomeVideo) { + toolbar.setNavigationIcon(R.drawable.ic_toolbar_back_white) + toolbar.setNavigationOnClickListener { requireActivity().finish() } + } smartRefreshLayout.setNoMoreData(true) @@ -129,6 +137,40 @@ class VideoDetailContainerFragment : NormalFragment(), OnBackPressedListener { mOldList.clear() mOldList.addAll(it) } + + mViewModel.networkError.observeNonNull(this) { + if (it) { + showNetworkErrorView() + toast("网络异常") + } + } + + mViewModel.noDataError.observeNonNull(this) { + if (it) { + showNoDataErrorView() + toast("内容可能已被删除") + } + } + + mViewModel.needToUpdateVideoInfo.observeNonNull(this) { + findFirstCompletelyVisibleVideoViewByPosition()?.updateViewDetail(it) + } + + marquee_ad_title.isSelected = true + + mAdCountDownTimer = startTimer(20) + val isShowSlide = SPUtils.getBoolean(Constants.SP_SHOW_SLIDE_GUIDE) + if (!isShowSlide) { + showSlideGuide() + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + } + + private fun initListener() { recyclerview.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { super.onScrolled(recyclerView, dx, dy) @@ -152,19 +194,18 @@ class VideoDetailContainerFragment : NormalFragment(), OnBackPressedListener { if (isNext) { MtaHelper.onEvent("视频详情", "下滑", titleAndId) LogUtils.uploadVideoStreamingPlaying("开始播放-下滑", "", mViewModel.path, - mViewModel.entranceDetail, video.id, (activity as VideoDetailActivity).uuid.toString(), 0.0, 0, 0, "play") + mViewModel.entranceDetail, video.id, mViewModel.uuid, 0.0, 0, 0, "play") } else { MtaHelper.onEvent("视频详情", "上滑", titleAndId) LogUtils.uploadVideoStreamingPlaying("开始播放-上滑", "", mViewModel.path, - mViewModel.entranceDetail, video.id, (activity as VideoDetailActivity).uuid.toString(), 0.0, 0, 0, "play") + mViewModel.entranceDetail, video.id, mViewModel.uuid, 0.0, 0, 0, "play") } - val detailPlayerView = recyclerview.findViewHolderForAdapterPosition(position)?.itemView as DetailPlayerView - detailPlayerView.repeatPlayCount = 0 + val detailPlayerView = recyclerview.findViewHolderForAdapterPosition(position)?.itemView as? DetailPlayerView + detailPlayerView?.repeatPlayCount = 0 } } override fun onPageSelected(position: Int, isBottom: Boolean) { - val uuid = (context as VideoDetailActivity).uuid smartRefreshLayout.isEnableLoadMore = isBottom smartRefreshLayout.setNoMoreData(isBottom) val isShowClick = SPUtils.getBoolean(Constants.SP_SHOW_CLICK_GUIDE) @@ -177,6 +218,7 @@ class VideoDetailContainerFragment : NormalFragment(), OnBackPressedListener { val pos = mViewPagerLayoutManager.findFirstCompletelyVisibleItemPosition() val videoView = findFirstCompletelyVisibleVideoViewByPosition() + if (pos + 2 <= mAdapter.videoList.size - 1) {//预加载视频 CacheManager.getInstance().download(mAdapter.videoList[pos + 1].url, object : CacheObserver() {}) CacheManager.getInstance().download(mAdapter.videoList[pos + 2].url, object : CacheObserver() {}) @@ -185,7 +227,7 @@ class VideoDetailContainerFragment : NormalFragment(), OnBackPressedListener { } if (mLastPosition != pos && videoView?.isInPlayingState != true) { CacheManager.getInstance().cancel(mAdapter.videoList[pos].url) - CustomManager.releaseAllVideos("detail_$uuid") + CustomManager.releaseAllVideos("detail_${mViewModel.uuid}") videoView?.startPlayLogic() mBaseHandler.postDelayed({ videoView?.updateMuteStatus() @@ -210,33 +252,7 @@ class VideoDetailContainerFragment : NormalFragment(), OnBackPressedListener { } } }) - mViewModel.networkError.observeNonNull(this) { - if (it) { - showNetworkErrorView() - toast("网络异常") - } - } - mViewModel.noDataError.observeNonNull(this) { - if (it) { - showNoDataErrorView() - toast("内容可能已被删除") - } - } - - mViewModel.needToUpdateVideoInfo.observeNonNull(this) { - findFirstCompletelyVisibleVideoViewByPosition()?.updateViewDetail(it) - } - marquee_ad_title.isSelected = true - - mAdCountDownTimer = startTimer(20) - val isShowSlide = SPUtils.getBoolean(Constants.SP_SHOW_SLIDE_GUIDE) - if (!isShowSlide) { - showSlideGuide() - } - } - - private fun initListener() { marquee_ad.setOnClickListener { val pos = mViewPagerLayoutManager.findFirstCompletelyVisibleItemPosition() val videoEntity = mAdapter.videoList[pos] @@ -349,28 +365,28 @@ class VideoDetailContainerFragment : NormalFragment(), OnBackPressedListener { return Pair(itemHeight, iposition - itemTop) } - override fun onResume() { + + override fun onFragmentResume() { findFirstCompletelyVisibleVideoViewByPosition()?.uploadVideoStreamingPlaying("恢复页面") DownloadManager.getInstance(requireContext()).addObserver(dataWatcher) - CustomManager.onResumeAll() - super.onResume() + CustomManager.onResume("detail_${mViewModel.uuid}") + super.onFragmentResume() } - override fun onPause() { + override fun onFragmentPause() { DownloadManager.getInstance(requireContext()).removeObserver(dataWatcher) findFirstCompletelyVisibleVideoViewByPosition()?.uploadVideoStreamingPlaying("暂停页面") - CustomManager.onPauseAll() - if (mAdCountDownTimer != null && !mAdCountDownTimer!!.isDisposed) { - mAdCountDownTimer?.dispose() - } - super.onPause() + CustomManager.onPause("detail_${mViewModel.uuid}") + super.onFragmentPause() } override fun onDestroyView() { - val uuid = (context as VideoDetailActivity).uuid findFirstCompletelyVisibleVideoViewByPosition()?.uploadVideoStreamingPlaying("退出页面") - CustomManager.releaseAllVideos("detail_$uuid") + CustomManager.releaseAllVideos("detail_${mViewModel.uuid}") CacheManager.getInstance().removeAllCall() + if (mAdCountDownTimer != null && !mAdCountDownTimer!!.isDisposed) { + mAdCountDownTimer?.dispose() + } super.onDestroyView() } @@ -382,7 +398,9 @@ class VideoDetailContainerFragment : NormalFragment(), OnBackPressedListener { } private fun showNetworkErrorView() { - toolbar.setNavigationIcon(R.drawable.ic_bar_back) + if (!mIsHomeVideo) { + toolbar.setNavigationIcon(R.drawable.ic_bar_back) + } errorContainer.visibility = View.VISIBLE reuse_no_connection.visibility = View.VISIBLE reuse_no_connection.setOnClickListener { @@ -391,13 +409,17 @@ class VideoDetailContainerFragment : NormalFragment(), OnBackPressedListener { } private fun showNoDataErrorView() { - toolbar.setNavigationIcon(R.drawable.ic_bar_back) + if (!mIsHomeVideo) { + toolbar.setNavigationIcon(R.drawable.ic_bar_back) + } errorContainer.visibility = View.VISIBLE reuse_none_data.visibility = View.VISIBLE } private fun hideNetworkErrorView() { - toolbar.setNavigationIcon(R.drawable.ic_toolbar_back_white) + if (!mIsHomeVideo) { + toolbar.setNavigationIcon(R.drawable.ic_toolbar_back_white) + } errorContainer.visibility = View.GONE reuse_no_connection.visibility = View.GONE } diff --git a/app/src/main/java/com/gh/gamecenter/video/detail/VideoDetailContainerViewModel.kt b/app/src/main/java/com/gh/gamecenter/video/detail/VideoDetailContainerViewModel.kt index 8511300cfe..b58d5eafd4 100644 --- a/app/src/main/java/com/gh/gamecenter/video/detail/VideoDetailContainerViewModel.kt +++ b/app/src/main/java/com/gh/gamecenter/video/detail/VideoDetailContainerViewModel.kt @@ -7,12 +7,16 @@ import androidx.lifecycle.MutableLiveData import com.gh.common.history.HistoryDatabase import com.gh.common.runOnIoThread import com.gh.common.util.UrlFilterUtils +import com.gh.common.util.createRequestBodyAny import com.gh.gamecenter.entity.MyVideoEntity import com.gh.gamecenter.entity.VideoEntity import com.gh.gamecenter.manager.UserManager import com.gh.gamecenter.retrofit.BiResponse import com.gh.gamecenter.retrofit.RetrofitManager +import com.halo.assistant.HaloApp +import com.lightgame.utils.Util_System_Phone_State import com.lightgame.utils.Utils +import io.reactivex.Single import io.reactivex.schedulers.Schedulers import okhttp3.ResponseBody @@ -30,6 +34,11 @@ class VideoDetailContainerViewModel(application: Application) : AndroidViewModel var videoList = MutableLiveData>() var needToUpdateVideoInfo = MutableLiveData() + var uuid = "" + var mLocation = "" + var mIsHomeVideo = false//是否是视频总入口 + private var page = 1 + private var cacheVideoIds: ArrayList? = null // 是否为第一次加载(即没有进行加载更多) private var mIsFirstLoad = true @@ -44,6 +53,20 @@ class VideoDetailContainerViewModel(application: Application) : AndroidViewModel @SuppressLint("CheckResult") fun getVideoDetailList(videoId: String, location: String, isLoadNext: Boolean) { + when (location) { + Location.VIDEO_CHOICENESS.value, + Location.VIDEO_HOT.value -> { + if (isLoadNext) { + getVideoStream(location, videoId) + } + } + else -> { + getNormalVideoStream(videoId, location, isLoadNext) + } + } + } + + private fun getNormalVideoStream(videoId: String, location: String, isLoadNext: Boolean) { RetrofitManager.getInstance(getApplication()) .api.getVideoDetailList(videoId, getFilter(location, isLoadNext)) .subscribeOn(Schedulers.io()) @@ -74,6 +97,58 @@ class VideoDetailContainerViewModel(application: Application) : AndroidViewModel }) } + private fun getVideoStream(type: String, videoId: String) { + val requestMap = hashMapOf() + getCacheVideoIds().flatMap { + requestMap["cache_video_ids"] = it + val body = requestMap.createRequestBodyAny() + RetrofitManager.getInstance(getApplication()) + .api.getVideoStream(type, body, videoId, "${Util_System_Phone_State.getImei(HaloApp.getInstance().application)}${System.currentTimeMillis()}", page) + }.subscribeOn(Schedulers.io()) + .subscribe(object : BiResponse>() { + override fun onSuccess(data: ArrayList) { + if (mIsFirstLoad) { + // 无数据时直接显示无数据页面 + if (data.size == 0) { + noDataError.postValue(true) + return + } + startPosition = 0 + } + + //总播放算法-当获取的数据总数少于page_size时,重新将page设为1再重新循环 + if (type == Location.VIDEO_CHOICENESS.value && data.size < 20) { + page = 1 + } + + if (mIsFirstLoad || !(data.size == 1 && data[0].id == videoId)) { + mergeVideoList(data, true) + } + + mIsFirstLoad = false + } + + override fun onFailure(exception: Exception) { + super.onFailure(exception) + if (mIsFirstLoad) { + networkError.postValue(true) + mIsFirstLoad = false + } + } + }) + } + + //获取视频缓存ids + private fun getCacheVideoIds(): Single> { + return if (cacheVideoIds != null) { + Single.create { emitter -> emitter.onSuccess(cacheVideoIds!!) } + } else { + HistoryDatabase.instance.videoHistoryDao() + .getVideoWithOffset(50, (page - 1) * 50) + .map { t -> t.map { it.id }.toMutableList() } + } + } + fun mergeVideoList(receivedDataList: ArrayList, isLoadNext: Boolean = true) { if (receivedDataList.isNullOrEmpty()) return @@ -195,6 +270,7 @@ class VideoDetailContainerViewModel(application: Application) : AndroidViewModel vote = videoEntity.vote length = videoEntity.length time = System.currentTimeMillis() + videoStreamRecord = if (mLocation == Location.VIDEO_CHOICENESS.value || mLocation == Location.VIDEO_HOT.value) 1 else 0 } runOnIoThread { HistoryDatabase.instance.videoHistoryDao().addVideo(videoHistory) @@ -214,6 +290,10 @@ class VideoDetailContainerViewModel(application: Application) : AndroidViewModel * * APP 自定义的类型 * single_video 仅当前视频(即只拿一个视频) + * + * v372新增 [https://gitlab.ghzs.com/halo/halo-api/wikis/v3.7.2/API-v3.7.2#get-videosstream] + * choiceness 总播放算法,使用场景:导航栏视频总入口、新首页-轮播图、新首页-内容管理、问答-推荐入口 + * hot 单个游戏播放算法,使用场景:游戏详情、回答详情、文章详情 */ enum class Location(val value: String) { HOTTEST_GAME_VIDEO("hottest_game_video"), @@ -226,7 +306,11 @@ class VideoDetailContainerViewModel(application: Application) : AndroidViewModel USER_UPLOADED_VIDEO("user_uploaded_video"), - SINGLE_VIDEO("single_video") + SINGLE_VIDEO("single_video"), + + VIDEO_CHOICENESS("choiceness"), + + VIDEO_HOT("hot") } } \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_video_detail_bottom.xml b/app/src/main/res/drawable/bg_video_detail_bottom.xml new file mode 100644 index 0000000000..da7a281c9f --- /dev/null +++ b/app/src/main/res/drawable/bg_video_detail_bottom.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_community.xml b/app/src/main/res/layout/fragment_community.xml index 03898d2e90..0c2067d895 100644 --- a/app/src/main/res/layout/fragment_community.xml +++ b/app/src/main/res/layout/fragment_community.xml @@ -3,7 +3,8 @@ 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:layout_height="match_parent" + android:paddingBottom="55dp"> + android:layout_height="match_parent" + android:paddingBottom="55dp"> + android:layout_height="match_parent" /> + android:paddingBottom="8dp" + android:clickable="true"> + + + + + + - - + --> + android:fitsSystemWindows="true" + android:paddingBottom="55dp"> diff --git a/app/src/main/res/layout/layout_video_detail_surface.xml b/app/src/main/res/layout/layout_video_detail_surface.xml index 54a2a753ef..89992b355d 100644 --- a/app/src/main/res/layout/layout_video_detail_surface.xml +++ b/app/src/main/res/layout/layout_video_detail_surface.xml @@ -13,9 +13,12 @@ + android:orientation="vertical" + android:paddingTop="10dp"> + +