From 2af6e5d99f192d51051ebd31bb770ed6ae008f9a Mon Sep 17 00:00:00 2001 From: garyxgwang Date: Tue, 7 Dec 2021 10:38:13 +0800 Subject: [PATCH] Fix bug, update sdk. --- Demo/app/build.gradle | 2 +- .../tencent/liteav/demo/DemoApplication.java | 20 - .../com/tencent/liteav/demo/MainActivity.java | 5 +- .../tencent/liteav/demo/TXCSDKService.java | 38 ++ .../drawable/app_update_dialog_background.xml | 19 + Demo/app/src/main/res/values-en/strings.xml | 8 + Demo/app/src/main/res/values-zh/strings.xml | 1 + Demo/app/src/main/res/values/colors.xml | 1 + Demo/app/src/main/res/values/strings.xml | 8 + .../liteav/demo/common/UserModelManager.java | 5 +- Demo/superplayerdemo/build.gradle | 6 +- .../src/main/AndroidManifest.xml | 6 + .../liteav/demo/player/demo/FeedActivity.java | 201 ++++++++ .../demo/player/demo/SuperPlayerActivity.java | 26 +- .../shortvideo/core/TXVodPlayerWrapper.java | 8 +- .../shortvideo/view/SuperShortVideoView.java | 37 +- .../expand/model/entity/VideoModel.java | 17 +- .../model/utils/SuperVodListLoader.java | 23 +- .../demo/player/feed/FeedPlayerManager.java | 97 ++++ .../liteav/demo/player/feed/FeedView.java | 230 +++++++++ .../demo/player/feed/FeedViewCallBack.java | 23 + .../demo/player/feed/anim/FeedAnim.java | 28 ++ .../feeddetailview/FeedDetailAdapter.java | 98 ++++ .../FeedDetailListClickListener.java | 8 + .../feed/feeddetailview/FeedDetailView.java | 224 +++++++++ .../FeedDetailViewCallBack.java | 19 + .../FeedLinearSmoothScroller.java | 73 +++ .../feed/feedlistview/FeedListAdapter.java | 139 ++++++ .../feed/feedlistview/FeedListCallBack.java | 18 + .../feed/feedlistview/FeedListItemView.java | 219 +++++++++ .../feedlistview/FeedListScrollListener.java | 139 ++++++ .../feed/feedlistview/FeedListView.java | 238 ++++++++++ .../player/feed/model/FeedVodListLoader.java | 252 ++++++++++ .../demo/player/feed/player/FeedPlayer.java | 28 ++ .../player/feed/player/FeedPlayerView.java | 226 +++++++++ .../main/res/layout/feed_activity_layout.xml | 40 ++ .../feedview_detailview_item_layout.xml | 55 +++ .../res/layout/feedview_detailview_layout.xml | 61 +++ .../res/layout/feedview_list_item_layout.xml | 62 +++ .../src/main/res/values-en/strings.xml | 5 +- .../src/main/res/values/colors.xml | 1 + .../src/main/res/values/strings.xml | 5 +- Demo/superplayerkit/build.gradle | 6 + .../demo/superplayer/SuperPlayerDef.java | 1 + .../superplayer/SuperPlayerGlobalConfig.java | 28 ++ .../demo/superplayer/SuperPlayerModel.java | 28 ++ .../demo/superplayer/SuperPlayerView.java | 440 +++++++++++------- .../demo/superplayer/model/SuperPlayer.java | 50 +- .../superplayer/model/SuperPlayerImpl.java | 349 ++++++++------ .../model/SuperPlayerObserver.java | 11 + .../model/protocol/PlayInfoParserV2.java | 5 + .../demo/superplayer/ui/player/AbsPlayer.java | 6 +- .../ui/player/FullScreenPlayer.java | 86 +++- .../demo/superplayer/ui/player/Player.java | 1 + .../superplayer/ui/player/WindowPlayer.java | 70 ++- .../demo/superplayer/ui/view/DanmuView.java | 3 + .../ui/view/VideoProgressLayout.java | 2 +- .../superplayer/ui/view/VipWatchView.java | 4 +- .../demo/superplayer/ui/view/VodMoreView.java | 23 + .../drawable-xxhdpi/superplayer_default.png | Bin 0 -> 198038 bytes .../layout/superplayer_quality_popup_view.xml | 3 +- .../superplayer_vod_player_fullscreen.xml | 158 ++++--- .../layout/superplayer_vod_player_window.xml | 126 ++--- 63 files changed, 3600 insertions(+), 519 deletions(-) create mode 100644 Demo/app/src/main/java/com/tencent/liteav/demo/TXCSDKService.java create mode 100644 Demo/app/src/main/res/drawable/app_update_dialog_background.xml create mode 100644 Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/demo/FeedActivity.java create mode 100644 Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/FeedPlayerManager.java create mode 100644 Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/FeedView.java create mode 100644 Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/FeedViewCallBack.java create mode 100644 Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/anim/FeedAnim.java create mode 100644 Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feeddetailview/FeedDetailAdapter.java create mode 100644 Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feeddetailview/FeedDetailListClickListener.java create mode 100644 Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feeddetailview/FeedDetailView.java create mode 100644 Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feeddetailview/FeedDetailViewCallBack.java create mode 100644 Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feedlistview/FeedLinearSmoothScroller.java create mode 100644 Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feedlistview/FeedListAdapter.java create mode 100644 Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feedlistview/FeedListCallBack.java create mode 100644 Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feedlistview/FeedListItemView.java create mode 100644 Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feedlistview/FeedListScrollListener.java create mode 100644 Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feedlistview/FeedListView.java create mode 100644 Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/model/FeedVodListLoader.java create mode 100644 Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/player/FeedPlayer.java create mode 100644 Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/player/FeedPlayerView.java create mode 100644 Demo/superplayerdemo/src/main/res/layout/feed_activity_layout.xml create mode 100644 Demo/superplayerdemo/src/main/res/layout/feedview_detailview_item_layout.xml create mode 100644 Demo/superplayerdemo/src/main/res/layout/feedview_detailview_layout.xml create mode 100644 Demo/superplayerdemo/src/main/res/layout/feedview_list_item_layout.xml create mode 100644 Demo/superplayerkit/src/main/res/drawable-xxhdpi/superplayer_default.png diff --git a/Demo/app/build.gradle b/Demo/app/build.gradle index 8ad7c29..1679297 100644 --- a/Demo/app/build.gradle +++ b/Demo/app/build.gradle @@ -9,7 +9,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 24 - versionName "9.3.1169" + versionName "9.4.0.1218" multiDexEnabled true ndk { diff --git a/Demo/app/src/main/java/com/tencent/liteav/demo/DemoApplication.java b/Demo/app/src/main/java/com/tencent/liteav/demo/DemoApplication.java index 10d16ed..d3c05fd 100644 --- a/Demo/app/src/main/java/com/tencent/liteav/demo/DemoApplication.java +++ b/Demo/app/src/main/java/com/tencent/liteav/demo/DemoApplication.java @@ -6,9 +6,6 @@ import androidx.multidex.MultiDexApplication; -import com.tencent.rtmp.TXLiveBase; -import com.tencent.rtmp.TXLiveBaseListener; - import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -19,10 +16,6 @@ public class DemoApplication extends MultiDexApplication { // private RefWatcher mRefWatcher; private static DemoApplication instance; - // 如何获取License? 请参考官网指引 https://cloud.tencent.com/document/product/454/34750 - String licenceUrl = "请替换成您的licenseUrl"; - String licenseKey = "请替换成您的licenseKey"; - private Context mAppContext; @Override public void onCreate() { @@ -32,19 +25,6 @@ public void onCreate() { mAppContext = this.getApplicationContext(); instance = this; - - TXLiveBase.getInstance().setLicence(instance, licenceUrl, licenseKey); - TXLiveBase.setListener(new TXLiveBaseListener() { - @Override - public void onUpdateNetworkTime(int errCode, String errMsg) { - if (errCode != 0) { - TXLiveBase.updateNetworkTime(); - } - } - }); - TXLiveBase.updateNetworkTime(); - - // 短视频licence设置 StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build()); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { diff --git a/Demo/app/src/main/java/com/tencent/liteav/demo/MainActivity.java b/Demo/app/src/main/java/com/tencent/liteav/demo/MainActivity.java index 9bea9e8..7378562 100644 --- a/Demo/app/src/main/java/com/tencent/liteav/demo/MainActivity.java +++ b/Demo/app/src/main/java/com/tencent/liteav/demo/MainActivity.java @@ -18,6 +18,7 @@ import androidx.recyclerview.widget.RecyclerView; import com.tencent.liteav.demo.common.widget.expandableadapter.BaseExpandableRecyclerViewAdapter; +import com.tencent.liteav.demo.player.demo.FeedActivity; import com.tencent.liteav.demo.player.demo.SuperPlayerActivity; import com.tencent.liteav.demo.player.demo.shortvideo.view.ShortVideoActivity; import com.tencent.rtmp.TXLiveBase; @@ -50,10 +51,11 @@ protected void onCreate(Bundle savedInstanceState) { return; } + TXCSDKService.init(getApplicationContext()); setContentView(R.layout.activity_main); mTvVersion = (TextView) findViewById(R.id.main_tv_version); - mTvVersion.setText(getString(R.string.app_tv_super_player_version, TXLiveBase.getSDKVersionStr()+"(9.3.1169)")); + mTvVersion.setText(getString(R.string.app_tv_super_player_version, TXLiveBase.getSDKVersionStr()+"(9.4.0.1218)")); mMainTitle = (TextView) findViewById(R.id.main_title); mMainTitle.setOnLongClickListener(new View.OnLongClickListener() { @@ -139,6 +141,7 @@ private List initGroupData() { List playerChildList = new ArrayList<>(); playerChildList.add(new ChildBean(getString(R.string.app_item_super_player), R.drawable.play, 3, SuperPlayerActivity.class)); playerChildList.add(new ChildBean(getString(R.string.app_item_shortvideo_player), R.drawable.play, 3, ShortVideoActivity.class)); + playerChildList.add(new ChildBean(getString(R.string.app_feed_player), R.drawable.play, 3, FeedActivity.class)); if (playerChildList.size() != 0) { GroupBean playerGroupBean = new GroupBean(getString(R.string.app_item_player), R.drawable.composite, playerChildList); groupList.add(playerGroupBean); diff --git a/Demo/app/src/main/java/com/tencent/liteav/demo/TXCSDKService.java b/Demo/app/src/main/java/com/tencent/liteav/demo/TXCSDKService.java new file mode 100644 index 0000000..f7cf156 --- /dev/null +++ b/Demo/app/src/main/java/com/tencent/liteav/demo/TXCSDKService.java @@ -0,0 +1,38 @@ +package com.tencent.liteav.demo; + +import android.content.Context; + +import com.tencent.rtmp.TXLiveBase; +import com.tencent.rtmp.TXLiveBaseListener; + +public class TXCSDKService { + private static final String TAG = "TXCSDKService"; + // 如何获取License? 请参考官网指引 https://cloud.tencent.com/document/product/454/34750 + private static final String licenceUrl = + "请替换成您的licenseUrl"; + private static final String licenseKey = "请替换成您的licenseKey"; + + private TXCSDKService() { + } + + /** + * 初始化腾讯云相关sdk。 + * SDK 初始化过程中可能会读取手机型号等敏感信息,需要在用户同意隐私政策后,才能获取。 + * + * @param appContext The application context. + */ + public static void init(Context appContext) { + TXLiveBase.getInstance().setLicence(appContext, licenceUrl, licenseKey); + TXLiveBase.setListener(new TXLiveBaseListener() { + @Override + public void onUpdateNetworkTime(int errCode, String errMsg) { + if (errCode != 0) { + TXLiveBase.updateNetworkTime(); + } + } + }); + TXLiveBase.updateNetworkTime(); + + // 短视频licence设置 + } +} diff --git a/Demo/app/src/main/res/drawable/app_update_dialog_background.xml b/Demo/app/src/main/res/drawable/app_update_dialog_background.xml new file mode 100644 index 0000000..065cfd1 --- /dev/null +++ b/Demo/app/src/main/res/drawable/app_update_dialog_background.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/Demo/app/src/main/res/values-en/strings.xml b/Demo/app/src/main/res/values-en/strings.xml index 4aebae1..2cd4609 100755 --- a/Demo/app/src/main/res/values-en/strings.xml +++ b/Demo/app/src/main/res/values-en/strings.xml @@ -42,6 +42,7 @@ Superplayer Short Video Player + Feed stream play Player Shooting @@ -105,4 +106,11 @@ Screen Share V1 Pull V1 Co-anchoring (Old) + + Downloading + %s downloading + Download failed + Upgrade note + New version detected: %s, Please upgrade! + Upgrade diff --git a/Demo/app/src/main/res/values-zh/strings.xml b/Demo/app/src/main/res/values-zh/strings.xml index 692427a..d6f6e9d 100755 --- a/Demo/app/src/main/res/values-zh/strings.xml +++ b/Demo/app/src/main/res/values-zh/strings.xml @@ -45,6 +45,7 @@ 超级播放器 播放器 短视频播放 + Feed流播放 视频录制 特效编辑 diff --git a/Demo/app/src/main/res/values/colors.xml b/Demo/app/src/main/res/values/colors.xml index 3bba7d8..534ae2d 100644 --- a/Demo/app/src/main/res/values/colors.xml +++ b/Demo/app/src/main/res/values/colors.xml @@ -35,6 +35,7 @@ #181818 #0accac + #85ce57 diff --git a/Demo/app/src/main/res/values/strings.xml b/Demo/app/src/main/res/values/strings.xml index 130cf2a..49baae3 100644 --- a/Demo/app/src/main/res/values/strings.xml +++ b/Demo/app/src/main/res/values/strings.xml @@ -112,6 +112,7 @@ DEBUG开关已关闭 点播播放器 超低延时播放 + Feed流播放 其他工具 直播推流 V1 @@ -119,4 +120,11 @@ 屏幕分享 V1 直播拉流 V1 连麦演示 V1 + + 下载中 + %s 下载中 + 下载失败 + 升级提示 + 检测到当前Android 腾讯云视立方有新版本: %s, 请升级后使用! + 开始升级 diff --git a/Demo/common/src/main/java/com/tencent/liteav/demo/common/UserModelManager.java b/Demo/common/src/main/java/com/tencent/liteav/demo/common/UserModelManager.java index f747e11..ee74fb1 100644 --- a/Demo/common/src/main/java/com/tencent/liteav/demo/common/UserModelManager.java +++ b/Demo/common/src/main/java/com/tencent/liteav/demo/common/UserModelManager.java @@ -30,9 +30,8 @@ public static UserModelManager getInstance() { } public synchronized UserModel getUserModel() { - if (mUserModel == null) { - loadUserModel(); - } + // 保证每次都能获取到最新的UserModel + loadUserModel(); return mUserModel == null ? new UserModel() : mUserModel; } diff --git a/Demo/superplayerdemo/build.gradle b/Demo/superplayerdemo/build.gradle index a48b2dc..bae91d8 100644 --- a/Demo/superplayerdemo/build.gradle +++ b/Demo/superplayerdemo/build.gradle @@ -33,10 +33,14 @@ dependencies { compile 'me.dm7.barcodescanner:zxing:1.8.4' compile 'com.squareup.okhttp3:okhttp:3.11.0' compile 'androidx.multidex:multidex:2.0.0' - compile 'com.github.bumptech.glide:glide:3.7.0' + compile 'com.github.bumptech.glide:glide:4.12.0' compile 'androidx.cardview:cardview:1.0.0' compile 'com.google.code.gson:gson:2.3.1' compile('com.blankj:utilcode:1.25.9', { exclude group: 'com.google.code.gson', module: 'gson' }) + + implementation 'com.scwang.smart:refresh-layout-kernel:2.0.3' //核心必须依赖 + implementation 'com.scwang.smart:refresh-header-classics:2.0.3' //经典刷新头 + implementation 'com.scwang.smart:refresh-footer-classics:2.0.3' //经典加载 } diff --git a/Demo/superplayerdemo/src/main/AndroidManifest.xml b/Demo/superplayerdemo/src/main/AndroidManifest.xml index 3da4ca9..9f206e3 100644 --- a/Demo/superplayerdemo/src/main/AndroidManifest.xml +++ b/Demo/superplayerdemo/src/main/AndroidManifest.xml @@ -30,6 +30,12 @@ android:screenOrientation="portrait" /> + diff --git a/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/demo/FeedActivity.java b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/demo/FeedActivity.java new file mode 100644 index 0000000..80353f5 --- /dev/null +++ b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/demo/FeedActivity.java @@ -0,0 +1,201 @@ +package com.tencent.liteav.demo.player.demo; + +import android.app.Activity; +import android.os.Build; +import android.os.Bundle; +import android.view.View; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.Nullable; + +import com.tencent.liteav.demo.player.R; +import com.tencent.liteav.demo.player.expand.model.entity.VideoModel; +import com.tencent.liteav.demo.player.feed.FeedView; +import com.tencent.liteav.demo.player.feed.FeedViewCallBack; +import com.tencent.liteav.demo.player.feed.model.FeedVodListLoader; + +import java.util.List; + + +public class FeedActivity extends Activity { + + private Button backBtn = null; + private FeedView feedView = null; + private TextView titleTxt = null; + private int page = 0; + private FeedVodListLoader feedVodListLoader = null; + private boolean isFullScreen = false; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.feed_activity_layout); + this.backBtn = findViewById(R.id.feed_ac_back_btn); + titleTxt = findViewById(R.id.feed_ac_layout_txt); + feedView = findViewById(R.id.feed_ac_feed_view); + feedView.setFeedViewCallBack(new FeedViewCallBack() { + @Override + public void onLoadMore() { + loadMore(); + } + + @Override + public void onRefresh() { + loadData(true); + } + + @Override + public void onStartDetailPage() { + titleTxt.setText(getResources().getString(R.string.app_feed_detail_title)); + } + + @Override + public void onStopDetailPage() { + titleTxt.setText(getResources().getString(R.string.app_feed_title)); + } + + @Override + public void onLoadDetailData(VideoModel videoModel) { + loadDetailData(); + } + + + @Override + public void onStartFullScreenPlay() { + isFullScreen = true; + findViewById(R.id.title_layout).setVisibility(View.GONE); + } + + @Override + public void onStopFullScreenPlay() { + isFullScreen = false; + findViewById(R.id.title_layout).setVisibility(View.VISIBLE); + } + }); + backBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + onBackPressed(); + } + }); + feedVodListLoader = new FeedVodListLoader(); + loadData(false); + + } + + + private void loadData(final boolean isRefresh) { + page = 0; + feedVodListLoader.loadListData(page, new FeedVodListLoader.LoadDataCallBack() { + @Override + public void onLoadedData(List videoModels) { + if (isDestroyed()) { + return; + } + feedView.addData(videoModels, true); + if (isRefresh) { + feedView.finishRefresh(true); + } + } + + @Override + public void onError(int errorCode) { + if (isDestroyed()) { + return; + } + Toast.makeText(FeedActivity.this, "暂未获取到数据", Toast.LENGTH_SHORT).show(); + } + }); + } + + + /** + * 加载更多数据 + */ + private void loadMore() { + feedVodListLoader.loadListData(page + 1, new FeedVodListLoader.LoadDataCallBack() { + @Override + public void onLoadedData(List videoModels) { + if (isDestroyed()) { + return; + } + page++; + feedView.addData(videoModels, false); + feedView.finishLoadMore(true, false); + } + + @Override + public void onError(int errorCode) { + if (isDestroyed()) { + return; + } + feedView.finishLoadMore(false, true); + } + }); + } + + + private void loadDetailData() { + feedVodListLoader.loadListData(page, new FeedVodListLoader.LoadDataCallBack() { + @Override + public void onLoadedData(List videoModels) { + if (isDestroyed()) { + return; + } + feedView.addDetailListData(videoModels); + } + + @Override + public void onError(int errorCode) { + } + }); + } + + + @Override + protected void onResume() { + super.onResume(); + feedView.onResume(); + //添加屏幕常亮 + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + if (isFullScreen) { + //隐藏虚拟按键,并且全屏 + View decorView = getWindow().getDecorView(); + if (decorView == null) { + return; + } + if (Build.VERSION.SDK_INT > 11 && Build.VERSION.SDK_INT < 19) { // lower api + decorView.setSystemUiVisibility(View.GONE); + } else if (Build.VERSION.SDK_INT >= 19) { + int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_FULLSCREEN; + decorView.setSystemUiVisibility(uiOptions); + } + } + } + + @Override + protected void onPause() { + super.onPause(); + feedView.onPause(); + //清楚屏幕常亮 + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + feedView.onDestroy(); + } + + @Override + public void onBackPressed() { + if (!feedView.goBack()) { + super.onBackPressed(); + } + + } +} diff --git a/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/demo/SuperPlayerActivity.java b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/demo/SuperPlayerActivity.java index 8c803cc..612b3f0 100644 --- a/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/demo/SuperPlayerActivity.java +++ b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/demo/SuperPlayerActivity.java @@ -433,7 +433,7 @@ protected void onResume() { if (mSuperPlayerView.getPlayerState() == SuperPlayerDef.PlayerState.PLAYING || mSuperPlayerView.getPlayerState() == SuperPlayerDef.PlayerState.PAUSE) { Log.i(TAG, "onResume state :" + mSuperPlayerView.getPlayerState()); - if(!mSuperPlayerView.isShowingVipView()) { + if (!mSuperPlayerView.isShowingVipView()) { mSuperPlayerView.onResume(); } if (mSuperPlayerView.getPlayerMode() == SuperPlayerDef.PlayerMode.FLOAT) { @@ -478,6 +478,9 @@ protected void onDestroy() { */ @Override public void onSuccess(final VideoModel videoModel) { + if ("8602268011437356984".equals(videoModel.fileid)) { + videoModel.title = "自定义封面演示"; + } runOnUiThread(new Runnable() { @Override public void run() { @@ -509,7 +512,7 @@ public void onItemClick(int position, final VideoModel videoModel) { private void playVideoModel(VideoModel videoModel) { final SuperPlayerModel superPlayerModelV3 = new SuperPlayerModel(); superPlayerModelV3.appId = videoModel.appid; - superPlayerModelV3.vipWatchMode=videoModel.vipWatchModel; + superPlayerModelV3.vipWatchMode = videoModel.vipWatchModel; if (!TextUtils.isEmpty(videoModel.videoURL)) { if (isSuperPlayerVideo(videoModel)) { playSuperPlayerVideo(videoModel); @@ -530,6 +533,10 @@ private void playVideoModel(VideoModel videoModel) { superPlayerModelV3.videoId.fileId = videoModel.fileid; superPlayerModelV3.videoId.pSign = videoModel.pSign; } + superPlayerModelV3.playAction = videoModel.playAction; + superPlayerModelV3.placeholderImage = videoModel.placeholderImage; + superPlayerModelV3.coverPictureUrl = videoModel.coverPictureUrl; + superPlayerModelV3.duration = videoModel.duration; mSuperPlayerView.playWithModel(superPlayerModelV3); } @@ -918,6 +925,21 @@ public void onStartFloatWindowPlay() { startActivity(intent); } + @Override + public void onPlaying() { + + } + + @Override + public void onPlayEnd() { + + } + + @Override + public void onError(int code) { + + } + @Override public void onRefresh() { if (mDefaultVideo) { diff --git a/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/demo/shortvideo/core/TXVodPlayerWrapper.java b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/demo/shortvideo/core/TXVodPlayerWrapper.java index 805e928..107a9b3 100644 --- a/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/demo/shortvideo/core/TXVodPlayerWrapper.java +++ b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/demo/shortvideo/core/TXVodPlayerWrapper.java @@ -10,6 +10,8 @@ import com.tencent.rtmp.TXVodPlayer; import com.tencent.rtmp.ui.TXCloudVideoView; +import java.io.File; + import static com.tencent.rtmp.TXLiveConstants.PLAY_EVT_PLAY_BEGIN; import static com.tencent.rtmp.TXLiveConstants.PLAY_EVT_PLAY_END; import static com.tencent.rtmp.TXLiveConstants.PLAY_EVT_PLAY_PROGRESS; @@ -31,6 +33,10 @@ public TXVodPlayerWrapper(Context context) { mTXVodPlayConfig.setProgressInterval(1); mTXVodPlayConfig.setSmoothSwitchBitrate(true); mTXVodPlayConfig.setMaxBufferSize(5); + File sdcardDir = context.getExternalFilesDir(null); + if (sdcardDir != null) { + mTXVodPlayConfig.setCacheFolderPath(sdcardDir.getPath() + "/txcache"); + } mVodPlayer.setConfig(mTXVodPlayConfig); } @@ -112,8 +118,8 @@ public void preStartPlay(ShortVideoBean bean) { mVodPlayer.stopPlay(true); TXLog.i(TAG, "[preStartPlay] , startOnPrepare ," + mStartOnPrepare + ", mVodPlayer " + mVodPlayer.hashCode()); mVodPlayer.setAutoPlay(false); - mVodPlayer.startPlay(bean.videoURL); mVodPlayer.setBitrateIndex(bean.bitRateIndex); + mVodPlayer.startPlay(bean.videoURL); } private void playerStatusChanged(TxVodStatus status) { diff --git a/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/demo/shortvideo/view/SuperShortVideoView.java b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/demo/shortvideo/view/SuperShortVideoView.java index b55f8e3..0e58858 100644 --- a/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/demo/shortvideo/view/SuperShortVideoView.java +++ b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/demo/shortvideo/view/SuperShortVideoView.java @@ -24,18 +24,19 @@ import java.util.List; public class SuperShortVideoView extends RelativeLayout { - private static final String TAG = "ShortVideoDemo:SuperShortVideoView"; - private static final int MAX_PLAYER_COUNT_ON_PASS = 3; - private View mRootView; - private RecyclerView mRecyclerView; - private ShortVideoPlayAdapter mAdapter; - private List mUrlList; - private LinearLayoutManager mLayoutManager; - private PagerSnapHelper mSnapHelper; - private int mLastPositionInIDLE = -1; - private TXVideoBaseView mBaseItemView; - private Handler mHandler; + private static final String TAG = "ShortVideoDemo:SuperShortVideoView"; + private static final int MAX_PLAYER_COUNT_ON_PASS = 3; + private View mRootView; + private RecyclerView mRecyclerView; + private ShortVideoPlayAdapter mAdapter; + private List mUrlList; + private LinearLayoutManager mLayoutManager; + private PagerSnapHelper mSnapHelper; + private int mLastPositionInIDLE = -1; + private TXVideoBaseView mBaseItemView; + private Handler mHandler; + private Object mLock = new Object(); public SuperShortVideoView(Context context) { super(context); @@ -80,7 +81,13 @@ public void setDataSource(final List dataSource) { TXLog.i(TAG, "[setDataSource]"); Message message = new Message(); message.obj = dataSource; - mHandler.sendMessage(message); + synchronized (mLock) { + if (mHandler == null) { + return; + } + mHandler.sendMessage(message); + } + } private void addListener() { @@ -160,8 +167,10 @@ public void pause() { } public void releasePlayer() { - mHandler.removeCallbacksAndMessages(null); - mHandler = null; + synchronized (mLock) { + mHandler.removeCallbacksAndMessages(null); + mHandler = null; + } if (mBaseItemView != null) { mBaseItemView.stopPlayer(); } diff --git a/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/expand/model/entity/VideoModel.java b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/expand/model/entity/VideoModel.java index 9cad2c3..f4b5021 100755 --- a/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/expand/model/entity/VideoModel.java +++ b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/expand/model/entity/VideoModel.java @@ -4,6 +4,8 @@ import com.tencent.liteav.demo.superplayer.model.VipWatchModel; import java.util.List; +import static com.tencent.liteav.demo.superplayer.SuperPlayerModel.PLAY_ACTION_AUTO_PLAY; + /** * Created by yuejiaoli on 2018/7/4. */ @@ -20,11 +22,18 @@ public class VideoModel { */ public String videoURL; + /** - * 视频封面本地图片 + * 从服务器拉取的封面图片 */ public String placeholderImage; + + /** + * 用户设置图片的接口 如果是本地图片前面加file:// + */ + public String coverPictureUrl; + /** * 视频时长 */ @@ -45,6 +54,9 @@ public class VideoModel { */ public String pSign; + public int playAction = PLAY_ACTION_AUTO_PLAY; + + /** * VIDEO 不同清晰度的URL链接 */ @@ -52,6 +64,9 @@ public class VideoModel { public int playDefaultIndex; // 指定多码率情况下,默认播放的连接Index public VipWatchModel vipWatchModel = null; + //feed流视频描述信息 + public String videoDescription = null; + public String videoMoreDescription = null; public static class VideoPlayerURL { diff --git a/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/expand/model/utils/SuperVodListLoader.java b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/expand/model/utils/SuperVodListLoader.java index c4b1898..32433ad 100644 --- a/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/expand/model/utils/SuperVodListLoader.java +++ b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/expand/model/utils/SuperVodListLoader.java @@ -4,6 +4,7 @@ import android.os.Handler; import android.os.HandlerThread; import android.text.TextUtils; +import android.util.Log; import com.tencent.liteav.basic.log.TXCLog; import com.tencent.liteav.demo.player.R; @@ -18,6 +19,7 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.List; import java.util.concurrent.TimeUnit; import okhttp3.Call; @@ -26,6 +28,9 @@ import okhttp3.Request; import okhttp3.Response; +import static com.tencent.liteav.demo.superplayer.SuperPlayerModel.PLAY_ACTION_AUTO_PLAY; +import static com.tencent.liteav.demo.superplayer.SuperPlayerModel.PLAY_ACTION_MANUAL_PLAY; + /** * Created by liyuejiao on 2018/7/3. * 获取点播信息 @@ -90,10 +95,17 @@ public ArrayList loadDefaultVodList(Context applicationContext) { model.vipWatchModel = new VipWatchModel(applicationContext.getString(R.string.superplayer_vip_watch_tip), 15); list.add(model); + model = new VideoModel(); + model.appid = 1500005830; + model.playAction = PLAY_ACTION_MANUAL_PLAY; + model.fileid = "8602268011437356984"; + model.coverPictureUrl = "http://1500005830.vod2.myqcloud.com/6c9a5118vodcq1500005830/cc1e28208602268011087336518/MXUW1a5I9TsA.png"; + list.add(model); + return list; } - public void getVodInfoOneByOne(ArrayList videoModels) { + public void getVodInfoOneByOne(List videoModels) { if (videoModels == null || videoModels.size() == 0) { return; } @@ -171,7 +183,6 @@ public void onResponse(Call call, Response response) throws IOException { model.appid = playItem.optInt("appId", 0); model.title = playItem.optString("name", ""); model.placeholderImage = playItem.optString("coverUrl", ""); - JSONArray urlList = playItem.getJSONArray("playUrl"); if (urlList.length() > 0) { @@ -227,9 +238,7 @@ private void parseJson(VideoModel videoModel, String content) { if (TextUtils.isEmpty(title)) { title = playInfoResponse.name(); } - if (videoModel.vipWatchModel != null) { - videoModel.title = title + videoModel.title; - } else { + if (videoModel.vipWatchModel == null) { videoModel.title = title; } if (mOnVodInfoLoadListener != null) { @@ -244,9 +253,7 @@ private void parseJson(VideoModel videoModel, String content) { if (TextUtils.isEmpty(title)) { title = basicInfo.optString("name"); } - if (videoModel.vipWatchModel != null) { - videoModel.title = title + videoModel.title; - } else { + if (videoModel.vipWatchModel == null) { videoModel.title = title; } videoModel.placeholderImage = basicInfo.optString("coverUrl"); diff --git a/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/FeedPlayerManager.java b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/FeedPlayerManager.java new file mode 100644 index 0000000..ae472e1 --- /dev/null +++ b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/FeedPlayerManager.java @@ -0,0 +1,97 @@ +package com.tencent.liteav.demo.player.feed; + +import com.tencent.liteav.demo.player.feed.player.FeedPlayerView; + +public class FeedPlayerManager { + + private boolean isPlaying = false; + private FeedPlayerView feedPlayerView = null; + private int lastPosition = -1; + + + /** + * 将正在播放的item添加进来 + * + * @param playerView + * @param position + */ + public void setPlayingFeedPlayerView(FeedPlayerView playerView, int position) { + if (lastPosition == position) { + return; + } + if (feedPlayerView != null) { + feedPlayerView.pause(); + } + lastPosition = position; + feedPlayerView = playerView; + } + + + public void removePlayingFeedPlayerView(int position) { + if (lastPosition == position) { + isPlaying = false; + lastPosition = -1; + feedPlayerView = null; + } + } + + + public int getLastPosition() { + return lastPosition; + } + + + public void onResume() { + if (isPlaying && feedPlayerView != null) { + isPlaying = false; + feedPlayerView.resume(); + } + } + + public void onPause() { + if (feedPlayerView == null) { + return; + } + if (feedPlayerView.isPlaying()) { + isPlaying = true; + } + feedPlayerView.pause(); + } + + public void reset() { + isPlaying = false; + lastPosition = -1; + feedPlayerView = null; + } + + public void destroy() { + if (feedPlayerView != null) { + feedPlayerView.stop(); + feedPlayerView.destroy(); + } + reset(); + } + + public boolean isPlaying() { + if (feedPlayerView != null) { + return feedPlayerView.isPlaying(); + } + return false; + } + + + /** + * 将全屏模式设置为窗口模式 + * + * @return true 表示消费了此次事件, + */ + public boolean setWindowPlayMode() { + if (feedPlayerView != null && feedPlayerView.isFullScreenPlay()) { + feedPlayerView.setWindowPlayMode(); + return true; + } + return false; + } + + +} diff --git a/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/FeedView.java b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/FeedView.java new file mode 100644 index 0000000..d053b42 --- /dev/null +++ b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/FeedView.java @@ -0,0 +1,230 @@ +package com.tencent.liteav.demo.player.feed; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.FrameLayout; + +import com.tencent.liteav.demo.player.expand.model.entity.VideoModel; +import com.tencent.liteav.demo.player.feed.feeddetailview.FeedDetailView; +import com.tencent.liteav.demo.player.feed.feeddetailview.FeedDetailViewCallBack; +import com.tencent.liteav.demo.player.feed.feedlistview.FeedListCallBack; +import com.tencent.liteav.demo.player.feed.feedlistview.FeedListItemView; +import com.tencent.liteav.demo.player.feed.feedlistview.FeedListView; +import com.tencent.liteav.demo.player.feed.player.FeedPlayerView; + +import java.util.List; + + +public class FeedView extends FrameLayout implements FeedListCallBack, FeedDetailViewCallBack { + + private FeedListView feedListView = null; + private FeedDetailView feedDetailView = null; + private FeedViewCallBack feedViewCallBack = null; + private FeedListItemView feedListItemView = null; //被点击的item + private boolean isShowDetailView = false; //是否展示了详情页面 + + + public FeedView(Context context) { + super(context); + initViews(); + } + + public FeedView(Context context, AttributeSet attrs) { + super(context, attrs); + initViews(); + } + + public FeedView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initViews(); + } + + /** + * 初始化界面元素 + */ + private void initViews() { + feedListView = new FeedListView(getContext()); + feedListView.setFeedListCallBack(this); + addView(feedListView); + feedDetailView = new FeedDetailView(getContext()); + feedDetailView.setFeedDetailViewCallBack(this); + } + + + /** + * 设置回调接口 + * + * @param callBack + */ + public void setFeedViewCallBack(FeedViewCallBack callBack) { + feedViewCallBack = callBack; + } + + + /** + * 添加数据 + * + * @param videoModels 数据列表 + * @param isCleanData 是否清理之前的数据 + */ + public void addData(List videoModels, boolean isCleanData) { + feedListView.addData(videoModels, isCleanData); + } + + + /** + * 结束下拉刷新 + * + * @param success + */ + public void finishRefresh(boolean success) { + feedListView.finishRefresh(success); + } + + /** + * 结束上拉加载 + * + * @param success + * @param noMoreData + */ + public void finishLoadMore(boolean success, boolean noMoreData) { + feedListView.finishLoadMore(success, noMoreData); + } + + /** + * 播放 + */ + public void onResume() { + feedListView.onResume(); + } + + /** + * 暂停 + */ + public void onPause() { + feedListView.onPause(); + } + + public void onDestroy() { + if (feedListView != null) { + feedListView.destroy(); + } + if (feedDetailView != null) { + feedDetailView.destroy(); + } + } + + /** + * 添加详情页列表数据 + */ + public void addDetailListData(List videoModels) { + if (isShowDetailView) { + feedDetailView.addDetailListData(videoModels); + } + } + + /** + * 是否消费返回事件 + * + * @return TRUE 表示feedView响应了此事件 + */ + public boolean goBack() { + if (feedListView.setWindowPlayMode()) { + return true; + } + if (isShowDetailView) { + isShowDetailView = false; + feedDetailView.removeFeedDetailView(feedListItemView == null ? 0 : feedListItemView.getTop()); + return true; + } + return false; + } + + + @Override + public void onLoadMore() { + if (feedViewCallBack != null) { + feedViewCallBack.onLoadMore(); + } + } + + @Override + public void onRefresh() { + if (feedViewCallBack != null) { + feedViewCallBack.onRefresh(); + } + } + + + @Override + public void onListItemClick(FeedPlayerManager feedPlayerManager, FeedListItemView itemView, VideoModel videoModel, int position) { + if (isShowDetailView) { + return; + } + isShowDetailView = true; + feedListItemView = itemView; + feedListItemView.removeFeedPlayFromItem(); + feedDetailView.showDetailView(this, videoModel, itemView.getFeedPlayerView(), Math.max(itemView.getTop(), 0)); + if (!itemView.getFeedPlayerView().isPlaying()) { + feedPlayerManager.setPlayingFeedPlayerView(itemView.getFeedPlayerView(), position); + itemView.resume(); + } + if (feedViewCallBack != null) { + feedViewCallBack.onStartDetailPage(); + } + } + + @Override + public void onRemoveDetailView(FeedPlayerView feedPlayerView, boolean isChangeVideo) { + if (feedViewCallBack != null) { + feedViewCallBack.onStopDetailPage(); + } + if (isChangeVideo) { + feedListItemView.play(feedListItemView.getVideoModel()); + } + feedListItemView.addFeedPlayToItem(); + } + + @Override + public void onStartFullScreenPlay() { + if (feedViewCallBack != null) { + feedViewCallBack.onStartFullScreenPlay(); + } + } + + @Override + public void onStopFullScreenPlay() { + if (feedViewCallBack != null) { + feedViewCallBack.onStopFullScreenPlay(); + } + } + + @Override + public void onLoadDetailData(VideoModel videoModel) { + if (feedViewCallBack != null) { + feedViewCallBack.onLoadDetailData(videoModel); + } + } + + @Override + public void onClickSmallReturnBtn() { + goBack(); + } + + @Override + public void onStartDetailFullScreenPlay(int position) { + feedListView.scrollToPosition(position); + if (feedViewCallBack != null) { + feedViewCallBack.onStartFullScreenPlay(); + } + } + + @Override + public void onStopDetailFullScreenPlay() { + if (feedViewCallBack != null) { + feedViewCallBack.onStopFullScreenPlay(); + } + } + + +} diff --git a/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/FeedViewCallBack.java b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/FeedViewCallBack.java new file mode 100644 index 0000000..405979c --- /dev/null +++ b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/FeedViewCallBack.java @@ -0,0 +1,23 @@ +package com.tencent.liteav.demo.player.feed; + + +import com.tencent.liteav.demo.player.expand.model.entity.VideoModel; + +public interface FeedViewCallBack { + + + void onLoadMore(); + + void onRefresh(); + + void onStartDetailPage(); + + void onStopDetailPage(); + + void onLoadDetailData(VideoModel videoModel); + + void onStartFullScreenPlay(); + + void onStopFullScreenPlay(); + +} diff --git a/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/anim/FeedAnim.java b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/anim/FeedAnim.java new file mode 100644 index 0000000..371c0dc --- /dev/null +++ b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/anim/FeedAnim.java @@ -0,0 +1,28 @@ +package com.tencent.liteav.demo.player.feed.anim; + +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.view.View; +import android.view.animation.LinearInterpolator; + +public class FeedAnim { + + + public static void moveAnim(View view, int start, int end, AnimatorListenerAdapter listenerAdapter) { + if (start == end) { + if (listenerAdapter != null) { + listenerAdapter.onAnimationEnd(null); + } + return; + } + ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(view, "translationY", start, end);//文字标签Y轴平移 + objectAnimator.setDuration(300); + objectAnimator.setInterpolator(new LinearInterpolator()); + if (listenerAdapter != null) { + objectAnimator.addListener(listenerAdapter); + } + objectAnimator.start(); + } + + +} diff --git a/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feeddetailview/FeedDetailAdapter.java b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feeddetailview/FeedDetailAdapter.java new file mode 100644 index 0000000..b8d54d7 --- /dev/null +++ b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feeddetailview/FeedDetailAdapter.java @@ -0,0 +1,98 @@ +package com.tencent.liteav.demo.player.feed.feeddetailview; + +import android.annotation.SuppressLint; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; +import com.tencent.liteav.demo.player.R; +import com.tencent.liteav.demo.player.expand.model.entity.VideoModel; + +import java.util.List; + + +/** + * 详情页面recycleview 的adapter + */ +public class FeedDetailAdapter extends RecyclerView.Adapter { + + + private List videoModels = null; + private FeedDetailListClickListener clickListener = null; + + public FeedDetailAdapter(FeedDetailListClickListener feedDetailListClickListener) { + clickListener = feedDetailListClickListener; + } + + public void setFeedEntityList(List videoModels) { + this.videoModels = videoModels; + notifyItemRangeChanged(0, getItemCount()); + } + + @NonNull + @Override + public FeedDetailItemHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.feedview_detailview_item_layout, parent, false); + return new FeedDetailItemHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull FeedDetailItemHolder holder, @SuppressLint("RecyclerView") final int position) { + VideoModel videoModel = videoModels.get(position); + Glide.with(holder.itemView.getContext()).load(videoModel.placeholderImage).into(holder.headImg); + holder.titleTxt.setText(videoModel.title); + holder.describeTxt.setText(videoModel.videoDescription); + holder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (clickListener != null) { + clickListener.onItemClickListener(videoModels.get(position), position); + } + } + }); + holder.durationTxt.setText(formattedTime(videoModel.duration)); + } + + @Override + public int getItemCount() { + return videoModels != null ? videoModels.size() : 0; + } + + private String formattedTime(long second) { + String formatTime; + long h = second / 3600; + long m = (second % 3600) / 60; + long s = (second % 3600) % 60; + if (h == 0) { + formatTime = String.format("%02d:%02d", m, s); + } else { + formatTime = String.format("%02d:%02d:%02d", h, m, s); + } + return formatTime; + } + + + static class FeedDetailItemHolder extends RecyclerView.ViewHolder { + + private ImageView headImg = null; + private TextView titleTxt = null; + private TextView describeTxt = null; + private TextView durationTxt = null; + + public FeedDetailItemHolder(@NonNull View itemView) { + super(itemView); + headImg = itemView.findViewById(R.id.feed_detail_holder_img); + titleTxt = itemView.findViewById(R.id.feed_detail_holder_title_txt); + describeTxt = itemView.findViewById(R.id.feed_detail_holder_describe_txt); + durationTxt = itemView.findViewById(R.id.feed_detail_holder_video_duration_tv); + } + } + + +} diff --git a/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feeddetailview/FeedDetailListClickListener.java b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feeddetailview/FeedDetailListClickListener.java new file mode 100644 index 0000000..9f28c31 --- /dev/null +++ b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feeddetailview/FeedDetailListClickListener.java @@ -0,0 +1,8 @@ +package com.tencent.liteav.demo.player.feed.feeddetailview; + +import com.tencent.liteav.demo.player.expand.model.entity.VideoModel; + +public interface FeedDetailListClickListener { + + void onItemClickListener(VideoModel videoModel, int position); +} diff --git a/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feeddetailview/FeedDetailView.java b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feeddetailview/FeedDetailView.java new file mode 100644 index 0000000..590d704 --- /dev/null +++ b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feeddetailview/FeedDetailView.java @@ -0,0 +1,224 @@ +package com.tencent.liteav.demo.player.feed.feeddetailview; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.resource.bitmap.CircleCrop; +import com.bumptech.glide.request.RequestOptions; +import com.tencent.liteav.demo.player.R; +import com.tencent.liteav.demo.player.expand.model.entity.VideoModel; +import com.tencent.liteav.demo.player.feed.FeedView; +import com.tencent.liteav.demo.player.feed.anim.FeedAnim; +import com.tencent.liteav.demo.player.feed.player.FeedPlayerView; + +import java.util.List; + +/** + * feed流详情页面 + */ +public class FeedDetailView extends FrameLayout implements FeedDetailListClickListener { + + private RecyclerView recyclerView = null; + private FeedDetailAdapter feedDetailAdapter = null; + private ImageView headImg = null; + private TextView titleTxt = null; + private TextView descriptionTxt = null; + private TextView detailDescriptionTxt = null; + private boolean isChangeVideo = false; //用于在详情页面是否播放了底部列表的视频,TRUE表示播放了 + private FeedPlayerView feedPlayerView = null; + private FeedDetailViewCallBack feedDetailViewCallBack = null; + private int playerViewHeight = 0; //PlayerView 的在窗口模式时的高度 + private FeedPlayerView.FeedPlayerCallBack feedPlayerCallBack = null; //用于存放之前给FeedPlayerView设置的callBack对象 + private RelativeLayout detailLayout = null; //详情页面用于展示视频介绍和视频列表的布局 + + public FeedDetailView(Context context) { + super(context); + initViews(); + } + + public FeedDetailView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + initViews(); + } + + public FeedDetailView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initViews(); + } + + private void initViews() { + setBackgroundResource(R.color.feed_page_bg); + LayoutInflater.from(getContext()).inflate(R.layout.feedview_detailview_layout, this, true); + detailLayout = findViewById(R.id.feed_detail_layout); + headImg = findViewById(R.id.feed_detail_layout_head_img); + titleTxt = findViewById(R.id.feed_detail_describe_layout_title_txt); + descriptionTxt = findViewById(R.id.feed_detail_layout_describe_txt); + detailDescriptionTxt = findViewById(R.id.feed_detail_layout_detail_describe_txt); + recyclerView = findViewById(R.id.feed_detail_layout_rv); + recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + feedDetailAdapter = new FeedDetailAdapter(this); + recyclerView.setAdapter(feedDetailAdapter); + } + + public void setFeedDetailViewCallBack(FeedDetailViewCallBack feedDetailViewCallBack) { + this.feedDetailViewCallBack = feedDetailViewCallBack; + } + + /** + * 设置视频的描述信息 + * + * @param videoModel + */ + private void setVideoDescription(VideoModel videoModel) { + RequestOptions options = RequestOptions.bitmapTransform(new CircleCrop()); + Glide.with(headImg.getContext()).load(videoModel.placeholderImage).apply(options).into(headImg); + titleTxt.setText(videoModel.title); + descriptionTxt.setText(videoModel.videoDescription); + detailDescriptionTxt.setText(videoModel.videoMoreDescription); + } + + /** + * 设置底部列表的数据 + * + * @param videoModels + */ + public void addDetailListData(List videoModels) { + feedDetailAdapter.setFeedEntityList(videoModels); + } + + + /** + * 添加详情页面到feedview中 + * + * @param parentView + */ + public void showDetailView(FeedView parentView, final VideoModel videoModel, FeedPlayerView playerView, int distanceY) { + feedPlayerView = playerView; + isChangeVideo = false; //还原为默认值 + parentView.addView(this, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + //保存之前的callback + feedPlayerCallBack = playerView.getFeedPlayerCallBack(); + //根据在列表页面视频比例,计算出在详情页面播放器的高度 + int phoneWidth = playerView.getContext().getResources().getDisplayMetrics().widthPixels; + playerViewHeight = playerView.getHeight() * phoneWidth / playerView.getWidth(); + LayoutParams layoutParams = (LayoutParams) detailLayout.getLayoutParams(); + layoutParams.topMargin = playerViewHeight; + detailLayout.setLayoutParams(layoutParams); + //设置新的callback回调 + feedPlayerView.setFeedPlayerCallBack(new FeedPlayerView.FeedPlayerCallBack() { + @Override + public void onStartFullScreenPlay() { + if (feedDetailViewCallBack != null) { + feedDetailViewCallBack.onStartDetailFullScreenPlay(feedPlayerView.getPosition()); + } + feedPlayerView.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + } + + @Override + public void onStopFullScreenPlay() { + if (feedDetailViewCallBack != null) { + feedDetailViewCallBack.onStopDetailFullScreenPlay(); + } + feedPlayerView.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, playerViewHeight)); + } + + @Override + public void onClickSmallReturnBtn() { + //小窗口的时候,左上角的返回事件 + if (feedDetailViewCallBack != null) { + feedDetailViewCallBack.onClickSmallReturnBtn(); + } + } + }); + //将播放器添加进view + addView(playerView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, playerViewHeight)); + //启动上移动画 + FeedAnim.moveAnim(playerView, distanceY, 0, new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + setVideoDescription(videoModel); + if (feedDetailViewCallBack != null) { + feedDetailViewCallBack.onLoadDetailData(videoModel); + } + } + }); + } + + + /** + * 移除详情页面 + * 1. 启动视频下移动画,动画结束时回调onMoveDownwardEnd()方法,在onMoveDownwardEnd方法中将feedPlayerView移除 + * 并调用回调接口feedDetailViewCallBack.onRemoveDetailView方法,告知 + */ + public void removeFeedDetailView(int itemTop) { + //先清理数据 + destroy(); + feedPlayerView.setFeedPlayerCallBack(feedPlayerCallBack); + FeedAnim.moveAnim(feedPlayerView, 0, itemTop, new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + if (feedDetailViewCallBack != null) { + feedDetailViewCallBack.onRemoveDetailView(feedPlayerView, isChangeVideo); + } + //将feedDetail页面从View树中移除 + ((ViewGroup) getParent()).removeView(FeedDetailView.this); + } + }); + } + + + /** + * 当点击详情页面底部视频列表时触发此事件,此时可在此处进行视频播放 + * + * @param entity + * @param position + */ + @Override + public void onItemClickListener(VideoModel entity, int position) { + isChangeVideo = true; + if (feedPlayerView != null) { + feedPlayerView.play(entity); + } + setVideoDescription(entity); + } + + /** + * 对页面数据进行清除 + */ + public void destroy() { + //清理描述信息 + headImg.setImageResource(0); + titleTxt.setText(""); + descriptionTxt.setText(""); + detailDescriptionTxt.setText(""); + //清理掉列表数据 + if (recyclerView != null && recyclerView.getChildCount() > 0) { + recyclerView.removeAllViews(); + feedDetailAdapter.setFeedEntityList(null); + } + } + + /** + * 当此页面从父控件移除的时候调用, + */ + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + destroy(); + } +} diff --git a/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feeddetailview/FeedDetailViewCallBack.java b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feeddetailview/FeedDetailViewCallBack.java new file mode 100644 index 0000000..3bd3a67 --- /dev/null +++ b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feeddetailview/FeedDetailViewCallBack.java @@ -0,0 +1,19 @@ +package com.tencent.liteav.demo.player.feed.feeddetailview; + +import com.tencent.liteav.demo.player.expand.model.entity.VideoModel; +import com.tencent.liteav.demo.player.feed.player.FeedPlayerView; + +public interface FeedDetailViewCallBack { + + void onRemoveDetailView(FeedPlayerView feedPlayerView, boolean isChangeVideo); + + //实现此方法,在此方法中获取详情页面底部列表数据,feedEntity是传入详情页的视频信息 + void onLoadDetailData(VideoModel videoModel); + + void onClickSmallReturnBtn(); + + void onStartDetailFullScreenPlay(int position); + + void onStopDetailFullScreenPlay(); + +} diff --git a/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feedlistview/FeedLinearSmoothScroller.java b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feedlistview/FeedLinearSmoothScroller.java new file mode 100644 index 0000000..b46e693 --- /dev/null +++ b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feedlistview/FeedLinearSmoothScroller.java @@ -0,0 +1,73 @@ +package com.tencent.liteav.demo.player.feed.feedlistview; + + +import android.content.Context; + +import androidx.recyclerview.widget.LinearSmoothScroller; + +public class FeedLinearSmoothScroller extends LinearSmoothScroller { + private Context context = null; + private int itemHeight = 0; + + public FeedLinearSmoothScroller(Context context, int height) { + super(context); + this.context = context; + itemHeight = height; + } + + + @Override + protected int getVerticalSnapPreference() { + return LinearSmoothScroller.SNAP_TO_START; + } + + + @Override + protected void onStop() { + super.onStop(); + } + + @Override + public int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int snapPreference) { + switch (snapPreference) { + case SNAP_TO_START: + int phoneHeight = context.getResources().getDisplayMetrics().heightPixels; + int height = (int) (phoneHeight - dp2px(context, 50) - getStatusBarHeightCompat(context)); + return boxStart - viewStart + (height - itemHeight) / 2; + case SNAP_TO_END: + return boxEnd - viewEnd; + case SNAP_TO_ANY: + final int dtStart = boxStart - viewStart; + if (dtStart > 0) { + return dtStart; + } + final int dtEnd = boxEnd - viewEnd; + if (dtEnd < 0) { + return dtEnd; + } + break; + default: + throw new IllegalArgumentException("snap preference should be one of the" + + " constants defined in SmoothScroller, starting with SNAP_"); + } + return 0; + } + + private float dp2px(Context context, float dp) { + final float scale = context.getResources().getDisplayMetrics().density; + return dp * scale + 0.5f; + } + + private int getStatusBarHeightCompat(Context context) { + int result = 0; + int resId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); + if (resId > 0) { + result = context.getResources().getDimensionPixelOffset(resId); + } + if (result <= 0) { + result = (int) dp2px(context, 25); + } + return result; + } + +} diff --git a/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feedlistview/FeedListAdapter.java b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feedlistview/FeedListAdapter.java new file mode 100644 index 0000000..2ae50a3 --- /dev/null +++ b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feedlistview/FeedListAdapter.java @@ -0,0 +1,139 @@ +package com.tencent.liteav.demo.player.feed.feedlistview; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.tencent.liteav.demo.player.expand.model.entity.VideoModel; +import com.tencent.liteav.demo.player.feed.FeedPlayerManager; + +import java.util.ArrayList; +import java.util.List; + + +public class FeedListAdapter extends RecyclerView.Adapter { + + private List videoModels = null; + private FeedListItemView.FeedListItemViewCallBack feedListItemViewCallBack = null; + private FeedPlayerManager feedPlayerManager = null; + private List listItemViews = new ArrayList<>(); + public int listItemHeight = 0; //列表item 高度 + + public FeedListAdapter(Context context, FeedListItemView.FeedListItemViewCallBack itemCallBack, FeedPlayerManager feedPlayerManager) { + this.feedListItemViewCallBack = itemCallBack; + this.feedPlayerManager = feedPlayerManager; + int videoViewWidth = (int) (context.getResources().getDisplayMetrics().widthPixels - dp2px(context, 20)); + int videoViewHeight = videoViewWidth * 9 / 16; + listItemHeight = (int) (videoViewHeight + dp2px(context, 20 + 55)); + } + + + /** + * 添加数据 + * + * @param videoModels 数据列表 + * @param isCleanData TRUE 表示清理之前的数据 + */ + public void addVideoData(List videoModels, boolean isCleanData) { + if (isCleanData) { + if (this.videoModels != null) { + this.videoModels.clear(); + } + this.videoModels = videoModels; + notifyDataSetChanged(); + } else { + if (videoModels != null && videoModels.size() > 0) { + if (this.videoModels == null) { + this.videoModels = new ArrayList<>(); + } + int size = this.videoModels.size(); + this.videoModels.addAll(videoModels); + notifyItemRangeInserted(size, getItemCount() - size); + } + } + } + + + @NonNull + @Override + public FeedListItemHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + FeedListItemView feedListItemView = new FeedListItemView(parent.getContext()); + feedListItemView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, listItemHeight)); + listItemViews.add(feedListItemView); + return new FeedListItemHolder(feedListItemView); + } + + @Override + public void onBindViewHolder(@NonNull final FeedListItemHolder holder, @SuppressLint("RecyclerView") final int position) { + holder.itemView.setTag(position); + } + + + @Override + public void onViewAttachedToWindow(@NonNull final FeedListItemHolder holder) { + super.onViewAttachedToWindow(holder); + final int position = (int) holder.itemView.getTag(); + holder.feedListItemView.bindItemData(videoModels.get(position), feedListItemViewCallBack, position); + holder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (feedListItemViewCallBack != null) { + feedListItemViewCallBack.onItemClick(holder.feedListItemView, videoModels.get(position), position); + } + } + }); + holder.feedListItemView.getFeedPlayerView().setFeedPlayerManager(feedPlayerManager); + } + + @Override + public void onViewDetachedFromWindow(@NonNull FeedListItemHolder holder) { + super.onViewDetachedFromWindow(holder); + holder.feedListItemView.stop(); + } + + @Override + public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) { + super.onDetachedFromRecyclerView(recyclerView); + for (FeedListItemView itemView : listItemViews) { + itemView.destroy(); + } + listItemViews.clear(); + } + + /** + * 此处可以对itemView进行还原处理, + * + * @param holder + */ + @Override + public void onViewRecycled(@NonNull FeedListItemHolder holder) { + super.onViewRecycled(holder); + holder.feedListItemView.reset(); + } + + + @Override + public int getItemCount() { + return this.videoModels != null ? this.videoModels.size() : 0; + } + + private float dp2px(Context context, float dp) { + final float scale = context.getResources().getDisplayMetrics().density; + return dp * scale + 0.5f; + } + + static class FeedListItemHolder extends RecyclerView.ViewHolder { + public FeedListItemView feedListItemView; + + public FeedListItemHolder(@NonNull View itemView) { + super(itemView); + if (itemView instanceof FeedListItemView) { + feedListItemView = (FeedListItemView) itemView; + } + } + } +} diff --git a/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feedlistview/FeedListCallBack.java b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feedlistview/FeedListCallBack.java new file mode 100644 index 0000000..3c3eb46 --- /dev/null +++ b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feedlistview/FeedListCallBack.java @@ -0,0 +1,18 @@ +package com.tencent.liteav.demo.player.feed.feedlistview; + +import com.tencent.liteav.demo.player.expand.model.entity.VideoModel; +import com.tencent.liteav.demo.player.feed.FeedPlayerManager; + +public interface FeedListCallBack { + + void onLoadMore(); + + void onRefresh(); + + void onListItemClick(FeedPlayerManager feedPlayerManager,FeedListItemView itemView, VideoModel videoModel, int position); + + void onStartFullScreenPlay(); + + void onStopFullScreenPlay(); + +} diff --git a/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feedlistview/FeedListItemView.java b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feedlistview/FeedListItemView.java new file mode 100644 index 0000000..da69b65 --- /dev/null +++ b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feedlistview/FeedListItemView.java @@ -0,0 +1,219 @@ +package com.tencent.liteav.demo.player.feed.feedlistview; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import androidx.annotation.Nullable; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.resource.bitmap.CircleCrop; +import com.bumptech.glide.request.RequestOptions; +import com.tencent.liteav.demo.player.R; +import com.tencent.liteav.demo.player.expand.model.entity.VideoModel; +import com.tencent.liteav.demo.player.feed.player.FeedPlayerView; + + +/** + * feed流列表页item + * 包含播放器FeedPlayerView以及展示视频描述信息的View + */ +public class FeedListItemView extends RelativeLayout { + + private FeedPlayerView feedPlayerView; + private ImageView headImg; + private TextView titleTxt; + private TextView describeTxt; + private FeedListItemViewCallBack feedListItemViewCallBack = null; + private VideoModel videoModel = null; //数据 + private LayoutParams playerLayoutParams = null; + + + public FeedListItemView(Context context) { + super(context); + initViews(); + } + + public FeedListItemView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + initViews(); + } + + public FeedListItemView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initViews(); + } + + /** + * 初始化界面 + */ + private void initViews() { + RelativeLayout relativeLayout = (RelativeLayout) LayoutInflater.from(getContext()).inflate(R.layout.feedview_list_item_layout, this, false); + headImg = relativeLayout.findViewById(R.id.feed_list_holder_head_img); + titleTxt = relativeLayout.findViewById(R.id.feed_list_holder_title_txt); + describeTxt = relativeLayout.findViewById(R.id.feed_list_holder_describe_txt); + RelativeLayout describeLayout = relativeLayout.findViewById(R.id.feed_list_item_describe_layout); + feedPlayerView = relativeLayout.findViewById(R.id.feed_list_item_player); + playerLayoutParams = (LayoutParams) feedPlayerView.getLayoutParams(); + relativeLayout.removeAllViews(); + addView(describeLayout); + addView(feedPlayerView, playerLayoutParams); + } + + + /** + * 给item设置数据,并预加载视频信息 + * + * @param videoModel + * @param callBack + * @param position + */ + public void bindItemData(VideoModel videoModel, FeedListItemViewCallBack callBack, final int position) { + this.videoModel = videoModel; + RequestOptions options = RequestOptions.bitmapTransform(new CircleCrop()); + Glide.with(headImg.getContext()).load(videoModel.placeholderImage).apply(options).into(headImg); + titleTxt.setText(videoModel.title); + describeTxt.setText(videoModel.videoDescription); + feedListItemViewCallBack = callBack; + feedPlayerView.setFeedPlayerCallBack(new FeedPlayerView.FeedPlayerCallBack() { + @Override + public void onStartFullScreenPlay() { + if (feedListItemViewCallBack != null) { + feedListItemViewCallBack.onStartFullScreenPlay(FeedListItemView.this, position); + } + } + + @Override + public void onStopFullScreenPlay() { + if (feedListItemViewCallBack != null) { + feedListItemViewCallBack.onStopFullScreenPlay(FeedListItemView.this); + } + } + + @Override + public void onClickSmallReturnBtn() { + + } + + }); + feedPlayerView.preparePlayVideo(position, videoModel); + } + + /** + * 直接播放个视频 + * + * @param videoModel + */ + public void play(VideoModel videoModel) { + if (feedPlayerView != null) { + feedPlayerView.play(videoModel); + } + } + + public void resume() { + if (feedPlayerView != null) { + feedPlayerView.resume(); + } + } + + public void pause() { + if (feedPlayerView != null) { + feedPlayerView.pause(); + } + } + + public void stop() { + if (feedPlayerView != null) { + feedPlayerView.stop(); + } + } + + /** + * 还原ItemView + */ + public void reset() { + if (feedPlayerView != null) { + feedPlayerView.reset(); + } + } + + /** + * 销毁item时调用 + */ + public void destroy() { + if (feedPlayerView != null) { + feedPlayerView.destroy(); + } + } + + public VideoModel getVideoModel() { + return videoModel; + } + + /** + * 从item中移除播放器 + */ + public void removeFeedPlayFromItem() { + removeView(feedPlayerView); + } + + /** + * 将播放器添加进item + */ + public void addFeedPlayToItem() { + ((ViewGroup) feedPlayerView.getParent()).removeView(feedPlayerView); + feedPlayerView.setTranslationY(0); + addView(feedPlayerView, playerLayoutParams); + } + + + /** + * 获取播放器控件 + * + * @return + */ + public FeedPlayerView getFeedPlayerView() { + return feedPlayerView; + } + + + /** + * 获取播放器底部Y的坐标,相对于父控件的 + * + * @return + */ + public int getPlayerDisY() { + return getTop() + feedPlayerView.getBottom(); + } + + + /** + * 获取item中视频控件之上的view的高度 + * 因为此item的视频控件在item的最顶部,所以返回0 + * + * @return + */ + public int getItemTopLayoutHeight() { + return (int) dp2px(getContext(), 10); + } + + + private float dp2px(Context context, float dp) { + final float scale = context.getResources().getDisplayMetrics().density; + return dp * scale + 0.5f; + } + + public interface FeedListItemViewCallBack { + + void onItemClick(FeedListItemView itemView, VideoModel videoModel, int position); + + void onStartFullScreenPlay(FeedListItemView itemView, int position); + + void onStopFullScreenPlay(FeedListItemView itemView); + } + +} diff --git a/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feedlistview/FeedListScrollListener.java b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feedlistview/FeedListScrollListener.java new file mode 100644 index 0000000..e653e68 --- /dev/null +++ b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feedlistview/FeedListScrollListener.java @@ -0,0 +1,139 @@ +package com.tencent.liteav.demo.player.feed.feedlistview; + +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.tencent.liteav.demo.player.feed.FeedPlayerManager; + + +public class FeedListScrollListener extends RecyclerView.OnScrollListener { + + private FeedPlayerManager feedPlayerManager = null; + + public FeedListScrollListener(FeedPlayerManager feedPlayerManager) { + this.feedPlayerManager = feedPlayerManager; + } + + /** + * 当刷新或者第一次添加数据的时候调用此方法 + * + * @param recyclerView + * @param newState + */ + public void firstPlayItem(@NonNull final RecyclerView recyclerView, int newState) { + if (feedPlayerManager != null) { + feedPlayerManager.reset(); + } + onScrollStateChanged(recyclerView, newState); + } + + + @Override + public void onScrollStateChanged(@NonNull final RecyclerView recyclerView, int newState) { + super.onScrollStateChanged(recyclerView, newState); + if (newState == RecyclerView.SCROLL_STATE_IDLE) { //滑动停止 + onScrollIdle(recyclerView); + } + } + + + private void onScrollIdle(RecyclerView recyclerView) { + if (recyclerView != null && recyclerView.getLayoutManager() instanceof LinearLayoutManager) { + LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); + int firstPosition = linearLayoutManager.findFirstVisibleItemPosition(); + int lastPosition = linearLayoutManager.findLastVisibleItemPosition(); + findFocusItem(linearLayoutManager, firstPosition, lastPosition, recyclerView.getHeight()); + } + } + + /** + * 查找第一个videoView完全展示的view + * + * @param firstPosition + * @param lastPosition + */ + private void findFocusItem(LinearLayoutManager linearLayoutManager, int firstPosition, int lastPosition, int recycleViewHeight) { + int focusStartItemPosition = -1; //第一个完全可见播放器的item的游标 + if (firstPosition == lastPosition) { //表示页面中只有一个item可见 + focusStartItemPosition = firstPosition; + } else if (lastPosition - firstPosition == 1) { //表示页面中有两个item 可见 + View firstView = linearLayoutManager.findViewByPosition(firstPosition); + View lastView = linearLayoutManager.findViewByPosition(lastPosition); + if (firstView != null && firstView.getTop() >= -((FeedListItemView) firstView).getItemTopLayoutHeight()) { + focusStartItemPosition = firstPosition; + } else if (lastView != null && ((FeedListItemView) lastView).getPlayerDisY() <= recycleViewHeight) { + focusStartItemPosition = lastPosition; + } + } else { //表示页面中至少有三个item可见 + View firstView = linearLayoutManager.findViewByPosition(firstPosition); + if (firstView == null) { + return; + } + if (firstView.getTop() >= -((FeedListItemView) firstView).getItemTopLayoutHeight()) { //表示第一个item的播放器完全可见 + focusStartItemPosition = firstPosition; + } else { + focusStartItemPosition = firstPosition + 1; + } + } + if (checkFocusItem(linearLayoutManager, focusStartItemPosition, lastPosition, recycleViewHeight)) { + return; + } + onItemFocus(linearLayoutManager, focusStartItemPosition); + } + + + /** + * 此方法返回TRUE 表示在可见的范围内找到了,上次正在播放的item,并且上次播放的这个item的播放器也是完全可见的,所以这个时候就不必播放新的item + * + * @param linearLayoutManager + * @param focusStartItemPosition + * @param lastPosition + * @param recycleViewHeight + * @return + */ + private boolean checkFocusItem(LinearLayoutManager linearLayoutManager, int focusStartItemPosition, int lastPosition, int recycleViewHeight) { + //focusStartItemPosition为-1表示没有找到符合条件的item,这个时候直接停止上次的item + if (focusStartItemPosition == -1) { + feedPlayerManager.onPause(); + return true; + } + for (int index = focusStartItemPosition; index <= lastPosition; index++) { + //修改后的 + //如果正在播放的item的游标和当前的相等,且这个item不是最后一个游标,如果是最后一个可见的item,则需判断最后一个item的播放器是否也完全可见 + if (index != feedPlayerManager.getLastPosition()) { + continue; + } + if (!feedPlayerManager.isPlaying()) { + continue; + } + FeedListItemView itemView = (FeedListItemView) linearLayoutManager.findViewByPosition(index); + if ((index != lastPosition || itemView.getPlayerDisY() <= recycleViewHeight)) { + return true; + } + } + return false; + } + + + /** + * 当滑动结束的时候调用次方法,如果返回-1表示屏幕中没有一个符合条件的的item + * + * @param position 符合条件的itemView的位置 + */ + private void onItemFocus(LinearLayoutManager linearLayoutManager, int position) { + //找到的item与上次播放的item是一个,并且上次的正在播放 + if (feedPlayerManager.getLastPosition() == position && feedPlayerManager.isPlaying()) { + return; + } + View view = linearLayoutManager.findViewByPosition(position); + if (view instanceof FeedListItemView) { + feedPlayerManager.setPlayingFeedPlayerView(((FeedListItemView) view).getFeedPlayerView(), position); + ((FeedListItemView) view).resume(); + } + } + + +} diff --git a/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feedlistview/FeedListView.java b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feedlistview/FeedListView.java new file mode 100644 index 0000000..ac77c16 --- /dev/null +++ b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/feedlistview/FeedListView.java @@ -0,0 +1,238 @@ +package com.tencent.liteav.demo.player.feed.feedlistview; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.LinearSmoothScroller; +import androidx.recyclerview.widget.RecyclerView; + +import com.scwang.smart.refresh.footer.ClassicsFooter; +import com.scwang.smart.refresh.header.ClassicsHeader; +import com.scwang.smart.refresh.layout.SmartRefreshLayout; +import com.scwang.smart.refresh.layout.api.RefreshLayout; +import com.scwang.smart.refresh.layout.listener.OnLoadMoreListener; +import com.scwang.smart.refresh.layout.listener.OnRefreshListener; +import com.tencent.liteav.demo.player.R; +import com.tencent.liteav.demo.player.expand.model.entity.VideoModel; +import com.tencent.liteav.demo.player.feed.FeedPlayerManager; + +import java.util.List; + +/** + * feed流主界面使用 + * 在FrameLayout中添加SmartRefreshLayout 在SmartRefreshLayout中添加一个RecycleView + */ +public class FeedListView extends FrameLayout implements FeedListItemView.FeedListItemViewCallBack { + + private RecyclerView recyclerView = null; + private FeedListCallBack feedListCallBack = null; + private SmartRefreshLayout refreshLayout = null; + private FeedListAdapter feedListAdapter = null; + private FeedListScrollListener feedListScrollListener = null; + private LinearSmoothScroller linearSmoothScroller = null; + private FeedPlayerManager feedPlayerManager = null; + + public FeedListView(Context context) { + super(context); + initViews(); + } + + public FeedListView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + initViews(); + } + + public FeedListView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initViews(); + } + + /** + * 初始化界面元素 + */ + private void initViews() { + feedPlayerManager = new FeedPlayerManager(); + refreshLayout = new SmartRefreshLayout(getContext()); + refreshLayout.setBackgroundResource(R.color.feed_page_bg); + refreshLayout.setRefreshHeader(new ClassicsHeader(getContext())); + refreshLayout.setRefreshFooter(new ClassicsFooter(getContext())); + refreshLayout.setEnableAutoLoadMore(false); + refreshLayout.setEnableOverScrollBounce(false);//是否启用越界回弹 + recyclerView = new RecyclerView(getContext()); + recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + recyclerView.setItemViewCacheSize(0); + recyclerView.setHasFixedSize(true); + feedListAdapter = new FeedListAdapter(getContext(), this, feedPlayerManager); + recyclerView.setAdapter(feedListAdapter); + feedListScrollListener = new FeedListScrollListener(feedPlayerManager); + recyclerView.addOnScrollListener(feedListScrollListener); + refreshLayout.setRefreshContent(recyclerView); + addView(refreshLayout); + refreshLayout.setOnRefreshListener(new OnRefreshListener() { + @Override + public void onRefresh(@NonNull RefreshLayout refreshlayout) { + if (feedListCallBack != null) { + feedListCallBack.onRefresh(); + } + } + }); + refreshLayout.setOnLoadMoreListener(new OnLoadMoreListener() { + @Override + public void onLoadMore(@NonNull RefreshLayout refreshlayout) { + if (feedListCallBack != null) { + feedListCallBack.onLoadMore(); + } + } + }); + linearSmoothScroller = new FeedLinearSmoothScroller(getContext(), feedListAdapter.listItemHeight); + } + + + /** + * 设置feedlistview 回调接口 + * + * @param callBack + */ + public void setFeedListCallBack(FeedListCallBack callBack) { + feedListCallBack = callBack; + } + + + /** + * 添加列表数据 + * + * @param videoModels 数据列表 + * @param isCleanData 是否清理之前的数据 + */ + public void addData(List videoModels, boolean isCleanData) { + feedListAdapter.addVideoData(videoModels, isCleanData); + if (isCleanData && videoModels != null && videoModels.size() > 0) { + postDelayed(new Runnable() { + @Override + public void run() { + feedListScrollListener.firstPlayItem(recyclerView, RecyclerView.SCROLL_STATE_IDLE); + } + }, 1000); + } + } + + /** + * 结束下拉刷新动画 + * + * @param success + */ + public void finishRefresh(boolean success) { + refreshLayout.finishRefresh(success); + } + + /** + * 结束上拉加载动画 + * + * @param success + * @param noMoreData + */ + public void finishLoadMore(boolean success, boolean noMoreData) { + refreshLayout.finishLoadMore(0, success, noMoreData); + } + + + public void onResume() { + feedPlayerManager.onResume(); + } + + public void onPause() { + feedPlayerManager.onPause(); + } + + /** + * 销毁时调用 + */ + public void destroy() { + if (feedPlayerManager != null) { + feedPlayerManager.destroy(); + } + //清理掉列表数据 + if (recyclerView != null) { + recyclerView.setAdapter(null); + recyclerView = null; + refreshLayout.removeAllViews(); + } + } + + + /** + * 点击RecycleView item 回调方法 + * + * @param itemView + * @param position + */ + @Override + public void onItemClick(FeedListItemView itemView, VideoModel videoModel, int position) { + linearSmoothScroller.setTargetPosition(position); + recyclerView.getLayoutManager().startSmoothScroll(linearSmoothScroller); + if (feedListCallBack != null) { + feedListCallBack.onListItemClick(feedPlayerManager, itemView, videoModel, position); + } + } + + /** + * 播放器全屏回调方法,在此方法中将全屏事件通知给feedView + * + * @param feedListItemView + * @param position + */ + @Override + public void onStartFullScreenPlay(FeedListItemView feedListItemView, int position) { + recyclerView.removeOnScrollListener(feedListScrollListener); + scrollToPosition(position); + if (feedListCallBack != null) { + feedListCallBack.onStartFullScreenPlay(); + } + feedListItemView.removeFeedPlayFromItem(); + addView(feedListItemView.getFeedPlayerView(), new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + if (!feedListItemView.getFeedPlayerView().isPlaying()) { + feedPlayerManager.setPlayingFeedPlayerView(feedListItemView.getFeedPlayerView(), position); + feedListItemView.resume(); + } + } + + public void scrollToPosition(int position) { + if (recyclerView != null) { + recyclerView.scrollToPosition(position); + } + } + + + /** + * 播放器小窗口事件,在此方法中将此事件通知feedView + * + * @param feedListItemView + */ + @Override + public void onStopFullScreenPlay(FeedListItemView feedListItemView) { + recyclerView.addOnScrollListener(feedListScrollListener); + if (feedListCallBack != null) { + feedListCallBack.onStopFullScreenPlay(); + } + feedListItemView.addFeedPlayToItem(); + } + + + /** + * 将全屏模式设置为窗口模式 + * + * @return true 表示消费了此次事件, + */ + public boolean setWindowPlayMode() { + if (feedPlayerManager != null) { + return feedPlayerManager.setWindowPlayMode(); + } else { + return false; + } + } +} diff --git a/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/model/FeedVodListLoader.java b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/model/FeedVodListLoader.java new file mode 100644 index 0000000..d03adca --- /dev/null +++ b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/model/FeedVodListLoader.java @@ -0,0 +1,252 @@ +package com.tencent.liteav.demo.player.feed.model; + +import static com.tencent.liteav.demo.superplayer.SuperPlayerModel.PLAY_ACTION_PRELOAD; + +import android.os.Handler; +import android.os.Looper; +import android.text.TextUtils; + +import com.tencent.liteav.demo.player.expand.model.entity.VideoModel; +import com.tencent.liteav.demo.player.expand.model.utils.SuperVodListLoader; +import com.tencent.liteav.demo.superplayer.SuperPlayerModel; +import com.tencent.liteav.demo.superplayer.SuperPlayerVideoId; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; + +public class FeedVodListLoader { + + private Handler handler = new Handler(Looper.getMainLooper()); + + private List loadDefaultVodList() { + List list = new ArrayList<>(); + VideoModel model = new VideoModel(); + model.appid = 1252463788; + model.fileid = "5285890781763144364"; + model.playAction = PLAY_ACTION_PRELOAD; + model.videoDescription = "现有超级播放器demo-点播列表-腾讯云"; + model.videoMoreDescription = "腾讯多年技术沉淀,300+ 款产品共筑腾讯云产品矩阵,从基础设施到行业应用领域,腾讯云提供完善的产品体系,助力您的业务腾飞。"; + list.add(model); + + model = new VideoModel(); + model.appid = 1252463788; + model.fileid = "4564972819220421305"; + model.playAction = PLAY_ACTION_PRELOAD; + model.videoDescription = "腾讯云短视频演示"; + model.videoMoreDescription = "短视频 (User Generated Short Video,UGSV)基于腾讯云强大的上传、存储、转码、分发的云点播能力,提供集成了采集、剪辑、拼接、特效、分享、播放等功能的客户端 SDK。"; + list.add(model); + + model = new VideoModel(); + model.appid = 1252463788; + model.fileid = "4564972819219071568"; + model.playAction = PLAY_ACTION_PRELOAD; + model.videoDescription = "小直播app基础功能"; + model.videoMoreDescription = "基于云直播服务、即时通信(IM)和对象存储服务(COS)构建,并使用云服务器(CVM)提供简单的后台服务,实现多项直播功能。"; + list.add(model); + + model = new VideoModel(); + model.appid = 1252463788; + model.fileid = "4564972819219071668"; + model.playAction = PLAY_ACTION_PRELOAD; + model.videoDescription = "利用小直播app实现连麦互动、文字互动和弹幕消息等功能"; + model.videoMoreDescription = "基于云直播服务、即时通信(IM)和对象存储服务(COS)构建,并使用云服务器(CVM)提供简单的后台服务,实现多项直播功能。"; + list.add(model); + + model = new VideoModel(); + model.appid = 1252463788; + model.fileid = "4564972819219071679"; + model.playAction = PLAY_ACTION_PRELOAD; + model.videoDescription = "可以实现登录、注册、开播、房间列表、连麦互动、文字互动和弹幕消息等功能"; + model.videoMoreDescription = "基于云直播服务、即时通信(IM)和对象存储服务(COS)构建,并使用云服务器(CVM)提供简单的后台服务,实现多项直播功能。"; + list.add(model); + + model = new VideoModel(); + model.appid = 1500005830; + model.fileid = "8602268011437356984"; + model.playAction = PLAY_ACTION_PRELOAD; + model.videoDescription = "一站式 VPaaS (Video Platform as a Service)解决方案"; + model.videoMoreDescription = "腾讯云点播(Video on Demand,VOD)基于腾讯多年技术积累与基础设施建设,为有音视频应用相关需求的客户提供包括音视频存储管理、音视频转码处理、音视频加速播放和音视频通信服务的一站式解决方案。"; + list.add(model); + + return list; + } + + /** + * 根据视频ID 获取视频标题 + * + * @param fileId + * @return + */ + private String getTitleByFileId(String fileId) { + String title = ""; + switch (fileId) { + case "5285890781763144364": + title = "腾讯云介绍"; + break; + case "4564972819220421305": + title = "小视频app"; + break; + case "4564972819219071568": + title = "小直播直播美颜、观众评论点赞等基础功能"; + break; + case "4564972819219071668": + title = "小直播app主播连麦"; + break; + case "4564972819219071679": + title = "小直播app-在线直播解决方案"; + break; + case "8602268011437356984": + title = "2分钟带你认识云点播"; + break; + default: + break; + } + return title; + } + + + /** + * 获取数据 + * + * @param loadDataCallBack + */ + public void loadListData(int page, final LoadDataCallBack loadDataCallBack) { + int random = getRandomNumber(1, 5); + List videoModelList = page == 0 ? loadDefaultVodList() : loadDefaultVodList().subList(0, random); + final int size = videoModelList.size(); + SuperVodListLoader mSuperVodListLoader = new SuperVodListLoader(); + mSuperVodListLoader.setOnVodInfoLoadListener(new SuperVodListLoader.OnVodInfoLoadListener() { + int count = 0; + List resultList = new ArrayList<>(); + + @Override + public void onSuccess(VideoModel videoModel) { + videoModel.title = getTitleByFileId(videoModel.fileid); + resultCallBack(videoModel); + } + + @Override + public void onFail(int errCode) { + resultCallBack(null); + } + + private void resultCallBack(final VideoModel videoModel) { + handler.post(new Runnable() { + @Override + public void run() { + if (videoModel != null) { + resultList.add(videoModel); + } + count++; + if (count != size) { + return; + } + if (resultList.size() > 0) { + loadDataCallBack.onLoadedData(resultList); + } else { + loadDataCallBack.onError(-1); + } + } + }); + } + }); + mSuperVodListLoader.getVodInfoOneByOne(videoModelList); + } + + + public interface LoadDataCallBack { + void onLoadedData(List videoModels); + + void onError(int errorCode); + } + + + /** + * 将VideoModel 转换为SuperPlayerModel + * + * @param videoModel + * @return + */ + public static SuperPlayerModel conversionModel(VideoModel videoModel) { + SuperPlayerModel superPlayerModelV3 = new SuperPlayerModel(); + superPlayerModelV3.appId = videoModel.appid; + superPlayerModelV3.vipWatchMode = videoModel.vipWatchModel; + if (!TextUtils.isEmpty(videoModel.videoURL)) { + if (isSuperPlayerVideo(videoModel)) { + return playSuperPlayerVideo(videoModel); + } else { + superPlayerModelV3.title = videoModel.title; + superPlayerModelV3.url = videoModel.videoURL; + + superPlayerModelV3.multiURLs = new ArrayList<>(); + if (videoModel.multiVideoURLs != null) { + for (VideoModel.VideoPlayerURL modelURL : videoModel.multiVideoURLs) { + superPlayerModelV3.multiURLs.add(new SuperPlayerModel.SuperPlayerURL(modelURL.url, modelURL.title)); + } + } + } + } else if (!TextUtils.isEmpty(videoModel.fileid)) { + superPlayerModelV3.videoId = new SuperPlayerVideoId(); + superPlayerModelV3.videoId.fileId = videoModel.fileid; + superPlayerModelV3.videoId.pSign = videoModel.pSign; + } + superPlayerModelV3.playAction = videoModel.playAction; + superPlayerModelV3.placeholderImage = videoModel.placeholderImage; + superPlayerModelV3.coverPictureUrl = videoModel.coverPictureUrl; + superPlayerModelV3.duration = videoModel.duration; + superPlayerModelV3.title = videoModel.title; + return superPlayerModelV3; + } + + private static boolean isSuperPlayerVideo(VideoModel videoModel) { + return videoModel.videoURL.startsWith("txsuperplayer://play_vod"); + } + + private static SuperPlayerModel playSuperPlayerVideo(VideoModel videoModel) { + SuperPlayerModel model = new SuperPlayerModel(); + String videoUrl = videoModel.videoURL; + String appIdStr = getValueByName(videoUrl, "appId"); + try { + model.appId = appIdStr.equals("") ? 0 : Integer.valueOf(appIdStr); + SuperPlayerVideoId videoId = new SuperPlayerVideoId(); + videoId.fileId = getValueByName(videoUrl, "fileId"); + videoId.pSign = getValueByName(videoUrl, "psign"); + model.videoId = videoId; + return model; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + private static String getValueByName(String url, String name) { //txsuperplayer://play_vod?v=4&appId=1400295357&fileId=5285890796599775084&pcfg=Default + String result = ""; + int index = url.indexOf("?"); + String temp = url.substring(index + 1); + String[] keyValue = temp.split("&"); + for (String str : keyValue) { + if (str.startsWith(name + "=")) { + result = str.replace(name + "=", ""); + break; + } + } + return result; + } + + /** + * 获取范围内的数据 + * + * @param min + * @param max + * @return + */ + private static Integer getRandomNumber(int min, int max) { + Random random = new Random(); + int result = random.nextInt(max) % (max - min + 1) + min; + return result; + } + + +} diff --git a/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/player/FeedPlayer.java b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/player/FeedPlayer.java new file mode 100644 index 0000000..09dbbaf --- /dev/null +++ b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/player/FeedPlayer.java @@ -0,0 +1,28 @@ +package com.tencent.liteav.demo.player.feed.player; + +import com.tencent.liteav.demo.player.expand.model.entity.VideoModel; + +public interface FeedPlayer { + + void preparePlayVideo(int position, VideoModel videoModel); + + void play(VideoModel videoModel); + + void resume(); + + void pause(); + + void reset(); + + void destroy(); + + boolean isPlaying(); + + boolean isFullScreenPlay(); + + void setWindowPlayMode(); + + void setFeedPlayerCallBack(FeedPlayerView.FeedPlayerCallBack callBack); + + FeedPlayerView.FeedPlayerCallBack getFeedPlayerCallBack(); +} diff --git a/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/player/FeedPlayerView.java b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/player/FeedPlayerView.java new file mode 100644 index 0000000..7255c7a --- /dev/null +++ b/Demo/superplayerdemo/src/main/java/com/tencent/liteav/demo/player/feed/player/FeedPlayerView.java @@ -0,0 +1,226 @@ +package com.tencent.liteav.demo.player.feed.player; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.FrameLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.tencent.liteav.demo.player.expand.model.entity.VideoModel; +import com.tencent.liteav.demo.player.feed.FeedPlayerManager; +import com.tencent.liteav.demo.player.feed.model.FeedVodListLoader; +import com.tencent.liteav.demo.superplayer.SuperPlayerCode; +import com.tencent.liteav.demo.superplayer.SuperPlayerDef; +import com.tencent.liteav.demo.superplayer.SuperPlayerModel; +import com.tencent.liteav.demo.superplayer.SuperPlayerView; + + +/** + * feed流需求的播放器控件 + */ +public class FeedPlayerView extends FrameLayout implements FeedPlayer { + + + private SuperPlayerView superPlayerView = null; + private FeedPlayerCallBack feedPlayerCallBack = null; + private FeedPlayerManager feedPlayerManager = null; + private int position = -1; + private VideoModel videoModel = null; + private boolean playWithModelIsSuccess = true; + + + public FeedPlayerView(@NonNull Context context) { + super(context); + this.initViews(); + } + + public FeedPlayerView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + this.initViews(); + } + + public FeedPlayerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + this.initViews(); + } + + private void initViews() { + superPlayerView = new SuperPlayerView(getContext()); + superPlayerView.showOrHideBackBtn(false); + superPlayerView.setPlayerViewCallback(new SuperPlayerView.OnSuperPlayerViewCallback() { + @Override + public void onStartFullScreenPlay() { + if (feedPlayerCallBack != null) { + feedPlayerCallBack.onStartFullScreenPlay(); + } + } + + @Override + public void onStopFullScreenPlay() { + if (feedPlayerCallBack != null) { + feedPlayerCallBack.onStopFullScreenPlay(); + } + } + + @Override + public void onClickFloatCloseBtn() { + + } + + @Override + public void onClickSmallReturnBtn() { + if (feedPlayerCallBack != null) { + feedPlayerCallBack.onClickSmallReturnBtn(); + } + } + + @Override + public void onStartFloatWindowPlay() { + + } + + @Override + public void onPlaying() { + if (feedPlayerManager != null) { + feedPlayerManager.setPlayingFeedPlayerView(FeedPlayerView.this, position); + } + } + + @Override + public void onPlayEnd() { + + } + + @Override + public void onError(int code) { + if (SuperPlayerCode.VOD_REQUEST_FILE_ID_FAIL == code) { + playWithModelIsSuccess = false; + } + if (feedPlayerManager != null) { + feedPlayerManager.removePlayingFeedPlayerView(position); + } + } + }); + addView(superPlayerView); + } + + /** + * 设置播放器管理类 + * + * @param manager + */ + public void setFeedPlayerManager(FeedPlayerManager manager) { + feedPlayerManager = manager; + } + + /** + * 设置回调接口 + * + * @param callBack + */ + @Override + public void setFeedPlayerCallBack(FeedPlayerCallBack callBack) { + feedPlayerCallBack = callBack; + } + + @Override + public FeedPlayerCallBack getFeedPlayerCallBack() { + return feedPlayerCallBack; + } + + + @Override + public void preparePlayVideo(int position, VideoModel videoModel) { + playWithModelIsSuccess = true; + this.position = position; + this.videoModel = videoModel; + SuperPlayerModel playerModel = FeedVodListLoader.conversionModel(videoModel); + if (playerModel != null && superPlayerView != null) { + superPlayerView.playWithModel(playerModel); + } + } + + @Override + public void play(VideoModel videoModel) { + SuperPlayerModel playerModel = FeedVodListLoader.conversionModel(videoModel); + if (playerModel != null && superPlayerView != null) { + superPlayerView.playWithModel(playerModel); + superPlayerView.onResume(); + } + } + + @Override + public void resume() { + if (superPlayerView != null) { + if (playWithModelIsSuccess) { + superPlayerView.onResume(); + } else { + play(videoModel); + } + } + } + + @Override + public void pause() { + if (superPlayerView != null) { + superPlayerView.onPause(); + } + } + + public void stop() { + if (superPlayerView != null) { + position = -1; + superPlayerView.stopPlay(); + } + } + + @Override + public void reset() { + position = -1; + if (superPlayerView != null) { + superPlayerView.revertUI(); + } + } + + @Override + public void destroy() { + reset(); + if (superPlayerView != null) { + superPlayerView.setPlayerViewCallback(null); + superPlayerView.resetPlayer(); + superPlayerView.release(); + } + superPlayerView = null; + } + + @Override + public boolean isPlaying() { + return superPlayerView.getPlayerState() == SuperPlayerDef.PlayerState.PLAYING; + } + + @Override + public boolean isFullScreenPlay() { + return superPlayerView.getPlayerMode() == SuperPlayerDef.PlayerMode.FULLSCREEN; + } + + @Override + public void setWindowPlayMode() { + superPlayerView.switchPlayMode(SuperPlayerDef.PlayerMode.WINDOW); + } + + public int getPosition() { + return position; + } + + public interface FeedPlayerCallBack { + void onStartFullScreenPlay(); + + void onStopFullScreenPlay(); + + void onClickSmallReturnBtn(); + + } + + +} diff --git a/Demo/superplayerdemo/src/main/res/layout/feed_activity_layout.xml b/Demo/superplayerdemo/src/main/res/layout/feed_activity_layout.xml new file mode 100644 index 0000000..18d2a33 --- /dev/null +++ b/Demo/superplayerdemo/src/main/res/layout/feed_activity_layout.xml @@ -0,0 +1,40 @@ + + + + + +